背景
Fastjson 相关安全问题在很多老系统里仍然比较常见,尤其是一些历史 Java Web 项目、内部管理平台、网关服务和定时任务系统。实际排查时,很多团队只关注“版本是否存在漏洞”,但从代码审计角度看,风险往往不止来自组件版本,还包括反序列化入口、AutoType 配置、全局 ParserConfig、异常日志泄露以及业务对象边界不清晰等问题。
这篇文章整理一套偏防守和审计视角的排查方法,适合用于内部资产自查、代码审计复核和安全加固,不涉及攻击利用细节。
问题场景
在一次老项目安全复核中,发现项目依赖里存在较早版本的 fastjson,同时代码中大量使用 JSON.parseObject、JSON.parseArray 和 JSONObject.toJavaObject。项目本身并没有直接暴露“反序列化接口”,但多个控制器会接收前端传入的 JSON 字符串,再转换为业务对象。
这类场景的主要问题是:开发人员通常认为“只是解析 JSON”,但如果组件版本较老、配置不当,或者解析目标类型不受控,就可能扩大攻击面。
常见风险点
- 组件版本过旧:老版本 Fastjson 曾多次出现反序列化相关安全问题,不能只依赖黑名单机制。
- 开启 AutoType:历史代码中可能通过全局配置开启 AutoType,导致类型解析边界变宽。
- 直接解析用户输入:将 HTTP 参数、消息队列内容、文件内容直接交给 JSON.parseObject 处理。
- 解析目标类型不明确:使用 JSONObject、Object、Map 等宽泛类型接收外部输入。
- 依赖链复杂:项目中存在大量第三方依赖,即使业务代码简单,也可能扩大反序列化后的类型可达范围。
- 日志记录不当:异常栈、原始请求体、内部类名泄露,可能帮助外部人员判断技术栈。
代码审计关注点
审计时不建议只做关键词搜索后简单下结论。更有效的方法是先定位输入边界,再追踪 JSON 解析链路,最后确认解析目标类型和安全配置。
可以优先搜索以下调用点:
JSON.parseObject(...)
JSON.parseArray(...)
JSONObject.parseObject(...)
JSONObject.toJavaObject(...)
JSON.parse(...)
ParserConfig.getGlobalInstance()
setAutoTypeSupport(true)如果项目较大,可以结合 ripgrep 或 IDE 全局搜索:
rg "parseObject|parseArray|toJavaObject|setAutoTypeSupport|ParserConfig" src/审计时重点看三个问题:
- 传入 JSON 解析方法的数据是否来自外部可控输入。
- 解析时是否指定了明确、稳定的业务 DTO 类型。
- 项目是否存在全局开启 AutoType 或自定义反序列化配置。
一个典型的风险写法
下面这种写法在历史系统中比较常见。它的问题不一定等同于“必然存在漏洞”,但从审计角度看,边界过宽,后续维护风险较高。
public Object handle(String body) {
return JSON.parseObject(body, Object.class);
}更稳妥的写法是定义明确的请求对象,并对字段做白名单校验:
public SubmitRequest handle(String body) {
SubmitRequest request = JSON.parseObject(body, SubmitRequest.class);
validate(request);
return request;
}如果是在 Spring MVC 场景中,更建议让框架完成请求体到 DTO 的绑定,并配合参数校验,而不是在控制器里手动解析任意 JSON 字符串。
@PostMapping("/submit")
public Result submit(@Valid @RequestBody SubmitRequest request) {
return service.submit(request);
}依赖版本排查
Maven 项目可以先查看依赖树,确认 fastjson 是否直接引入,还是被其他组件间接引入。
mvn dependency:tree | grep fastjsonGradle 项目可以使用:
./gradlew dependencies | grep fastjson排查时需要注意两点:
- 同一个项目可能存在多个模块,不同模块依赖版本不一致。
- 打包后的实际依赖以最终产物为准,不能只看某一个 pom.xml。
如果项目仍在使用 Fastjson 1.x,建议结合业务兼容性评估升级到维护状态更好的版本,或者迁移到 Jackson、Gson 等替代方案。升级前应准备回归测试,重点覆盖日期格式、枚举、泛型、字段别名、空值处理等场景。
安全配置建议
如果业务没有明确需求,不建议开启 AutoType。历史项目中可以重点确认是否存在如下配置:
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);如果发现全局开启,应先确认是谁依赖了这个行为。实际处理时不要简单删除后直接上线,建议按以下顺序进行:
- 定位依赖 AutoType 的业务接口或消息消费逻辑。
- 将宽泛对象解析改为明确 DTO 解析。
- 补充回归测试,覆盖历史 JSON 样例。
- 关闭全局 AutoType,并灰度发布观察。
对于必须进行多态反序列化的业务,应尽量限制在内部可信数据流中,并对允许的类型做显式白名单管理,避免把任意外部输入交给类型解析逻辑。
接口层防护要点
很多风险最终都会落到接口输入边界。除了组件升级,接口层也应做基本约束。
- 限制请求体大小:避免超大 JSON 导致解析资源消耗。
- 限制字段集合:不要接受业务无关字段,减少对象污染和兼容性隐患。
- 统一异常处理:不要将完整异常栈和内部类名返回给客户端。
- 记录必要审计日志:保留接口、时间、来源、错误类型,不建议原样记录敏感请求体。
- 配置 WAF 或网关规则:用于发现异常 JSON 特征,但不要把它作为唯一防线。
日志与监控排查
在事件复盘或风险自查中,可以从日志里寻找异常解析行为。常见线索包括:
- JSON 解析异常频繁出现。
- 异常日志中出现非业务类名。
- 某些接口收到结构异常复杂的请求体。
- 同一来源短时间内访问多个 JSON 接口。
- 网关层出现大量 400、500 状态码。
建议将 JSON 解析异常单独归类,避免被普通业务异常淹没。例如在统一异常处理里对反序列化失败、参数绑定失败做分类统计。
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<Result> handleJsonError(HttpMessageNotReadableException e) {
log.warn("json parse failed: {}", e.getClass().getSimpleName());
return ResponseEntity.badRequest().body(Result.fail("invalid request body"));
}灰度加固流程
生产系统加固不建议一次性大改。比较稳妥的流程如下:
- 第一步,梳理 fastjson 依赖版本和调用点。
- 第二步,标记外部可控输入进入 JSON 解析的位置。
- 第三步,将 Object、JSONObject、Map 等宽泛解析逐步改成 DTO。
- 第四步,关闭不必要的 AutoType 配置。
- 第五步,升级依赖并进行兼容性回归。
- 第六步,上线后观察异常率、接口错误率和业务关键指标。
实际项目里,最容易出问题的是“升级后序列化格式变化”。例如日期格式、BigDecimal 精度、空字段输出策略,都会影响前后端或上下游服务。因此升级前最好收集一批真实但脱敏的历史请求样例,做自动化对比。
审计清单
| 检查项 | 建议 |
|---|---|
| Fastjson 版本 | 确认是否为历史高风险版本,优先升级到安全版本或迁移替代库 |
| AutoType | 默认关闭,避免全局开启 |
| 解析入口 | 外部输入进入 JSON 解析前应有明确边界 |
| 目标类型 | 优先使用明确 DTO,避免 Object、JSONObject 承接复杂输入 |
| 异常处理 | 客户端返回通用错误,服务端保留必要审计信息 |
| 依赖范围 | 确认最终打包产物中的实际依赖版本 |
| 测试覆盖 | 升级前准备日期、枚举、泛型、空值等兼容性测试 |
注意点
- 不要把“没有公开接口”当作安全结论,消息队列、文件导入、内部 RPC 同样可能是输入面。
- 不要只看代码仓库里的依赖声明,最终运行环境里的 jar 才是实际结果。
- 不要简单依赖黑名单。安全边界应建立在明确类型、最小权限和输入约束上。
- 不要在日志中完整打印用户提交的 JSON,尤其是包含 token、手机号、身份证号等敏感字段时。
- 不要在不了解业务兼容性的情况下直接替换 JSON 库,否则可能引入隐蔽的业务问题。
总结
Fastjson 风险排查的重点不是记住某个漏洞编号,而是建立一套稳定的审计方法:先看依赖版本,再看输入边界,接着看解析目标类型和全局配置,最后结合日志监控和回归测试完成加固。
从长期维护角度看,最有效的做法是减少宽泛反序列化入口,使用明确 DTO 承接外部输入,关闭不必要的 AutoType,并保持依赖版本可控。这样即使后续组件再出现新的安全问题,系统暴露面也会小很多。
推荐意见