免责声明:本系列与靶场均具备真实 RCE 能力,仅限本人自建实验环境学习与防御研究,严禁对未授权系统使用。
这一篇分三层:代码怎么根治、上线怎么审计、出事怎么发现与处置。本篇自洽。
引子:一个安全从业者的"日常三问"
前面五篇都是"进攻视角":怎么找 gadget、怎么绕 checkAutoType、怎么把一条日志/JSON 变成 shell。但在真实岗位(甲方、应急、审计、开发安全)上,更常站在防守视角,被反复问三件事:
-
"系统有没有这个洞?"------需要扫依赖、看版本、翻代码;
-
"怎么修才彻底?"------需要分清"根治"和"缓解",而不是只会加 WAF;
-
"出事怎么发现、怎么处置?"------需要设计检测规则、会看日志、能走应急流程。
本篇把 Fastjson 防御拆成三层:代码层根治、审计层方法、检测与应急层落地,并给出一张"措施---性质---是否根治"的对照表,把零散的 payload 知识转化成可交付的防御能力。
同样的思路可以平移到 Log4j2 系列------这就是组件漏洞学习的通用套路。
本篇要点(读完应能回答):
-
为什么"用固定目标类型解析"能从根上阻止 autoType 攻击?
-
审计时除了 grep
JSON.parse,还要看哪两样东西才能判断"是否真的可利用"? -
日志里大量出现
autoType is not support意味着什么?怎么处置? -
WAF / 出网管控 / 升级 JDK 分别处在防御的哪一层?为什么都不能替代根因修复?
一、根治:别让不可信输入决定"加载哪个类"
回顾整个系列的根因:@type 让输入决定要实例化的类。 所以最有效的防御就一句话:
凡是用户可控的数据,都不允许它通过 autoType 去决定类名。
具体做法,按推荐程度从高到低:
1. 用"指定目标类型"的解析,而不是 JSON.parse
// 危险:目标类型未知,@type 生效
Object o = JSON.parse(userInput);
// 安全:明确目标类型;@type 与目标类型不符会被拒
User u = JSON.parseObject(userInput, User.class);
只要目标类型是固定的 DTO,攻击者就算塞 @type 也实例化不了危险类。这是性价比最高的一条。
2. 关闭 autoType / 开启 safeMode
// 明确关闭(1.2.25+ 默认就是 false,但要防止被人打开)
ParserConfig.getGlobalInstance().setAutoTypeSupport(false);
// 1.2.68+:彻底关闭 autoType,黑白名单都不看
ParserConfig.getGlobalInstance().setSafeMode(true);
// 或启动参数:-Dfastjson.parser.safeMode=true
注意检查启动脚本/中间件配置里有没有人加过
-Dfastjson.parser.autoTypeSupport=true------这类"为了兼容"的开关是重灾区。
3. 必须用多态时,用"最小白名单"
// 好:只允许自己的具体包/类
ParserConfig.getGlobalInstance().addAccept("com.mycompany.api.dto.");
// 差(等于没防):宽泛前缀
// addAccept("com.");
// addAccept("java.");
以上
ParserConfig.getGlobalInstance().setAutoTypeSupport(false)/setSafeMode(true)/addAccept(...)的逐词含义(类、方法、参数分别是什么)见 02 篇的「逐词拆解」。
4. 升级 / 迁移
-
1.x:升到 1.2.83+;
-
更优:迁到 fastjson2 (默认安全模型不同)或 Jackson,并关闭多态反序列化。
5. 纵深防御(不能替代上面)
-
出网管控:限制应用服务器对外的 LDAP/RMI 访问,能挡住大量 JNDI 类链;
-
JDK 保持较新,不要加
trustURLCodebase=true; -
WAF/网关做关键词与结构检测(见下),但已知可以绕过,只作辅助。
二、代码审计:拿这几条去搜
审计 Java 项目时,围绕"用户输入 + Fastjson + autoType"三个要素定位:
关键词(源码 / 配置 / 启动参数)
JSON.parse( # 目标类型未知,重点
JSON.parseObject( # 看是否给了固定 class;没给的要评估
setAutoTypeSupport(true)
fastjson.parser.autoTypeSupport
addAccept( # 看白名单是否过宽
setSafeMode( # 有没有开;搜不到往往意味着没开
@type # 配置/日志里出现要警惕
Feature.SupportNonPublicField
JSONReader.Feature.SupportAutoType
危险模式
-
JSON.parse(request.getParameter(...))/JSON.parse(body):直接把请求体丢进去; -
@RequestBody接收的是Object/Map而不是具体 DTO; -
全局打开了 autoType,或用反射式
parseObject(json, Class.forName(userType)); -
版本低于 1.2.83,且没有 safeMode;
-
项目依赖树里有大量"可被组合的 gadget"(commons-collections、xbean、ignite、shiro 等)------这决定了绕过能不能落地。
看依赖
mvn dependency:tree | grep -i fastjson # 打印依赖树并筛选 fastjson 相关条目(含实际使用的版本)
# 关注版本号:< 1.2.83 需重点评估;同时看是否混入了危险 gadget 库
预期输出示例(列出实际依赖的版本):
[INFO] +- com.alibaba:fastjson:jar:1.2.24:compile
[INFO] \- com.alibaba.fastjson2:fastjson2:jar:2.0.65:compile
只要看到 fastjson 1.x 且版本低于 1.2.83,就应重点评估;再结合是否开启 autoType 与依赖树里有无 gadget 判断可利用性。
三、流量与主机检测
1. 流量侧(网关 / NIDS / WAF / 日志)
Fastjson 攻击的流量特征集中在"输入里的类名":
-
JSON 请求体中出现
"@type",尤其是值包含com.sun、java.lang、javax.naming、org.apache等; -
出现
JdbcRowSetImpl、TemplatesImpl、dataSourceName、_bytecodes、_outputProperties等字段; -
出现
ldap://、rmi://、dns等外联特征; -
_bytecodes后面跟着长 Base64 串。
WAF 规则示例(示意,非万能):命中
"@type"且同时命中JdbcRowSetImpl|TemplatesImpl|ldap://|rmi://则告警/拦截。务必记住 :WAF 可以被变形绕过(大小写、编码、L...;、缓存写法等),它只能降低风险,不能替代根因修复。
2. 主机侧
-
进程异常创建子进程(
Runtime.exec/ProcessBuilder触发bash、curl、wget); -
应用进程产生到内网 LDAP/RMI 端口的异常外联;
-
临时目录出现可疑
.class/.jar落盘; -
日志里出现
autoType is not support的突发大量记录------可能是攻击者在试探 payload(这条非常实用:攻击者会不断换写法,被拦截也会留痕)。
3. 典型案例的处置闭环
-
发现 :日志/告警出现
@type、ldap://、autoType is not support; -
定位:确认受影响服务与 fastjson 版本、是否开启 autoType;
-
阻断:下线/隔离,限制出网;
-
修复 :升级 1.2.83+/fastjson2,或开 safeMode,改
JSON.parse为固定类型; -
排查:检查是否已被利用(子进程、外联、落盘、账号);
-
复盘:加检测规则、收敛依赖树。
四、一张"防御对照表"
| 措施 | 属于 | 能否根治 | 说明 |
|---|---|---|---|
用固定类型的 parseObject |
代码 | ✅ | 目标类型固定,@type 失去作用 |
| 关闭 autoType / safeMode | 配置 | ✅(基本) | 直接不启用该能力 |
| 升 1.2.83+ / fastjson2 | 升级 | ✅ | 消除已知绕过 |
| 最小白名单 | 配置 | ⚠️ | 取决于白名单是否真的最小 |
| 升级 JDK / 关 JNDI 远程加载 | 环境 | ❌ | 只挡住 JNDI 类链,TemplatesImpl 等仍可能 |
| WAF / 出网管控 | 边界 | ❌ | 纵深防御,可被绕过 |
五、总结
-
为什么"用固定的目标类型解析"能从根上阻止 autoType 攻击?
-
safeMode和"关闭 autoType"有什么区别?为什么官方推荐两者都考虑? -
审计时,除了 grep
JSON.parse,还要看哪两样东西才能判断"是否真的可被利用"? -
日志里出现大量
autoType is not support意味着什么?怎么处置? -
为什么说"WAF 不能替代修复"?