1. 项目概述:当反序列化遇上原生类
在PHP安全研究领域,反序列化漏洞一直是一个“宝藏”话题,它不像SQL注入那样直观,也不像XSS那样常见,但一旦被利用,往往能直捣黄龙,拿到服务器的控制权。而“原生类反序列化”更是这个宝藏中的“稀有矿脉”。很多开发者,甚至是一些安全人员,对反序列化的理解还停留在“ __wakeup ”和“ __destruct ”魔术方法上,认为只要没有这些危险的用户自定义类,反序列化操作就是安全的。这种想法在几年前或许还站得住脚,但如今,攻击者的目光早已投向了PHP语言本身自带的、我们每天都在使用却可能从未仔细审视过的“原生类”。
这次我们要深入探讨的,就是一个非常经典且极具代表性的组合技: 利用 SoapClient 原生类,结合CRLF注入,构造出一条无需任何用户自定义危险类即可触发的完整反序列化攻击链 。简单来说,攻击者可以只凭借一段精心构造的、完全由PHP标准类库(SPL)中的类对象组成的序列化字符串,就能让服务器对外发起一个任意HTTP请求,进而可能实现服务端请求伪造、内部服务探测甚至更严重的攻击。这个漏洞的巧妙之处在于,它完全绕过了对“危险类”的检查,因为 SoapClient 是PHP内置的、合法的、在很多SOAP服务中必须使用的类。理解这条利用链,不仅能让你深刻认识到反序列化漏洞的另一种维度,更能提升你在代码审计和防御建设中的视野。无论你是PHP开发者、安全研究员还是渗透测试工程师,掌握这个知识点都至关重要。
2. 核心原理深度拆解:为什么是SoapClient?
要理解这条利用链,我们必须先抛开“反序列化就是为了执行代码”的刻板印象。这条链的最终目标并非直接执行系统命令,而是 诱导服务器发起一个由攻击者控制的HTTP请求 。这属于SSRF的范畴。那么,一个反序列化的对象,如何能发起网络请求呢?答案就在 SoapClient 类的反序列化行为中。
2.1 SoapClient类的“记忆”与“执念”
SoapClient 是PHP用于调用Web Service(SOAP协议)的内置类。当你创建一个 SoapClient 对象时,通常会传入一个WSDL地址,客户端会根据这个地址获取服务描述,然后生成对应的调用方法。这个类有一个关键特性: 它会在内部存储最后一次请求的所有细节 ,包括目标URL、SOAPAction头、请求体等。
更关键的是它的 __call 魔术方法。当你调用一个不存在的方法时(在SOAP上下文中,这通常是你想调用的远程Web Service方法), __call 会被触发。它的默认行为是: 根据对象初始化时或内部存储的信息,构造一个完整的SOAP请求,并发送出去 。
现在,我们把反序列化加进来。当一个 SoapClient 对象被序列化后,其部分内部属性(包括那个存储着请求信息的 _stream_context 资源)会被保存。当这个字符串被反序列化还原成对象时,对象“复活”了,但它丢失了原始的socket连接等资源型属性。然而,某些用于构造请求的关键数据(在某些条件下)却得以保留。
攻击者的突破口就在这里:如果能在反序列化后,触发这个“复活”的 SoapClient 对象的 __call 方法,它就会尝试重新发起一个请求。而请求的细节,可以通过精心构造序列化字符串中的属性值来部分控制。
2.2 CRLF注入:操控HTTP请求的钥匙
控制请求的目标URL和内容体是困难的,因为 SoapClient 对请求格式有严格的SOAP协议约束。但是,攻击者发现了一个更巧妙的路径: 控制HTTP请求头 。
在HTTP协议中,头部字段与主体之间通过一个特殊的换行符分隔,即 \r\n\r\n (CRLF CRLF)。每个头部字段也以 \r\n 分隔。如果能将自定义的内容注入到HTTP头中,就能完全改变请求的含义。这就是CRLF注入攻击。
SoapClient 在构造请求时,会设置一个 User-Agent 头。在旧版本的PHP或特定配置下, SoapClient 对象中有一个 _user_agent 属性。如果这个属性在序列化时被恶意修改,并在反序列化后被用于构建请求头,那么攻击者就可以在这个 User-Agent 字符串中插入 \r\n ,从而提前结束头部块,并开始注入新的HTTP头部,甚至直接开始编写请求体。
例如,注入一个恶意的 Host 头、 Cookie 头,或者更危险的是,直接注入一个完整的 GET /admin HTTP/1.1 请求行,让服务器在同一个连接中发起第二个请求(即HTTP请求走私攻击的雏形)。但在我们讨论的经典利用链中,更直接的利用方式是: 在 User-Agent 后注入 \r\n ,然后加上一个 SOAPAction 头,而这个 SOAPAction 头的值,可以是一个指向内部网络服务的URL 。这样,当 SoapClient 尝试发送SOAP请求时,请求的目标就可能被 SOAPAction 头误导或影响,结合服务器处理逻辑的瑕疵,最终实现向任意地址发起请求。
2.3 链条闭合:反序列化触发与利用条件
整个链条可以概括为:
- 入口 :存在一个反序列化操作,参数用户可控。例如
unserialize($_GET[‘data’])。 - 载体 :攻击者构造一个恶意的序列化字符串,其中包含一个
SoapClient对象。该对象的_user_agent属性被设置为包含CRLF注入Payload的字符串(如”\r\nSOAPAction: \”http://internal-service/admin\”\r\n…”),同时_location等属性可能被设置为一个可控或无关的端点。 - 触发 :反序列化后,攻击者需要找到一个途径来触发这个
SoapClient对象的__call方法。这通常通过以下方式实现:- 如果代码中存在
$obj->someMethod()的调用,并且$obj正好是我们的恶意SoapClient对象,那么__call会被自动触发。 - 利用其他原生类的魔术方法(如
ArrayObject的__toString)在特定场景下(如被echo或进行字符串拼接时)自动触发,进而间接调用到SoapClient的__call。这需要更复杂的“属性嫁接”技巧,是高级利用的一部分。
- 如果代码中存在
- 执行 :
__call方法被触发后,SoapClient开始构建HTTP请求。由于_user_agent属性已被污染,构建出的请求头包含了恶意注入的SOAPAction或其他头部。这个请求被发送出去,目标可能是内网的Redis、MySQL管理接口、或其他Web服务,从而实现SSRF。
关键点 :这个漏洞的成功利用,极度依赖于PHP的
ext-soap扩展是否启用,以及PHP的版本。因为_user_agent属性的可序列化性及__call的具体行为在不同版本间有差异。通常,在PHP 5.x 系列和早期7.x版本中,利用条件更为成熟。
3. 漏洞环境搭建与关键配置
要真正理解并复现这个漏洞,光看理论是不够的,必须动手搭建环境。这里我分享一个最精简的复现环境配


465

被折叠的 条评论
为什么被折叠?



