- 类别:
bytecode别名:— 优先级:— - 作者:—
- 依赖:无声明依赖(构建期依赖 javassist;运行期由目标环境提供 Tomcat + fastjson)
- 实现:
Gadget(无基类)
一句话:把内层 gadget 产出的类字节码改写其父类为 java.lang.Exception,再交给上游写文件 gadget 落地到 Tomcat 的 docbase 目录,配合 fastjson 双 @type 技巧(CVE-2022-25845 类漏洞)加载并实例化该恶意类,实现 RCE。
Docbase 本身不产生攻击字节码,也不写文件、不发起加载;它是链条中间的一个「字节码后处理」环,唯一职责是给类打上 extends java.lang.Exception 的标记,以满足 fastjson autoType 加载时的类型校验约束。
- 入口
tags:BytecodeDocbaseTag—— 表示本 gadget 能承接「nextTags声明为BytecodeDocbaseTag」的上一环。在本知识库中该上一环即 fastjson 写文件类 gadget(见下「关联」),它们把 Docbase 的输出写入 docbase 路径。 - 衔接
nextTags:BytecodeConvertTag—— 表示 Docbase 之后(doCreate取到的内层)必须是一个标记了BytecodeConvertTag的 gadget,即 BytecodeConvert。BytecodeConvert 负责把最内层字节码 sink 的产物改名为攻击者指定的类名、按需混淆,然后交给 Docbase 改父类。 excludes:无。
标签方向要点:按知识库约定,外层 gadget 的 nextTags 必须匹配内层 gadget 的 tags。因此实际链条(配置顺序,外→内)为:
[ Fastjson写文件Gadget , Docbase , BytecodeConvert , <字节码 sink,如 Exec> ]
tags=FastjsonPayload tags=BytecodeDocbaseTag tags=BytecodeConvertTag tags=Bytecode/END
nextTags=BytecodeDocbaseTag nextTags=BytecodeConvertTag nextTags=Bytecode
执行方向相反(内→外,见 BUILD-SPEC 第 3 节):字节码 sink 先产出攻击字节码 → BytecodeConvert 改名/混淆 → Docbase 改父类为 Exception → Fastjson 写文件 gadget 把结果写盘并生成 fastjson 触发 payload。
Docbase 是一个极简的字节码处理 gadget,全部逻辑集中在 getObject 与 invoke 两个方法:
@GadgetTags(tags={"BytecodeDocbaseTag"}, nextTags={"BytecodeConvertTag"})
@GadgetAnnotation(name="生成tomcat-docbase需要的字节码", description="参考 …CVE-2022-25845-In-Spring…")
public class Docbase implements Gadget {
public byte[] getObject(byte[] bytes) throws Exception {
ClassPool pool = ClassPool.getDefault();
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
CtClass cc = pool.makeClass((InputStream)bis); // 从传入字节码重建 CtClass
CtClass exceptionClass = pool.get("java.lang.Exception");
cc.setSuperclass(exceptionClass); // 关键:把父类强改为 java.lang.Exception
return cc.toBytecode(); // 重新导出字节码
}
@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
byte[] bytecode = (byte[])chain.doCreate(context); // 取内层(BytecodeConvert)产出的字节码
return this.getObject(bytecode);
}
}逐段解释:
-
invoke→chain.doCreate(context):链由内向外构建。doCreate触发内层 BytecodeConvert,得到一段已改名/可选混淆的类字节码(byte[])。这段类内部通常带有攻击载荷——例如最内层为 Exec 时,类的静态代码块/构造逻辑会执行ProcessBuilder命令。 -
pool.makeClass(bis):用 javassist 的ClassPool从字节数组重建一个可编辑的CtClass对象。 -
cc.setSuperclass(pool.get("java.lang.Exception")):这是本 gadget 的唯一实质操作,也是整条 docbase 利用的命门。fastjson 的 autoType 触发点使用双@type写法:{ "@type":"java.lang.Exception", "@type":"org.example.Exception" }第一个
@type让 fastjson 把expectClass设为java.lang.Exception(异常类在很多黑名单/开关下仍被允许自动加载,因为反序列化异常本是常见需求);第二个@type给出真正要加载的类名org.example.Exception。fastjson 在加载后会用类似expectClass.isAssignableFrom(loadedClass)的类型校验来「放行」——只有当目标类确实是java.lang.Exception的子类时才会被实例化。Docbase 的setSuperclass正是为了让攻击者自造的类满足这一「必须继承 Exception」约束,否则加载会因类型不匹配被拒。 -
cc.toBytecode():导出改写后的字节码,返回给上层(fastjson 写文件 gadget),后者将其写入形如/tmp/tomcat-docbase.<随机后缀>/WEB-INF/classes/org/example/Exception.class的路径。
文件路径必须与类名一致(description 明示):因为最终由 Tomcat webapp 的 WebappClassLoader 按全限定类名去 docbase 的 WEB-INF/classes 下按目录结构定位 .class 文件。若类名为 org.example.Exception,则文件须落在 WEB-INF/classes/org/example/Exception.class。
本 gadget 无任何 @Param(params 为空数组),不接受用户配置。目标类名、写入路径、fastjson 版本等参数由链上其它 gadget 提供:
| 需要的配置 | 由谁提供 |
|---|---|
| 攻击载荷(命令/字节码逻辑) | 最内层字节码 sink,如 Exec |
| 目标类名(改名) | BytecodeConvert |
落地路径 DestPath / fastjson 版本 / Writer |
上游 fastjson 写文件 gadget,如 FastjsonCmonsio |
- 利用背景:description 指向
https://github.com/luelueking/CVE-2022-25845-In-Spring。CVE-2022-25845 是 fastjson 1.2.80 及之前 autoType 在「期望类型为异常类」等特定条件下可加载攻击者指定类的问题;当 Tomcat 的 docbase 目录可写(存在文件写入原语)时,把恶意类写进 webapp 的 classpath 再触发加载即可 RCE。 - 两个前置能力缺一不可:
- 文件写入原语:能把
.class写到 Tomcat docbase 的WEB-INF/classes下。本工具用 fastjson + commons-io 的写文件链实现(FastjsonCmonsio、FastjsonWriteFileJDK11),其DestPath默认即/tmp/tomcat_docbasexxx/WEB-INF/classes/xxx.class。 - 类加载触发:fastjson autoType 用双
@type让WebappClassLoader加载并实例化刚写入的类,触发其静态块/构造逻辑。
- 文件写入原语:能把
- 为什么必须继承
java.lang.Exception:这是绕过 fastjson 期望类型校验的硬约束——@type:"java.lang.Exception"使加载被限定为 Exception 子类,Docbase 的setSuperclass就是把任意攻击类「伪装成」异常子类。若跳过 Docbase 直接写普通类,fastjson 会因类型不匹配拒绝实例化。 - 环境要求:目标运行 Tomcat 且已知/可控 docbase 临时目录(Spring Boot 内嵌 Tomcat 部署 war/解压场景常见);fastjson 版本落在受影响区间;
WebappClassLoader能从WEB-INF/classes加载。 - javassist 依赖:Docbase 在构建 payload 的一方(java-chains 运行端)使用 javassist 改父类,目标机不需要 javassist。反编译头部
Could not load the following classes: javassist.ClassPool / javassist.CtClass属正常(CFR 缺该第三方依赖),不影响逻辑分析。
usedInChains 为空——自由构件,未直接出现在 51 条预设链中。它需与 fastjson 写文件 gadget、BytecodeConvert 及字节码 sink 手工组合成 docbase 利用链。
- 上游(外层,
nextTags=BytecodeDocbaseTag):fastjson 写文件 gadget——FastjsonCmonsio、FastjsonWriteFileJDK11。它们把 Docbase 输出的类字节码写入 docbase 路径并生成 fastjson 触发 payload。 - 下游(内层,
tags=BytecodeConvertTag):BytecodeConvert——把最内层字节码 sink 的产物改名为目标类名并按需混淆。 - 最内层字节码 sink:Exec 等(
tags含Bytecode/END),提供实际 RCE 载荷。
- 检测特征:
- 文件系统监控 Tomcat docbase / 临时工作目录(如
catalina.home下tomcat-docbase.*或/tmp/tomcat_docbase*)中WEB-INF/classes/**/*.class的运行时新增/写入——正常应用不会在运行期动态往 docbase 写 class。 - 恶意类的父类为
java.lang.Exception却带静态块/异常构造中执行命令,是本技术的强指纹:审计落盘 class 的常量池是否super=java/lang/Exception且方法体引用java/lang/ProcessBuilder、Runtime。 - 流量层:请求体出现双
@type且首个@type为java.lang.Exception(或java.lang.AutoCloseable、java.io.InputStream等被工具替换的期望类型),以及org.apache.commons.io.output.LockableFileWriter/WriterOutputStream/BOMInputStream等 commons-io 写文件类名。
- 文件系统监控 Tomcat docbase / 临时工作目录(如
- 加固建议:
- 升级 fastjson 至已修复版本并关闭 autoType(
safeMode),从根本上阻断双@type加载。 - 收敛 docbase / webapp 临时目录写权限,禁止应用运行用户对
WEB-INF/classes可写;隔离/只读挂载。 - Web 层禁用不必要的 commons-io 反序列化目标类;对 fastjson 输入做白名单类型校验。
- 运行期启用
WebappClassLoader加载审计,对从 docbase 动态加载的类告警。
- 升级 fastjson 至已修复版本并关闭 autoType(
- 反编译源码:
- 机器可读元数据:
../../index/gadgets.json - CVE-2022-25845(fastjson autoType)与 docbase 利用:
https://github.com/luelueking/CVE-2022-25845-In-Spring