NoFastJson:寻找新的攻击面"

admin 2026-11-09 01:16:46 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

No FastJson: 寻找新的攻击面

原创

路人甲
路人甲

红细胞安全实验室

2026年11月6日 22:09
湖南

去阅读

前景回顾

在《FastJson 1.2.83 远程代码执行分析》一文中,我们简要分析了fastjson 1.2.83下无gadget的代码执行原理,其本质是由于双亲委派机制导致的。我们在前文分析的过程中,已经明白了为什么默认的springboot fatjar场景下无法打成单http实现代码执行的原因,是因为在走进com.alibaba.fastjson.util.TypeUtils#loadClass(java.lang.String, java.lang.ClassLoader, boolean)的时候,由于传入的defaultClassLoader为null,转而选用了Thread.currentThread().getContextClassLoader()来获取当前上下文类加载器,也就是内置的TomcatEmbeddedWebappClassLoader,其loadClass方法调用了Class.forName,然后在forName0校验的过程中对类名做了限制,导致无法实现单http的情况下RCE。只有当设置了ParserConfig的defaultClassLoader或者在其他中间件环境下其不为空的情况下,才能正常走进URLClassLoader#loadClass,这样才不会存在前置的forName0校验类名。

No FastJson

眼尖的读者可能发现了其中的问题,在TomcatEmbeddedWebappClassLoader当中最终是通过org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader#loadFromParent来加载我们的payload

当中关键的是这个传入的payload并没有经过任何变化,最终保持了原始输入走到了这里,通过Class.forName触发了后续的类加载,而由于这里设置了第二个参数为false,导致在这里并不会执行类加载的初始化行为。

是不是可以有一个大胆的猜想,无需fastjson,仅通过Class.forName就能够触发呢?我们编写如下测试控制器来验证

这在很多产品业务代码里是一个很常见的场景,甚至在不少组件状态传递的过程中也有这种场景。

前面我们说了,由于Class.forName的第二个参数为false,不会执行初始化行为,也就是在这里不会执行类当中的静态代码块,只有等到被主动触发的时候才会执行。所以这里我们的payload需要改动一下,不再使用@JsonType注解:

通过javaassit修改成特定类名后如下:

接着构造请求来验证猜想:

这里其实也不能使用http的方式来加载,因为走的还是forName0,会校验类名的合法性。

我们下断点来回顾一下这个过程

这里通过ClassLoader.getClassLoader(caller)拿到了调用当前方法的类的类加载器(传入的caller就是调用Class.forName的类)

接着往下走

到这里的后续逻辑和前文基本一致了

在findClass的过程中,jdk本身也会通过getResource来查找资源,而前文我们说过,getResource本质是通过new URL来实现的,自然支持jar:来加载资源。

所以到这里这个由于类加载导致的问题已经可以完全脱离fastjson本身,将漏洞利用的条件下降至只需要fatjar + jdk,且仅需要Class.forName可控即可,而实战场景中能够控制Class.forName的场景足够多,以历年最经典的jdbc attack来举例:

引入的均为最新版依赖,mysql-connector-java 8.0.33已经修复getConnection阶段触发的反序列化,而postgresql 42.7.13也早已修复socketFactory,将其限制在了期望类内。

而在目前仅需Class.forName可控就能RCE的加持下,这些限制都不再是问题,我们先以mysql-connector-java 8.0.33为例,其触发类加载的在8.x从statementInterceptors变为了queryInterceptors

指定该参数后,传入的类名会通过com.mysql.cj.jdbc.ConnectionImpl#initializeSafeQueryInterceptors当中的Util.loadClasses进行加载,但是这里限制了类型必须是QueryInterceptor.class

也就是如果类型不匹配,其不会继续构造实例。解决办法也很简单,给我们的恶意类加上实现QueryInterceptor接口即可,这里可以选择在其初始化的时候植入恶意代码或是无参构造等都可以

接着同样修改为特殊类名

然后构造请求验证:

接下来换PostgreSQL,同样的也在之前的修复里判断了期望类

我们只需要给我们的恶意类继承至SocketFactory即可

修改为特殊类名后:

接着构造请求继续验证:

最后

jdbc场景只是抛砖引玉,相信未来会有更多姿势来利用这个点。目前实测很多产品在此基础上可以老洞开新花,剩下的玩法留给读者自行探索了。

本文当中所提及知识来自实战代码审计班课程内容,关于该产品更加深入利用手法可以咨询课程。

顺便再推荐一下我师傅的代码审计课程。近期某报表的课程章节已结束,马上开启新的章节,感兴趣的朋友可以尽快加入。课程中包含各类产品最新补丁对应的 0day 漏洞(非水洞) 的挖掘与分析;过往课程也涵盖多款市面产品的 0day / 1day / nday 漏洞案例讲解。课程后续会持续更新推进,支持一次报名长期学习与答疑。适合真正对代码审计感兴趣、或想系统学习但缺少思路与方法体系的同学(如有其他目的请勿打扰)。对基础要求不高,能读懂代码即可,真正帮助你从只能读懂代码到找到问题。个人学习过程中受益良多,特此分享推荐给希望提升自己的朋友(非广告)。若有打扰,敬请见谅。

下方是进阶班课表

下方是基础班课表

评论:0   参与:  0