0基础java参考

0基础java参考本文目录 一 前言 二 前置知识 2 1 Servlet 容器与 Engine Host Context 和 Wrapper 2 2 编写一个简单的 servlet 2 3 从代码层面看 servlet 初始化与装载流程 2 3 1 servlet 初始化流程分析 2 3 2 servlet 装载流程分析 2 4 Filter 容器与 FilterDefs

大家好,我是讯享网,很高兴认识大家。



本文目录:
- 一、前言
- 二、前置知识
- 2.1 Servlet容器与Engine、Host、Context和Wrapper
- 2.2 编写一个简单的servlet
- 2.3 从代码层面看servlet初始化与装载流程
- 2.3.1 servlet初始化流程分析
- 2.3.2 servlet装载流程分析
- 2.4 Filter容器与FilterDefs、FilterConfigs、FilterMaps、FilterChain
- 2.5 编写一个简单的Filter
- 2.6 从代码层面分析Filter运行的整体流程
- 2.7 Listener简单介绍
- 2.8 编写一个简单的Listener(ServletRequestListener)
- 2.9 从代码层面分析Listener运行的整体流程
- 2.10 简单的spring项目搭建
- 2.10.1 编写一个简单的Spring Controller
- 2.10.2 编写一个简单的Spring Interceptor
- 2.10.3 编写一个简单的Spring WebFlux的Demo(基于Netty)
- 2.11 Spring MVC介绍
- 2.11.1 Spring MVC九大组件
- 2.11.2 简单的源码分析
- 2.11.2.1 九大组件的初始化
- 2.11.2.2 url和Controller的关系的建立
- 2.11.2.3 Spring Interceptor引入与执行流程分析
- 2.12 Spring WebFlux介绍与代码调试分析
- 2.12.1 什么是Mono?
- 2.12.2 什么是Flux?
- 2.12.3 Spring WebFlux启动过程分析
- 2.12.4 Spring WebFlux请求处理过程分析
- 2.12.5 Spring WebFlux过滤器WebFilter运行过程分析
- 2.13 Tomcat Valve介绍与运行过程分析
- 2.13.1 Valve与Pipeline
- 2.13.2 编写一个简单Tomcat Valve的demo
- 2.13.3 Tomcat Valve打入内存马思路分析
- 2.14 Tomcat Upgrade介绍与打入内存马思路分析
- 2.14.1 编写一个简单的Tomcat Upgrade的demo
- 2.14.1.1 利用SpringBoot搭建
- 2.14.1.2 利用Tomcat搭建
- 2.14.2 Tomcat Upgrade内存马介绍与相关代码调试分析
- 2.15 Tomcat Executor内存马介绍与打入内存马思路分析
- 2.15.1
- 2.15.2 Tomcat Executor内存马介绍与代码调试分析
- 2.15.2.1 Endpoint五大组件
- 2.15.2.2 Endpoint分类
- 2.15.2.3 Executor相关代码分析
- 三、传统Web型内存马
- 3.1 Servlet内存马
- 3.1.1 简单的servlet内存马demo编写
- 3.1.2 servlet内存马demo代码分析
- 3.1.3 关于StandardContext、ApplicationContext、ServletContext的理解
- 3.2 Filter内存马
- 3.2.1 简单的filter内存马demo编写
- 3.2.2 servlet内存马demo代码分析
- 3.2.3 tomcat6下filter内存马的编写
- 3.3 Listener内存马
- 3.3.1 简单的Listener内存马demo编写
- 3.3.2 Listener内存马demo代码分析
- 四、Spring MVC框架型内存马
- 4.1 Spring Controller型内存马
- 4.1.1 简单的Spring Controller型内存马demo编写
- 4.1.2 Spring Controller型内存马demo代码分析
- 4.2 Spring Interceptor型内存马
- 4.3 Spring WebFlux内存马
- 4.3.1 简单的Spring WebFlux内存马demo编写
- 4.3.2 Spring WebFlux内存马demo代码分析
- 五、中间件型内存马
- 5.1 Tomcat Valve型内存马
- 5.2 Tomcat Upgrade内存马
- 5.3 Tomcat Executor内存马
- 六、致谢

一、前言

    之前写的零基础学的文章反响很不错,很多师傅在公众号后台和我的微信私聊我表示感谢,其实也没啥,大家都是零基础过来的。网上的文章多而杂,并且只有少部分文章是配图清楚、文字描述清晰的,很多时候新手学着学着可能就因为作者的某一个地方没有描述清楚而不知其所指,非常痛苦;亦或是文章面向对象不同,前置知识不扎实导致很多东西无法理解,这些痛点我都曾经历过。但是随着看过的代码逐渐增多,见识逐渐丰富,调试的次数越多,对各种问题的处理就会越得心应手。
    本文所讨论的内存马是安全中的一个不可或缺的板块,它内容丰富绮丽,研究起来让人着迷,沉沦其中流连忘返。我参考了师傅一年多以前发表在社区的这篇文章()中给出的分类方式,把整个零基础掌握内存马系列分成了以下几个部分:传统型、系列框架型、中间件型、其他内存马()、型内存马、实战内存马打入(/////...)和内存马。
    本文已开源至:

https://github.com/W01fh4cker/LearnJavaMemshellFromZero

    好了,让我们闲话少叙,就此开始。

二、前置知识

本篇文章除特殊说明外,使用的是+ ,后者下载地址为:

https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.85/bin/apache-tomcat-9.0.85-windows-x64.zip。

2.1 Servlet容器与Engine、Host、Context和Wrapper

这部分我找了好久,终于在一大堆高深/垃圾的文章中邂逅了一篇写的还算简明扼要易于理解的文章。

原文地址:https://www.maishuren.top/archives/tomcat-zhong-servlet-rong-qi-de-she-ji-yuan-li

这里组合引用其原文,简单概括,就是:

设计了四种容器,分别是、、和,其关系如下:

这一点可以从的配置文件中看出来。

此时,设想这样一个场景:我们此时要访问,那是如何实现请求定位到具体的的呢?为此设计了,其中保存了容器组件与访问路径的映射关系。

然后就开始四步走:

  1. 根据协议和端口号选定和。

我们知道的每个连接器都监听不同的端口,比如默认的连接器监听端口、默认的连接器监听端口。上面例子中的URL访问的是端口,因此这个请求会被连接器接收,而一个连接器是属于一个组件的,这样组件就确定了。我们还知道一个组件里除了有多个连接器,还有一个容器组件,具体来说就是一个容器,因此确定了也就意味着也确定了。

  1. 根据域名选定。

和确定后,组件通过中的域名去查找相应的容器,比如例子中的访问的域名是,因此会找到这个容器。

  1. 根据路径找到组件。

确定以后,根据的路径来匹配相应的应用的路径,比如例子中访问的是,因此找到了这个容器。

  1. 根据路径找到()。

确定后,再根据中配置的映射路径来找到具体的和,例如这里的的。

这里的翻译过来就是上下文,它包括运行的基本环境;这里的翻译过来就是包装器,它负责管理一个,包括其装载、初始化、执行和资源回收。

关于上图中的连接器的设计,可以继续参考该作者的博文:

https://www.maishuren.top/archives/yi-bu-bu-dai-ni-le-jie-tomcat-zhong-de-lian-jie-qi-shi-ru-he-she-ji-de

写到后面之后我又发现了一篇写的极佳的文章,贴在这儿供大家参考,讲的是关于架构的原理解析:

https://blog.nowcoder.net/n/0c4baa0b313f22df9ac2c09

2.2 编写一个简单的servlet

文件如下:

同步下依赖:

代码如下:

讯享网

然后配置项目运行所需的环境:

然后配置,直接点击:

然后添加模块:

运行之后,访问http://localhost:8080/testServlet/test:

2.3 从代码层面看servlet初始化与装载流程

主要参考文章:

https://longlone.top/安全/java/java安全/内存马/Tomcat-Servlet型/

我们这里不采用我们下载的来运行我们的项目,我们使用嵌入式也就是所谓的。关于动态调试,我是图省事,直接用,你当然也可以调试直接调试源码,环境搭建方法可以参考师傅的文章:

https://mp.weixin..com/s/DMVcqtiNG9gMdrBUyCRCgw

我们重开一个项目,文件代码如下:

讯享网

2.3.1 servlet初始化流程分析

我们在处下断点调试:

我们尝试按追踪它的上层调用位置,但是提示我们找不到,需要按两次:

然后就可以看到,上层调用位置位于:

接下来我们详细看下面这段代码:

首先通过获取的所有定义,并建立循环;然后创建一个对象,并设置的加载顺序、是否启用(即获取标签的值)、的名称等基本属性;接着遍历的初始化参数并设置到中,并处理安全角色引用、将角色和对应链接添加到中;如果定义中包含文件上传配置,则根据配置信息设置;设置是否支持异步操作;通过将配置好的添加到中,完成的初始化过程。

上面大的循环中嵌套的最后一个循环则负责处理的映射,将的与名称关联起来。

也就是说,的初始化主要经历以下六个步骤:

  • 创建对象;
  • 设置的的值;
  • 设置的名称;
  • 设置的;
  • 将配置好的添加到中;
  • 将和类做映射

2.3.2 servlet装载流程分析

我们在这里打下断点进行调试,重点关注:

可以看到,装载顺序为-->-->:

可以看到,上面红框中的代码都调用了,跟进该方法,代码如下:

可以看到,这段代码先是创建一个,然后遍历传入的数组,将每个的值作为键,将对应的对象存储在相应的列表中;如果这个值是负数,除非你请求访问它,否则就不会加载;如果是非负数,那么就按照这个的升序的顺序来加载。

2.4 Filter容器与FilterDefs、FilterConfigs、FilterMaps、FilterChain

开头先明确一点,就是容器是用于对请求和响应进行过滤和处理的,以下这张图是根据师傅文章中的图片重制的:

https://mp.weixin..com/s/eI-50-_W89eN8tsKi-5j4g

从上图可以看出,这个就是一个关卡,客户端的请求在经过之后才会到,那么如果我们动态创建一个并且将其放在最前面,我们的就会最先执行,当我们在中添加恶意代码,就可以实现命令执行,形成内存马。

这些名词其实很容易理解,首先,需要定义过滤器,存放这些的数组被称为,每个定义了一个具体的过滤器,包括描述信息、名称、过滤器实例以及等,这一点可以从的代码中看出来;然后是,它只是过滤器的抽象定义,而则是这些过滤器的具体配置实例,我们可以为每个过滤器定义具体的配置参数,以满足系统的需求;紧接着是,它是用于将映射到具体的请求路径或其他标识上,这样系统在处理请求时就能够根据请求的路径或标识找到对应的,从而确定要执行的过滤器链;而是由多个组成的链式结构,它定义了过滤器的执行顺序,在处理请求时系统会按照中的顺序依次执行每个过滤器,对请求进行过滤和处理。

2.5 编写一个简单的Filter

我们继续用我们之前在中搭建的环境,添加:

跑起来之后,控制台输出,当我们访问路由的时候,控制台继续输出,当我们结束的时候,会触发方法,从而输出:

2.6 从代码层面分析Filter运行的整体流程

我们在上面的中的函数这里下断点进行调试:

跟进:

继续跟进变量,找到定义处的代码:

查看该方法():

我们在该方法和下面定义那行下断点进行调试,可以看到,这段代码先是判断是否为空,如果是就表示没有有效的,无法创建过滤器链;然后根据传入的的类型来分类处理,如果是类型,并且启用了安全性,那么就创建一个新的,如果没启用,那么就尝试从请求中获取现有的过滤器链,如果不存在那么就创建一个新的;接着是设置过滤器链的和异步支持属性,这个没啥说的;关键点在于后面从中获取父级上下文(),然后获取该上下文中定义的过滤器映射数组();最后遍历过滤器映射数组,根据请求的和请求路径匹配过滤器,并将匹配的过滤器添加到过滤器链中,最终返回创建或更新后的过滤器链。

从上面的两张图我们也可以清晰地看到、、的结构。

跟进刚才的方法,位于:

可以看到都是调用了方法,在这个方法中会依次拿到和:

0基础java参考

好了,大致过程到这里就结束了,但是我们的目的是打入内存马,也就是要动态地创建一个,回顾之前的调试过程,我们发现在那个函数里面有两个关键点:

也就是这里我用箭头指出来的和。

二者的实现代码粘贴如下:

也就是说我们只需要查找到现有的上下文,然后往里面插入我们自定义的恶意过滤器映射和过滤器配置,就可以实现动态添加过滤器了。

那也就是说,我们现在的问题就转化为如何添加和。我们搜索关键词,即可看到在中有两个相关的方法:

注释里面也说的很清楚,是在一组映射末尾添加新的我们自定义的新映射;而则会自动把我们创建的丢到第一位去,无需再手动排序,这正是我们需要的呀!

可以看到,上面的函数中第一步是先执行这个函数,点击去看看:

发现我们需要保证它在根据找的时候,得能找到,也就是说,我们还得自定义并把它加入到,不过这个也很简单,也有对应的方法,也就是:

搞定,继续去看如何添加。经过搜索发现,不存在类似上面的这种方法:

但是有和这两个方法:

那也就是说,我们只能通过反射的方法去获取相关属性并添加进去。

2.7 Listener简单介绍

由上图可知,是最先被加载的,所以根据前面我们学到的思路,我动态注册一个恶意的,就又可以形成一种内存马了。

在中,常见的有以下几种:

  • ,用来监听整个应用程序的启动和关闭事件,需要实现和这两个方法;
  • ,用来监听请求的创建和销毁事件,需要实现和这两个方法;
  • ,用来监听会话的创建和销毁事件,需要实现和这两个方法;
  • ,监听会话属性的添加、删除和替换事件,需要实现、和这三个方法。

很明显,是最适合做内存马的,因为它只要访问服务就能触发操作。

2.8 编写一个简单的Listener(ServletRequestListener)

我们继续用我们之前在中搭建的环境,替换掉之前的,重新写一个:

运行结果:

2.9 从代码层面分析Listener运行的整体流程

我们在如图所示的两个地方下断点调试:

往下翻可以看到方法的调用:

代码写的通俗易懂,主要有两个事情要干,一是通过找到这些的名字;二是实例化这些:

接着就是分类摆放,我们需要的被放在了里面:

分类摆放完了之后,干这样一件事情:

好理解,意思就是将数组转换为列表;也好理解,意思就是将括号里面的内容添加到之前实例化的监听器列表 中。关于括号里边的这个方法,我们点进去看,代码如下:

也很简单明了,就是把转换成一个包含任意类型对象的数组,也就是一个可能包含各种类型的应用程序事件监听器的数组。

那这总结起来就一句话,就是有两个来源,一是根据文件或者注解实例化得到的;二是中的。前面的我们肯定没法控制,因为这是给开发者用的,不是给黑客用的哈哈哈。那就找找看,有没有类似之前我们用到的这种函数呢?当然是有的,往上找:

方法名字叫做,在里面,代码如下,完美符合我们的需求,真是太哇塞了:

2.10 简单的spring项目搭建

新建个项目,设置为:

等待依赖解析完成:

这里给我们准备了一个示例,我们可以直接跑起来:

2.10.1 编写一个简单的Spring Controller

非常地简单:

2.10.2 编写一个简单的Spring Interceptor

就是之前写的,运行后访问:

2.10.3 编写一个简单的Spring WebFlux的Demo(基于Netty)

我们先聊聊怎么自己写一个框架的。

这里我们新建一个项目,取名:

这里选择:

接着新建两个文件,这里为了方便,我把这两个文件放到文件夹下。

我们可以新建文件夹,然后新建,通过来控制服务的端口:

接着我们运行:

这里我从上找了一个项目,也可以很好地帮助我们理解这个框架是如何使用的,它采用的是+:

https://github.com/Java-Techie-jt/springboot-webflux-demo

随便访问个路由。例如:

2.11 Spring MVC介绍

如果想要深入理解框架型内存马,那么对的基础了解是非常必要的,本节就从源码层面和大家简单聊聊这个框架。

首先引用《》上的一张图(这里我重制了一下)来了解的核心组件和大致处理流程(不过我在第五版书上貌似没有找到这张图,有找到的小伙伴可以公众号后台私信我):

可以看到,这里有一堆名词,我们一一来看:

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

还有一个概念需要了解,就是容器,因为这个名词会在本文后面的内容中提及。

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

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

2.11.1 Spring MVC九大组件

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

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

2.11.2 简单的源码分析

2.11.2.1 九大组件的初始化

首先是找到,可以看到里面有很多组件的定义和初始化函数以及一些其他的函数:

但是没有函数,我们翻看其父类的父类的时候发现有函数:

代码如下:

先是从的配置中获取初始化参数并创建一个对象,然后设置属性;关键在最后一步,调用了这个方法。

我们点进去之后发现该函数并没有写任何内容,说明应该是子类继承的时候了该方法:

果不其然,我们在中成功找到了该方法:

代码如下:

这段代码的和计时部分就不说了,我们捡关键的说。它先是调用方法,初始化容器,在初始化的过程中,会调用到这个方法,一般来说这个方法是在容器刷新完成后被调用的回调方法,它执行一些在应用程序启动后立即需要完成的任务:

跟入该方法,可以看到其中默认为空:

说明在它的子类中应该会有,果然我们定位到了方法:

这一下就明了了起来,这不是我们之前提到的九大组件嘛,到这一步就完成了的九大组件的初始化。

2.11.2.2 url和Controller的关系的建立

你可能会有这样的一个疑惑:我们是用注解在方法上的,那是怎么根据这个注解就把对应的请求和这个方法关联起来的?

从上面的九大组件的初始化中可以看到,有个方法就叫做,我们点进去详细看看:

这段代码和自带的注释写的也比较通俗易懂,分为两部分,第一部分是去(包括)里面找所有实现了接口的类,如果找到了至少一个符合条件的,那就把它的值转化为列表,并按照Java的默认排序机制对它们进行排序,最后将排序后的列表赋值给 ;那如果没有找到,就依然保持为;如果不需要检测所有处理程序映射,那就尝试从中获取名称为 的,如果成功获取到了则将其作为单一元素的列表赋值给 ,如果获取失败了,那也没关系,因为人家注释里面讲的很明白,会添加一个默认的,这也就是我们要讲的第二部分的代码。

第二部分说的是,如果之前一套操作下来,还是为,那么就调用 方法去获取默认的,并将其赋给 。

这么一看的话,这个方法还是挺关键的,我们点进去看看:

这段代码挺有意思,先是加载资源文件,并将其内容以属性键值对的形式存储在中;接下来从获取一个名称,然后用这个名称在中查找相应的值,如果找到了,就将这个值按逗号分隔成类名数组,接着遍历这个类名数组,对于每个类名都执行以下两个操作:①尝试通过方法加载该类 ②使用方法创建该类的实例;最后将创建的策略对象添加到列表中并返回。

那就很好奇了,这段代码中的里面有啥?追踪:

原来是一个名叫的文件,我们可以在左侧的依赖列表里面很快地翻到它,因为它应该是和在一块儿的:

从文件内容中,我们可以很快地锁定关键信息:

也就是说,会有三个值,分别是、和,我们一般用的是第二个,我们点进看一下:

它的父类的父类实现了这个接口,这个接口用于在初始化完成后执行一些特定的自定义初始化逻辑。

点进该接口,只有一个方法,关于该方法的用途可以参考:

那我们就看看它是具体咋实现的的吧:

重写的也很简单,调用这个方法,继续跟踪该方法:

注释里面写的很清楚:扫描中的,然后检测并注册。

我们在这里打下断点进行调试,到图中这一步之后:

我们来看这个方法的具体逻辑:

这里我们自然很好奇,这个是判断啥的,我们点进去看看:

可以看到,这里并没有给出实现,说明子类中应该会给出,于是直接找到了:

很明显,是用来检测给定的类是否带有注解或者注解。

解决了这个,继续往后看,后面是调用了这个方法,我们点进去看看:

我们分开来看,首先是这行代码,它是综合起来写的,意思是说,先判断是否是字符串类型,如果是,则通过获取它的类型;否则,直接获取的类型。:

然后是这部分:

先是获取处理器的用户类,用户类是没有经过代理包装的类,这样就可以确保获取到的是实际处理请求的类;然后是这个方法,这个方法有两个参数,第一个参数就是用户类,第二个参数是一个回调函数。关键就在于理解这个回调函数的作用。对于每个方法,它会尝试调用来获取方法的映射信息。

我们点进这个方法,发现它是一个抽象方法:

那就去看看他的子类中有没有对应的实现,直接定位到:

我们在下图所示位置打断点调试:

分开来看,首先是第一行:

解析类的方法中的注解,生成一个对应的对象。我们可以进入方法:

可以看到这个里面保存了访问该方法的是,也就是我们在所想要看到的当时,调用方法。

继续一步步往下走,可以看到走到了的最后:

直接看表达式里面的内容:

意思是,先用方法根据和选择出一个可调用的方法,这样是为了处理可能存在的代理和的情况,确保获取到的是可直接调用的原始方法;然后把、和注册进。

到这里,和之间的关系是如何建立的问题就解决了。

2.11.2.3 Spring Interceptor引入与执行流程分析

我们回顾之前聊到的的思路和下面的节中所展示的内存马,可以考虑到这样一个问题:

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

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

那就不禁要问了:这个和我们之前所说的的区别是啥?

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

主要有以下六个方面:

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

我们在节中给出的的函数这里下断点,然后访问进入调试:

一步步步入调试之后,发现进入方法:

我们在方法的第一行下断点,重新访问页面调试:

看到了调用了这个函数,它的注释写的简单易懂:确定处理当前请求的,我们看看:

通过遍历当前数组中的对象,来判断哪个来处理当前的对象:

继续步入这个函数里面所用到的方法,也就是:

代码简单易懂,先是通过来获取,如果获取不到,那就调用来获取默认的,如果还是获取不到,就直接返回;然后检查是不是一个字符串,如果是,说明可能是一个的名字,这样的话就通过来获取对应名字的对象,这样就确保 最终会是一个合法的处理器对象;接着检查是否已经有缓存的请求路径,如果没有缓存就调用 方法来初始化请求路径的查找;最后通过 方法创建一个处理器执行链。

这么看下来,这个方法很重要,我们步入看看:

遍历,判断拦截器是否是类型,如果是那就看是否匹配当前请求,如果匹配则将其实际的拦截器添加到执行链中,如果不是这个类型的那就直接将拦截器添加到执行链中。

再回到之前的方法中来,看看它的后半段:

主要都是处理跨域资源共享()的逻辑,只需要知道在涉及的时候把、和配置通过调用封装后返回就行了。

一步步执行回到一开始的中,这里就是调用方法来遍历所有拦截器进行预处理,后面的代码就基本不需要了解了:

2.12 Spring WebFlux介绍与代码调试分析

是中引入的新的响应式框架。传统的在处理请求时是阻塞的,即每个请求都会占用一个线程,如果有大量请求同时到达,就需要大量线程来处理,可能导致资源耗尽。为了解决这个问题,引入了非阻塞的响应式编程模型,通过使用异步非阻塞的方式处理请求,能够更高效地支持大量并发请求,提高系统的吞吐量;并且它能够轻松处理长连接和,适用于需要保持连接的应用场景,如实时通讯和推送服务;在微服务架构中,服务之间的通信往往需要高效处理,可以更好地适应这种异步通信的需求。

关于和的相关知识,可以参考知乎上的这篇文章,讲的通俗易懂,很透彻:

https://zhuanlan.zhihu.com/p/

框架开发的接口返回类型必须是或者是。因此我们第一个需要了解的就是什么是以及什么是。

2.12.1 什么是Mono?

用来表示包含或个元素的异步序列,它是一种异步的、可组合的、能够处理异步数据流的类型。比方说当我们发起一个异步的数据库查询、网络调用或其他异步操作时,该操作的结果可以包装在中,这样就使得我们可以以响应式的方式处理异步结果,而不是去阻塞线程等待结果返回,就像我们在节中的那张图中所看到的那样。

下面我们来看看常用的:

API 说明 代码示例 创建一个包含指定数据的 。 创建一个空的 。 创建一个包含错误的 。 从 Callable 创建 ,表示可能抛出异常的异步操作。 从 Runnable 创建 ,表示没有返回值的异步操作。 在指定的延迟后创建一个空的 。 延迟创建 ,直到订阅时才调用供应商方法。 将一组 合并为一个 ,当其中一个出错时,继续等待其他的完成。 对 中的元素进行映射。 对 中的元素进行异步映射。 过滤 中的元素。 如果 为空,则使用默认值。 在发生错误时提供一个备用的 。 在成功时执行操作,但不更改元素。 在发生错误时执行操作。 无论成功还是出错都执行操作。

2.12.2 什么是Flux?

表示的是到个元素的异步序列,可以以异步的方式按照时间的推移逐个或一批一批地元素。也就是说,允许在处理元素的过程中,不必等待所有元素都准备好,而是可以在它们准备好的时候立即推送给订阅者。这种异步的推送方式使得程序可以更灵活地处理元素的生成和消费,而不会阻塞执行线程。

下面是常用的:

API 说明 代码示例 创建包含指定元素的 从创建
从数组创建
创建一个空的 创建一个包含错误的 创建包含指定范围的整数序列的 创建包含定期间隔的元素的 合并多个Flux,按照时间顺序交织元素

连接多个,按照顺序发布元素

将多个的元素进行配对,生成

过滤满足条件的元素
转换每个元素的值
将每个元素映射到一个,并将结果平铺

2.12.3 Spring WebFlux启动过程分析

本来是想先用文字聊一堆关于和之间的区别的,但是这个已经被网上现有的不多的关于的文章讲烂了,大家随便搜都可以搜到,皮毛性的东西纯属浪费时间,于是我们直接看代码,去深挖的调用过程,从中我们自然可以发现这两者在调用过程中的类似和不同的地方。

我们直接在方法这里下断点,然后直接:

一步步地之后,我们可以看到调用了这个方法(前面的那些方法并不重要,直接略过就行):

这个方法光听名字,就感觉很重要,因为字面意思就是创建,这正是我们感兴趣的内容,我们进去看看:

可以看到,是根据不同的去选择创建不同的,比如我们这里的就是,也就是响应式的。

我们这里的方法:

发现里面有两个静态方法、一个方法和一个默认实现 ,这个默认实现通过加载 的所有候选实现,创建相应的上下文;如果没有找到合适的实现,则默认返回一个 实例。

我们继续走下去,可以看到我们对应的是:

继续往下走,我们会回到一开始这里,可以看到接下来会调用、和方法,这个过程就是一系列的初始化、监听的注册等操作:

我们这里的方法:

接着这里的方法:

进来之后,接着这里的方法:

可以看到,这里调用了一个,也就是父类的方法:

我们继续查看,发现这里调用了方法:

我们这里的,发现它调用了关键的:

继续可以看到,由于我们使用的是而不是,因此这里最终会调用类中的方法:

而上图中的类也是一个重要的封装类,里面有两个成员变量,一个是底层服务器的抽象,另一个是上层方法处理者的抽象:

那这个具体是怎么启动的呢?我们继续走到这个方法这里来,如果这里我们直接无脑,程序最终会回到方法,说明,启动的地方肯定就在这个方法里面:

我们进去看看:

接着去看看这里调用的方法,发现调用了方法,并且设置了自启动:

我们直接这个方法,一步步地过后,会发现调用了方法,看来我们在逐渐逼近真相:

我们继续这个方法,发现调用了这个方法:

直接进去看看,发现由于为[],所以没有调用方法,直接就是调用:

继续这个方法看看:

怎么会啥也没有呢?奇了怪了,到底是哪里出了问题了呢?我在这一步愣住了,决定把之前打的断点取消,在如下俩图所示的位置打上断点重新调试,因为这两个方法是关键方法:

调试了几遍之后发现是我疏忽了,这里的里面其实有三个,每调用一次方法就会删掉一个:

可以看到,我们刚才调用的是第一个的,所以当然没有启动相关的方法了:

我们一步步,当为时,我们再这个方法里面的:

即可看到:

我们继续这个方法:

仔细看看上面红框中的代码,先是初始化,这个方法其实根据的值的不同来决定何时初始化,如果值为,那么就等第一次请求到来时才真正初始化;如果为,那么就在 的 方法中调用 直接初始化:

我们继续步入这里的方法,发现其位置为

到这里才算真正明了,真正的启动的关键方法是:

从下面的中也可以看到,绑定的是:

2.12.4 Spring WebFlux请求处理过程分析

当一个请求过来的时候,是如何进行处理的呢?

这里我们在这里打上断点,然后进行调试,访问触发:

一步步地后来到:

之后可以看到是:

解释上面代码中的部分,首先检查是否为,如果是,那就调用方法返回一个表示未找到处理程序的;接着通过方法检查是否为预检请求,如果是,那就调用方法处理预检请求,如果不是预检请求且不为,通过一系列的操作,获取到请求的,然后调用方法执行处理程序,再调用方法处理执行结果,最终返回一个表示处理完成的。

左下角的这里,我们往下翻,可以看到在此之前是调用了一个:

我们把之前的断点去掉,然后在该函数这里打上断点:

发现调用了,我们再回去看,发现调用位置在:

点击去:

这里最终创建的是对象,需要注意的是在创建该对象时将中保存的列表作为参数传入,这样对象就有了解析参数的能力。

回到这个函数,看它的里面的匿名函数,发现其调用了,我们点进去看看:

发现只是在接口中定义了下:

于是去翻之前的:

首先调用方法来判断传入的是否符合路由要求,如果匹配到了处理方法,那就将保存的实现返回,否则就返回空的。

点进去这个方法,发现还是个接口,结合之前的和的命名规则,合理猜测方法的实现应该是在里面。果然是有的,我们取消之前下的所有断点,在函数这里重新打上断点后调试:

可以看到这里已经拿到了,那就还差解析里面的这个方法了:

我们继续,发现直接跳到了这里,我当时就挺纳闷儿,这里的和怎么就已知了:

这俩变量已知说明在执行之前肯定是已经被赋值了,我继续往后,从下图中可以看到,此时二者之间多了个,不难猜测,应该是调用了方法,因为还有一个,这个的话应该就是了:

于是我们再在方法这打上断点,此时我们还没有访问,就已经触发调试了,这是因为我们在里面写的代码中有方法、路由还有方法,因此会调用到,并把和分别复制给和:

到这里,我们基本就了解了路由匹配这么个事情。接下来我们要考虑的事情就是如何处理请求,这个就比较简单了,为什么这么说呢?因为在我们节中的分析中已经基本涉及到了。我们还是在打下断点调试:

可以看到,这里的里面有四个:

并不是所有的都会触发方法,只有当支持我们给定的的才可以调用:

然后我们这里的方法,发现是在:

而这里的也就是我们编写的方法:

到这里,关于处理请求的部分也就完结了。

2.12.5 Spring WebFlux过滤器WebFilter运行过程分析

对于而言,由于没有拦截器和监听器这个概念,要想实现权限验证和访问控制的话,就得使用,关于这一部分知识可以参考Spring的官方文档:

https://docs.spring.io/spring-security/reference/reactive/configuration/webflux.html

而在中,存在两种类型的过滤器:一个是,实现自接口。通过实现这个接口,可以定义全局的过滤器,它可以在请求被路由到之前或者之后执行一些逻辑;另一个就是,它是一种函数式编程的过滤器类型,实现自接口,与相比它更加注重函数式编程的风格,可以用于处理基于路由的过滤逻辑。

这里我们以为例,看看它的运行过程。新建一个,代码如下:

效果如下:

我们直接在函数这里下断点,进行调试:

注意到中调用了函数,于是看看:

可以看到是调用了函数。我们仔细看看这个类:

可以看到是有三个名为的函数,其中第一个是公共构造函数,第二个是私有构造函数(用来创建的中间节点),第三个是已经过时的构造函数。而在该类的注释中,有这样一句话:

Each instance of this class represents one link in the chain. The public constructor DefaultWebFilterChain(WebHandler, List) initializes the full chain and represents its first link.

也就是说,通过调用 类的公共构造函数,我们初始化了一个完整的过滤器链,其中的每个实例都代表链中的一个,而不是一个,这就意味着我们无法通过修改下图中的来实现新增:

但是这个类里面有个方法用来初始化过滤器链,这个方法里面调用的是这个私有构造方法:

那我们就看看这个公共构造方法是在哪里调用的:

光标移至该方法,按两下:

调用的地方位于:

那思路就来了,我们只需要构造一个对象,,然后把它通过反射写入到类对象的属性中就可以了。

那现在就剩下传入和这两个参数了,这个参数很好搞,就在里面:

然后这个的话,我们可以先获取到它本来的,然后把我们自己写的恶意放进去,放到第一位,就可以了。

那现在就是从内存中找到的位置,然后一步步反射就行。这里直接使用工具,克隆下来该项目,放到中:

image-20240126134339217

然后把生成的这个放到我们的项目的Project 中的中:

然后我们把我们的的代码修改成下面的:

这里我们设置的关键字是,然后直接运行:

也就是说,位置是在:

2.13 Tomcat Valve介绍与运行过程分析

2.13.1 Valve与Pipeline

在众多文章里面,下面的这篇我觉得是讲的最通俗易懂的,这里推荐给大家:

https://www.cnblogs.com/coldridgeValley/p/5816414.html

这里我组合引用原文,做了适当的修改,概括一下:

原来,当请求到达容器的时候,并非是直接调用对应的去处理相关的请求,而是调用了自己的一个组件去处理,这个组件就叫做组件,跟相关的还有个也是容器内部的组件,叫做组件。

的作用就如其中文意思一样——管道,可以把不同容器想象成一个独立的个体,那么就可以理解为不同容器之间的管道,道路,桥梁。那这个组件是什么东西呢?也可以直接按照字面意思去理解为阀门。我们知道,在生活中可以看到每个管道上面都有阀门,和关系也是一样的。代表管道上的阀门,可以控制管道的流向,当然每个管道上可以有多个阀门。如果把比作公路的话,那么可以理解为公路上的收费站,车代表中的内容,那么每个收费站都会对其中的内容做一些处理(收费,查证件等)。

在中,种容器都有自己的组件,每个组件上至少会设定一个,这个我们称之为,也就是基础阀。基础阀的作用是连接当前容器的下一个容器(通常是自己的自容器),可以说基础阀是两个容器之间的桥梁。

定义对应的接口,标准实现了。定义对应的接口,抽象实现类,个容器对应基础阀门分别是,,,。在实际运行中,和运行机制如下图:

这张图是新加坡的在上的演讲《Extending Valves in Tomcat》中的中的图片,链接如下:

https://people.apache.org/~huxing/acasia2022/Dennis-Jacob-Extending-Valves-in-Tomcat.pdf

这篇演讲的录屏在上面可以找到:

https://www.youtube.com/watch?v=Jmw-d0kyZ_4

2.13.2 编写一个简单Tomcat Valve的demo

由于在环境下使用Valve还要配置web.xml,我嫌麻烦,于是直接使用来搭建。记得这里勾选的是:

然后创建目录并在目录下创建两个文件,:

还有:

运行效果如下:

2.13.3 Tomcat Valve打入内存马思路分析

我们通常情况下用的都是,点进这个,可以看到是实现了接口:

点进可以看到该接口代码如下,这里我加上了注释:

接下来就是调试看看这个的运行流程了,我们在函数这里下断点调试:

我们看向左下角,看看之前调用到的方法:

在中,代码为:

在中,代码为:

之后的诸如和多线程的部分就不需要我们关注了。既然我们的目的是打入内存马,那根据我们掌握的内存马的思路来看,我们需要通过某种方式添加我们自己的恶意。

我们去掉之前打的断点,在这里打上断电并重新调试:

然后:

鼠标左键单击这里的即可进入到所调用的函数实现的位置:

再进入接口,可以看到是有个方法:

这不正是我们需要的吗?我们去看看它是在哪儿实现的,直接在函数处找继承该接口的类,可可以看到是在中:

image-20240130155445230

但是问题就来了,我们无法直接获取到这个,而我们能直接获取到的是,那就去看看中有没有获取的方法。

一眼就能看到我们的老熟人——方法:

那这样以来我们的思路就可以补充完整了,先反射获取,然后编写一个恶意,最后通过添加就可以了。当然,我们也可以反射获取,然后再,这样也是可以的。

2.14 Tomcat Upgrade介绍与打入内存马思路分析

2.14.1 编写一个简单的Tomcat Upgrade的demo

2.14.1.1 利用SpringBoot搭建

我这里在之前的项目的基础上做了简单的修改,删除之前目录下的,新建一个:

然后修改如下:

运行之后命令行执行命令,效果如下:

2.14.1.2 利用Tomcat搭建

当然也是可以利用来搭建的,只需要即可,因为里面含有定义的逻辑:

效果如下:

2.14.2 Tomcat Upgrade内存马介绍与相关代码调试分析

这部分主要参考了师傅的文章(原文地址为,但是由于图片链接挂掉导致图片无法显示,我们可以访问如下地址查看:)以及师傅的文章()。

和之前所提到的型内存马有点类似,在渗透过程中,尽管我们打入了内存马,但是因为原有的Filter包含鉴权或者其他功能,可能会导致我们的内存马无法访问,或者因为反向代理而导致我们无法找到对应的路径,这就需要我们在到这一步之前就得打入内存马。

这里,我引用码哥字节文章()里面的一张架构图:

可以清楚地看到,在此之前还有和两个模块,本节内容主要讨论后者,在下节中我们会讨论前者。

这一部分需要更加完备的的相关知识,不再满足于之前的四个容器,关于这些基础知识的学习,强烈建议看码哥字节的文章,写的确实特别的好:

https://blog.nowcoder.net/n/0c4baa0b313f22df9ac2c09

其实在之前学习的过程中,当时我是一步步跟完了所有的代码的,我当时也提了一嘴。我们还是以当时的项目为例来看。

我们还是在的这行打上断点:

从上面我红色箭头所指出的地方就可以看到调用到了函数,具体调用位置位于,我们跟过去看看:

可以看到,如果当前的状态是的时候,才会调用对应的去处理(第二张图的调用的位置可以通过第一张图左下角的那个的后一个点进去看到):

我们继续这里的方法看看:

继续,可以看到这里在检查中的头中是否为,这一点可以通过这个方法看到:

之后干两件事情:一是调用方法根据从拿到;二是调用对象的方法:

到了这里,我们似乎可以建立起一个猜想,和之前介绍的内存马类似,我们只要构造一个恶意的,然后把它插入到。

由于是一个,那么向里面添加的话用到的肯定是方法,直接搜:

我们在这行打上断点,然后调试,发现在我们没有执行这条命令之前,断点就到了,也就是说,这个事情是发生在启动的时候的。

那这样一来,思路就更加具体了一点:反射找到,把恶意插入进去即可构成内存马,思路和之前是一模一样的。

那现在只需要解决最后一个问题——如何找到的位置。我们打开之前用搭建的的,在如下位置打下断点,然后执行命令进入断点调试::

一步即可在下方看到属性:

然后在里面的的里面发现了:

接下来就是一步步地反射了。

2.15 Tomcat Executor内存马介绍与打入内存马思路分析

2.15.1

新建一个项目,配置好运行环境和目录,然后新建以下两个文件,第一个是TestExecutor.java:

第二个是:

然后访问浏览器对应下的路由:

2.15.2 Tomcat Executor内存马介绍与代码调试分析

在节中,我们聊到过可以在模块中打入内存马,本节就来分析具体流程。本节主要参考文章为以下四篇:

https://p4d0rn.gitbook.io/java/memory-shell/tomcat-middlewares/executor

https://cjlusec.ldxk.edu.cn/2023/02/15/Executor/

https://xz.aliyun.com/t/11593

https://xz.aliyun.com/t/11613

在我之前提到过的讲tomcat架构的基础文章(),有详细地讲述组件中的部件,如果之前没有看完整地可以再去看下。里面这张图画的很好,我这里作引用:

2.15.2.1 Endpoint五大组件

如下表所示:

组件 描述 连接控制器,控制最大连接数 接收新连接并返回给的对象 监控状态,类似于中的 封装的任务类,处理连接的具体操作 自定义线程池,用于执行任务类
2.15.2.2 Endpoint分类

接口的具体实现类为,具体的实现类有、、:

Endpoint 简要解释 Tomcat 源码位置 使用模式解决异步问题,提高性能 使用代码实现异步 使用实现非阻塞

上面所提到的,指的是如下依赖:

默认启动是以来启动的,它是中默认的负责使用方式进行网络通信功能的模块,它负责监听处理请求连接,并将解析出的字节流传递给进行后续的处理。

2.15.2.3 Executor相关代码分析

点开即可看到有一个方法:


追踪即可看到这个接口在这个抽象类中有相关实现:

在中搜索,往下翻即可看到有和这两个函数:

查看函数的调用位置,发现就在该文件中有一个关键调用:

跟过去:

从下面这篇文章中我们可以知道在运行过程中的作用:

https://blog.51cto.com/u_/

那此时我们就有一个想法,如果我能控制,我把原来的通过变成我恶意创建的,然后再通过这后面的()一执行就可以加载我们的恶意逻辑了。

但是现在有一个很头疼的问题,那就是标准的需要经过的封装后才可获得,这里还在阶段,其后面封装的和无法直接获取。

那怎么办呢?结合之前学过的知识,我们很容易想到在之前我们第一次接触的时候,师傅写的这篇文章:

http://gv7.me/articles/2020/semi-automatic-mining-request-implements-multiple-middleware-echo/

那就试试看呗,我们导入包到项目之后修改代码如下:

接着访问路由,然后在控制台输出中搜索:

直接搜索到了这条链:

我们来验证一下,在的这里下断点,不断,就可以找到这里的的位置:

点开这里的,可以看到它是一个字节数组,右键找到即可变成字符串:

再点击上面我指出来的即可清楚看到具体内容:

这就意味着我们可以把命令作为的一部分传入,再把结果作为的一部分传出即可。

三、传统Web型内存马

3.1 Servlet内存马

3.1.1 简单的servlet内存马demo编写

根据我们在上面的节中的分析可以得出以下结论:

如果我们想要写一个内存马,需要经过以下步骤:

  • 找到
  • 继承并编写一个恶意
  • 创建对象
  • 设置的的值
  • 设置的
  • 设置对应的
  • 将添加到的中
  • 将路径和类做映射

由以上结论我们可以写出如下内存马:

访问,执行任意命令:

3.1.2 servlet内存马demo代码分析

先完成第一个任务:找到,代码如下:

首先得知道是什么。在中,这个类属于包,用于表示类的成员变量(字段)。类提供了访问和操作类的字段的方法,包括获取字段的名称、类型、修饰符等信息,以及在实例上获取或设置字段的值。这样我们就可以实现在运行时动态获取类的信息,绕过一些访问修饰符的限制,访问和操作类的私有成员。

所以上述代码的含义就是:从当前中获取对象,然后使用反射机制获取类中名为的私有字段,并赋值给类型的变量,把这个变量的属性设置为可访问,这样我们后续可以通过反射获取它的值。接着通过反射获取对象的私有字段的值,并将其强制类型转换为。接下来继续使用反射机制获取类中名为的私有字段,并赋值给类型的变量,同样将其设置为可访问;最后通过反射获取对象的私有字段的值,并将其强制类型转换为,到这里,我们就成功找到了。

接着完成第二个任务:继承并编写一个恶意,代码如下:

可以看到,除了代码之外,我们还编写了、、和方法,可是它们并没有用到,要么返回,要么直接留空不写,那我们为什么还要写这四个方法呢?

那我们就来试试看注释掉之后会怎么样:

报错:。

我们直接跟进类,可以看到其是一个接口:

原来,在中,接口中的方法默认都是抽象的,除非在及以后的版本中使用了默认方法。并且,如果一个类实现了某个接口,那么它必须提供该接口中所有抽象方法的具体实现,这就是我们必须要写出上述四个方法的原因。

这里我使用来实现可以执行带有空格的命令,例如我在中举例的;对于系统,那就是;接着就是关于输入或者返回结果中带有中文的情况的处理,我们需要设置编码为即可,当然这个就需要具体情况具体对待了。

接着我们需要完成后续的六个任务:创建对象、设置的的值、设置的、设置对应的、将添加到的中、将路径和类做映射,代码如下:

前面几步在之前已经讲过了,这个是为了让我们自定义的成为应用程序的一部分;然后也可以写成如下形式:

3.1.3 关于StandardContext、ApplicationContext、ServletContext的理解

请参考师傅和师傅的文章,他们写的非常详细,这里直接贴出链接:

https://yzddmr6.com/posts/tomcat-context/

https://mp.weixin..com/s/BrbkTiCuX4lNEir3y24lew

引用师傅的一句话总结:

是规范;是的实现;接口是容器结构中的一种容器,代表的是一个应用程序,是独有的,其标准实现是,是容器的重要组成部分。

关于的获取方法,除了本文中提到的将我们的转为从而获取这个方法,还有以下两种方法:

  1. 从线程中获取StandardContext,参考Litch1师傅的文章:https://mp.weixin..com/s/O9Qy0xMen8ufc3ecC33z6A
  2. 从MBean中获取,参考54simo师傅的文章:https://scriptboy.cn/p/tomcat-filter-inject/,不过这位师傅的博客已经关闭了,我们可以看存档:https://web.archive.org/web/514/https://scriptboy.cn/p/tomcat-filter-inject/
  3. 从spring运行时的上下文中获取,参考 LandGrey@奇安信观星实验室 师傅的文章:https://www.anquanke.com/post/id/

这两种方法,如果后面有时间的话我会补充完整。

3.2 Filter内存马

3.2.1 简单的filter内存马demo编写

根据我们在上面的节中所讨论的内容,我们可以得出以下结论:

如果我们想要写一个内存马,需要经过以下步骤:

参考:https://longlone.top/安全/java/java安全/内存马/Tomcat-Filter型/

  • 获取;
  • 继承并编写一个恶意;
  • 实例化一个类,包装并存放到中;
  • 实例化一个类,将我们的和相对应,使用存放到中;
  • 通过反射获取,实例化一个()类,传入与,存放到中。

参考:https://tyaoo.github.io/2021/12/06/Tomcat内存马/

需要注意的是,一定要先修改,再修改,不然会抛出找不到的异常。

由以上结论我们可以写出如下内存马:

效果如下:

同样的,这里我也适配了中文编码,和一些提示性语句的输出。

3.2.2 servlet内存马demo代码分析

我们分开来分析,首先看这段代码:

先是获取当前的上下文并拿到其私有字段,然后设置可访问,这样就可以通过反射这个字段的值,这个值是一个对象;接着获取的私有字段并设置可访问,然后通过反射获取的字段的值,这个值是一个对象;最后是获取的私有字段,设置可访问之后通过反射获取的字段的值。

中间的构造匿名类的部分就不说了,和之前的是很像的,别忘记最后的就行。

然后是这段代码:

也就是定义我们自己的和并加入到中,接着反射获取 类的构造函数并将构造函数设置为可访问,然后创建了一个 对象的实例,接着将刚刚创建的实例添加到过滤器配置的 中, 为键,这样就可以将动态创建的过滤器配置信息加入应用程序的全局配置中。

需要注意的是,在及以前和这两个类所属的包名是:

及以后,包名是这样的:

由于这方面的区别,最好是直接都用反射去写这个内存马,具体参考:

https://github.com/feihong-cs/memShell/blob/master/src/main/java/com/memshell/tomcat/FilterBasedWithoutRequestVariant.java

还有个需要注意的点就是,我给出的这个代码只适用于及以上,因为 这行代码中用到的是在规范中才有的。

3.2.3 tomcat6下filter内存马的编写

这里直接贴出参考文章,后面有空的话,会在我的博客中补全这部分的研究:

https://xz.aliyun.com/t/9914

https://mp.weixin..com/s/sAVh3BLYNHShKwg3b7WZlQ

https://www.cnblogs.com/CoLo/p/16840371.html

https://flowerwind.github.io/2021/10/11/tomcat6、7、8、9内存马/

https://9bie.org/index.php/archives/960/

https://github.com/xiaopan233/GenerateNoHard

https://github.com/ax1sX/MemShell/tree/main/TomcatMemShell

3.3 Listener内存马

3.3.1 简单的Listener内存马demo编写

根据我们在上面的节中所讨论的内容,我们可以得出以下结论:

如果我们想要写一个内存马,需要经过以下步骤:

  • 继承并编写一个恶意
  • 获取
  • 调用添加恶意

由以上结论我们可以写出如下内存马:

效果如下:

3.3.2 Listener内存马demo代码分析

最关键部分的代码如下:

前面四行代码干一件事:获取;后两行干代码干这两件事:实例化我们编写的恶意,调用方法加入到中去,这样最终就会到。

四、Spring MVC框架型内存马

4.1 Spring Controller型内存马

4.1.1 简单的Spring Controller型内存马demo编写

由节中的分析可知,要编写一个型内存马,需要经过以下步骤:

  • 获取
  • 获取实例
  • 通过反射获得自定义的恶意方法的对象
  • 定义
  • 动态注册

代码如下:

运行效果:

4.1.2 Spring Controller型内存马demo代码分析

代码的关键在于如下这几行:

这段代码先利用获取当前请求的,这个是框架提供的用于存储和访问请求相关信息的工具类;接着从上一步中获取到的中获取;接着通过反射获得我们自定义的恶意方法的对象,然后就是拿到对应的对象;通过实例+处理请求的+对应的对象即可调用方法动态添加恶意。

4.2 Spring Interceptor型内存马

由节的分析我们很容易得出型内存马的编写思路:

  • 获取
  • 通过反射来获取
  • 将要注入的恶意拦截器放入到中

具体代码我会放到针对实际中间件打内存马那里。

4.3 Spring WebFlux内存马

4.3.1 简单的Spring WebFlux内存马demo编写

由节的分析我们可以写出下面的代码:

4.3.2 Spring WebFlux内存马demo代码分析

从之前的分析我们知道,主要思路就是通过反射找到,然后拿到,把我们的插入到其中的第一位,再用这个重新调用公共构造函数,赋值给之前分析里面我没看到的即可。

思路就是这么个思路,我们来看具体的代码。

先是通过反射来获取当前运行的所有线程组,然后遍历线程数组,检查每个线程是否为实例。如果发现一个线程是,那就继续下一步的操作。接下来就是找对象:

这条链子在之前的分析中已经提到过,一步步调用我们写的函数即可。

然后就是修改这个过滤器链,添加我们自定义的恶意filter,并把它放到第一位:

然后通过反射获取的私有字段,设置为可访问之后,通过反射将原始的过滤器链替换为新创建的过滤器链,然后恢复字段的可访问权限:

这里补充一下上面的和的含义,第一个代码意思就是使用反射机制,通过对象来修改字段的修饰符,返回字段的当前修饰符,然后通过位运算,将当前修饰符的位清除(置为),表示移除了修饰符;第二个则是把字段的修饰符重新设置为包含修饰符的修饰符,这样就可以保持字段的封装性。

五、中间件型内存马

5.1 Tomcat Valve型内存马

我这里是新建了一个项目,并创建配置好了目录和环境,中的依赖如下:

如果idea启动tomcat报错,可以看看是不是你开了网易云哈哈哈:

在目录下新建一个:

上面的这个是采用了从反射获取的方式,效果如下:

下面的则是调用 实现的:

效果如下:

5.2 Tomcat Upgrade内存马

由节中的分析,我们可以写出如下代码:

运行之后执行命令,结果如下:

版本为:

启动项目之后执行以下两条命令:

5.3 Tomcat Executor内存马

由的分析,我们可以写出下面的内存马:

关于上面的内存马的分析,请参考下面这篇文章:

https://mp.weixin..com/s/cU2s8D2BcJHTc7IuXO-1UQ

效果:

需要注意的是,原文中的代码没有考虑到命令输出结果中含有中文等字符的情况,所以需要编码,这一点我在上面的代码中已改进。

当然,如果目标条件运行,你也可以利用直接外带出来,代码如下:

先开启监听:

然后发送两次数据包,第一次是为了访问,第二次是为了执行命令:

可以看到数据已经传输过来了:

当然,用自带的这个是有缺陷的,就是不能持续接受,因为不能返回自定义的状态码,因此我们可以自己写一个:

然后修改代码中的:

最后效果如下:

六、致谢

我在学习Java内存马的过程中阅读参考引用了以下文章,每篇文章都或多或少地给予了我帮助与启发,于是在此一并列出,以表我诚挚的谢意:

小讯
上一篇 2025-01-01 10:30
下一篇 2024-12-30 16:49

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/6624.html