0x00 unserialize函数
讲到php反序列化漏洞首先要提到unserialize函数,这个函数是php反序列化漏洞利用的基础。
php官方文档对unserialize()的说明是:unserialize() 对单一的已序列化的变量进行操作,将其转换回 PHP 的值。若被解序列化的变量是一个对象,在成功地重新构造对象之后,PHP 会自动地试图去调用 __wakeup() 成员函数(如果存在的话)。
这也是我们要去利用的地方。
0x01 理解序列化的字符串
要学习反序列化漏洞,还要明白序列化后的字符串的意义,方便我们构造payload。
例如:
1 |
|
运行这个php
1 | 233.txt |
第二行就是我们序列化之后的内容,我们来分析一下
O:对象,对象名长度为3,名字为“foo”,有一个参数。
s:字符串,长度为8,变量名字符串为“filename”
我们执行序列化之后可以将类,数组等压缩到一起。
注:a表示数组,i表示integer
0x02 php反序列化漏洞
php反序列化漏洞又称对象注入,可能会导致远程代码执行(RCE)
反序列化漏洞的执行过程就是通过可控的unserialze函数,构造一个类,调用这个类并执行魔术方法,然后执行类中的函数,达到攻击目的。
所以要利用反序列化必须满足两个条件:
1、应用程序中必须有一个实现某种php魔术方法的类,可用于执行恶意攻击。
2、当调用脆弱的unserialize()时,必须声明攻击期间所使用的所有类,否则必须支持此类的对象自动加载。
魔术方法:
魔术方法不需要调用就可以执行。
常见的魔术方法如下:
__construct() 类的构造函数
__destruct() 类的析构函数
__call() 在对象中调用一个不可访问方法时调用
__callStatic() 用静态方式中调用一个不可访问方法时调用
__get() 获得一个类的成员变量时调用
__set() 设置一个类的成员变量时调用
__isset() 当对不可访问属性调用isset()或empty()时调用
__unset() 当对不可访问属性调用unset()时被调用
__sleep() 执行serialize()时,先会调用这个函数
__wakeup() 执行unserialize()时,先会调用这个函数
__toString() 类被当成字符串时的回应方法
__invoke() 调用函数的方式调用一个对象时的回应方法
__set_state() 调用var_export()导出类时,此静态方法会被调用
__clone() 当对象复制完成时调用
__debugInfo() 打印所需调试信息
__autoload() 尝试加载未定义的类
0x03 实战
HBctf中有一道综合性的web题,最后的部分刚好是反序列化。
查看源码:
1 | you are not admin ! |
直接在get中构造赋值并没有用。
想到了php的伪协议 php://input
,同时post数据the user is admin
第一步绕过,到了include($file); //class.php
,提示我读取class.php
可以运用伪协议php://filter
读取文件
利用方法:php://filter/convert.base64-encode/resource=xxxxx.php
得到base64编码后的class.php,解密得:
1 |
|
这里定义了一个Read类,有魔术方法__toString。
然后我用php伪协议读取了一下index.php,真正的源码如下
1 |
|
有unserialize(),有class类和魔术方法__toString(),我们就可以进行反序列化攻击了。
构造:O:4:"Read":1:{s:4:"file";s:57:"php://filter/read=convert.base64-encode/resource=f1a9.php";}
解base64编码,得到flag。
bugku中的一个题与这个类似,有兴趣的可以去尝试一下
http://120.24.86.145:8006/test1/index.php
0x04 如何防御php反序列化漏洞
防御php反序列化漏洞很简单。
1、对参数进行过滤转义处理。
2、换用更安全的函数。