PHP后端的反序列化POP链攻击,本质上是攻击者通过构造特定的序列化字符串,触发应用程序中一系列关联的类方法调用(属性导向编程,Property-Oriented Programming),最终达到执行任意代码或危险操作的目的。修复的核心在于严格校验反序列化数据源、禁用危险魔术方法或在反序列化过程中进行深度检查。
理解反序列化与POP链的工作原理
PHP的unserialize()函数用于将序列化的字符串转换回PHP值或对象。当一个对象被反序列化时,PHP会自动调用其__wakeup()或__destruct()等魔术方法。POP链挖掘就是寻找一条从这些自动调用的“起点”方法,到最终能执行危险操作(如eval()、system())或进行文件操作、数据库查询的“终点”方法之间的调用路径。这条路径通常由多个类的__toString()、__get()、__call()等魔术方法串联而成。
典型的POP链构造与攻击示例
假设我们有以下存在缺陷的类定义:
class ExampleClass {
public $data;
public function __destruct() {
echo $this->data; // 触发__toString()
}
}
class DangerousClass {
private $cmd;
public function __toString() {
system($this->cmd); // 危险操作终点
}
}攻击者可以构造这样的攻击链:首先创建一个DangerousClass对象,并将其$cmd属性设置为系统命令。然后,创建一个ExampleClass对象,将其$data属性设置为刚才的DangerousClass对象。当这个ExampleClass对象被序列化后传输给后端并反序列化时,流程如下:
1. 反序列化完成,触发ExampleClass::__destruct();
2. __destruct()尝试echo $this->data,由于$data是一个对象,PHP会尝试将其转换为字符串,从而触发DangerousClass::__toString();
3. __toString()执行了system($this->cmd),命令注入成功。挖掘POP链的关键在于代码审计,梳理所有类的魔术方法及其之间的属性关联。
挖掘POP链的实战方法与工具
手动挖掘POP链分为四步:第一步,全局搜索所有包含魔术方法(__destruct, __wakeup, __toString, __get, __call等)的类,将其标记为可能的“入口点”。第二步,分析这些入口点方法中的代码,看其操作的对象属性是否用户可控,以及这些操作是否会触发其他对象的魔术方法(例如,将对象当作字符串使用、调用对象的方法、访问对象的属性)。第三步,以图的形式绘制出类与方法之间的调用关系,寻找从入口点到危险函数(如eval, include, system, unlink)的路径。第四步,根据路径构造序列化字符串进行验证。自动化工具方面,PHPGGC(PHP Generic Gadget Chains)收录了针对主流框架(如Laravel, Symfony, ThinkPHP)的通用POP链,在已知框架类型和版本时可以直接利用。对于自研代码,可以使用静态分析工具(如RIPS, PHPStan)辅助寻找敏感函数调用链。
根本性修复策略:禁用或替换不安全反序列化
最彻底的修复方案是避免使用PHP原生反序列化。对于数据传输,应优先使用JSON、XML等安全格式。如果业务必须使用序列化,可以考虑使用json_encode()/json_decode()替代,因为JSON格式不支持对象表示,从根本上杜绝了对象魔术方法的触发。另一种方案是使用PHP的serialize()和unserialize(),但配合白名单机制。PHP 7.0+提供了unserialize()的第二个参数,可以指定允许反序列化的类名白名单。
// 只允许反序列化SafeClassA和SafeClassB $data = unserialize($input, ['allowed_classes' => ['SafeClassA', 'SafeClassB']]);
将allowed_classes设置为false则将所有对象反序列化为__PHP_Incomplete_Class对象,完全阻止对象魔术方法的执行,这是一种非常有效的缓解措施。
深度防御:输入校验与运行时监控
如果无法立即更换序列化方案,必须实施深度防御。首先,严格校验反序列化数据的来源,确保其来自可信的、经过身份验证的通道,并对数据进行强完整性校验(如数字签名)。其次,在反序列化之前,可以对字符串进行严格的格式检查,例如使用正则表达式匹配,禁止字符串中出现可能指向危险类的对象表示。再者,在代码层面,审查并重写危险的魔术方法,避免在其中执行关键操作。例如,在__destruct()中只进行简单的资源清理,不执行复杂的逻辑。最后,部署运行时应用自我保护机制,监控反序列化过程中异常的函数调用链,特别是对system、eval、file_put_contents等敏感函数的调用,一旦发现立即中断并告警。
安全开发规范与代码审计流程
将反序列化安全纳入开发规范至关重要。规范中必须明确:
1. 禁止反序列化任何用户可控的输入;
2. 如必须使用,必须采用带有严格白名单的unserialize();
3. 谨慎编写类的魔术方法,避免在其中使用用户可控的属性去调用其他对象的方法或进行文件、数据库、系统命令操作。在代码审计流程中,反序列化点应作为高危审计项。审计时,不仅看反序列化函数本身,还要追踪其参数来源。对于发现的POP链,修复时不仅要打断当前链,还要思考是否存在其他类似的链,进行系统性修复。
总结:构建纵深防御体系
防御PHP反序列化POP链攻击是一个系统工程,不能依赖单一措施。一个有效的纵深防御体系包括:在入口处,使用JSON等安全格式替代序列化(预防层)。在反序列化点时,强制使用白名单机制(核心防御层)。在代码实现中,遵循安全规范编写魔术方法(减少攻击面层)。在服务器层面,部署代码安全监控与入侵检测系统(运行时防护层)。通过多层防护,即使某一层被绕过,其他层仍能提供有效保护,从而显著提升后端服务的安全性。
