Java内存马之Servlet、Filter和Listener的开发基础
Java内存马之Servlet、Filter和Listener的开发基础
Natro92前言
看到某个题里面用了,但是还没学过,这里就系统地学习一下Java的内存马,这部分再重学一下基础,并且复习一下开发这部分。跟着调试一下里面的细节。
其中部分内容、学习路线来自于:
GitHub - W01fh4cker/LearnJavaMemshellFromZero: 【三万字原创】完全零基础从0到1掌握Java内存马
讲的真不错,推荐一下。
准备知识
Tomcat
的四种容器中,Engine
、Host
、Context
、Wrapper
中关系如下。
如果需要访问一个https://domain1.xx.com:8080/user/list
- 首先请求到
Tomcat
服务器,Tomcat的每个连接器都监听着不同的端口。例如HTTP
连接器默认监听8080
端口,AJP
连接器监听8009
端口。因此这里被HTTP
连接器接收,而HTTP
连接器是属于Service
组件,而有一个Service
中有一个容器组件,Service
中的Engine
也被选中。 Service
和Engine
被确定了之后,Mapper
(保存着容器组件和访问路径的映射)会通过url
中的域名去寻找Host
容器。比如这里面的domain1.xx.com
对应的Host1
容器。Host
确定,Mapper
根据url
的路径匹配web应用。比如这里访问的/user
找到Context1
。Context
确定之后,Mapper
再根据web.xml
配置的Servlet
的路径找到Wrapper
和Servlet
,也就是这里的/list
这个Context
拼完地出现在Java中,我对它的理解就是某个时间点的当前所在环境的一个切片,切片中存储着当时的环境变量等信息。
这个图片比较完整,可以通过这个大致了解一下。
Servlet
环境配置
1 | <dependencies> |
简单Servlet组件
1 | package com.natro92; |
配置Tomcat环境:
修复构建工件Web Application: Exploded
展开型。
然后修改项目结构:
然后保存运行访问即可:
Servlet初始化和装载
环境配置
这里不用已经打包好的Tomcat,而是Tomcat的依赖,也就是tomcat-embed-core
1 | <dependencies> |
1 | package org.example; |
1 | package org.example; |
初始化流程分析
断点在org.apache.catalina.core.StandardWrapper#setServletClass
Ctrl+Alt+F7
查找上层调用,其中的org.apache.catalina.startup.ContextConfig#configureContext
从1510
行到1568
行:
先webxml.getServlets()
获取所有Servlet
,创建Wrapper
,判断加载顺序是否开启,设置Servlet
名称。将所有参数加入Wrapper
中。中间配置一些设置信息Multipartdef
。
设置是否支持异步,是否重写,并且将Wrapper
键入上下文。再通过循环将Servlet
和url
映射起来。
也就是Servlet
的初始化按照下面几个步骤:
- 创建
Wrapper
对象 - 设置
Servlet
的LoadOnStartUp
- 设置
Servlet
名称 - 设置
Servlet
的class
- 将
Wrapper
添加到Context
中 - 将
Servlet
和url
映射加入context
装载流程分析
org.apache.catalina.core.StandardWrapper#loadServlet
在startInternal
函数中我们可以看到有其中的调用顺序:listener
->filter
->servlet
按照这个顺序load。
这其中调用的org.apache.catalina.core.StandardContext#loadOnStartup
:
将传入的Container
中的数组中的Servlet
的loadOnStartUp
作为键,将Wrapper
存入响应列表。如果loadOnStartup
是负数,则除了访问,不会加载。如果不是负数,按照loadOnStartup
升序加载。
Filter
Filter
是 Servlet
规范中的一部分,它允许你对请求和响应进行预处理和后处理。Filter
就像一个过滤器,在请求到达 Servlet
之前或响应返回给客户端之前,对请求或响应进行拦截和修改。org/apache/tomcat/util/descriptor/web/FilterDef.java
中能看出来,需要定义过滤器FilterDef
,存放这些FilterDef
的数组被称为FilterDefs
,每个FilterDef
定义了一个具体的过滤器,包括描述信息、名称、过滤器实例以及class等;然后是FilterDefs
,它只是过滤器的抽象定义,而FilterConfigs
则是这些过滤器的具体配置实例,我们可以为每个过滤器定义具体的配置参数,以满足系统的需求;紧接着是FilterMaps
,它是用于将FilterConfigs
映射到具体的请求路径或其他标识上,这样系统在处理请求时就能够根据请求的路径或标识找到对应的FilterConfigs
,从而确定要执行的过滤器链;而FilterChain
是由多个FilterConfigs
组成的链式结构,它定义了过滤器的执行顺序,在处理请求时系统会按照FilterChain
中的顺序依次执行每个过滤器,对请求进行过滤和处理。
简单实现
环境还是ServletTest
不变,新增一个TestFilter.java
1 | package com.natro92; |
运行时就输出了初始化,而访问/test
路由之后,控制台输出TestFilter doFilter
部分。最后在关闭Tomcat
时,出现TestFilter destroy
。
PS:如果关闭之后出现报错:严重 [main] org.apache.catalina.startup.Catalina.stopServer 未配置关闭端口。通过OS信号关闭服务器。服务器未关闭。
则修改Tomcat根目录/conf/web.xml
中,将port改为8005即可:
即可出现销毁事件:
流程分析
这里需要先配置依赖:
1 | <dependency> |
从doFilter
处下断点:
跟进org.apache.catalina.core.StandardWrapperValve#invoke
这里再次展示了FilterChain
部分这里的结构:
下面这段代码中有两次出现,我们直接查看168行部分的。
1 | filterChain.doFilter(request.getRequest(), response.getResponse()); |
其中的filterChain
我们返回到前面实例化位置。
查看其中的createFilterChain
方法:
1 | public static ApplicationFilterChain createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet) { |
上来先判断servlet
是否为空。如果不为空继续下去,根据ServletRequest
如果是Request
类型,并且开启了IS_SECURITY_ENABLED
,则创建新的ApplicationFilterChain
,否则尝试获取已有的ApplicationFilterChain
(不存在就创建新的)。
设置Servlet
和ServletSupportsAsync
属性。从Wrapper
中获取父级上下文StandardContext
,然后获取filterMaps
,遍历获取然后根据DispatcherType
,将匹配的过滤器添加到FilterChain
,最终返回创建或者更新后的FilterChain
。
从filterChain.doFilter()
进去到org.apache.catalina.core.ApplicationFilterChain#doFilter
处。
其中调用的internalDoFilter
里面org.apache.catalina.core.ApplicationFilterChain#internalDoFilter
拿到filterConfig
和filter
如果需要打入内存马,需要动态创建一个Filter
这里的org.apache.catalina.core.StandardContext#findFilterMaps
和org.apache.catalina.core.StandardContext#findFilterConfig
实现代码:
找到现有的上下文,然后插入自定义的恶意过滤器映射和过滤器配置,实现动态添加过滤器。也就是如何添加filterMap
和filterConfig
。
而org.apache.catalina.core.StandardContext#addFilterMap
和org.apache.catalina.core.StandardContext#addFilterMapBefore
可以从
这两个前者可以将一个新filter
添加到当前filter
映射的最后;而后者可以将一个filter
移动到最先。
进入到二者最先调用的方法org.apache.catalina.core.StandardContext#validateFilterMap
。
这里显示再根据通过filterName
查找FilterDef
时,需要不报错,也就是存在,就需要自定义一个filterDef
并加入filterDefs
,可以使用org.apache.catalina.core.StandardContext#addFilterDef
实现。
但是现在没有添加filterConfig
的方法,只有filterStart
和filterStop
这两个方法。
可以使用反射来获取并添加属性。
Listener
Listener
作为最先被加载的,因此也可以形成一种内存马。
常见Listener
:
ServletContextListener
,用来监听整个Web应用程序的启动和关闭事件,需要实现contextInitialized
和contextDestroyed
这两个方法;ServletRequestListener
,用来监听HTTP请求的创建和销毁事件,需要实现requestInitialized
和requestDestroyed
这两个方法;HttpSessionListener
,用来监听HTTP会话的创建和销毁事件,需要实现sessionCreated
和sessionDestroyed
这两个方法;HttpSessionAttributeListener
,监听HTTP会话属性的添加、删除和替换事件,需要实现attributeAdded
、attributeRemoved
和attributeReplaced
这三个方法。
很明显,ServletRequestListener
是最适合做内存马的,因为它只要访问服务就能触发操作。
简单实现
继续使用上一个项目。
1 | package com.natro92; |
流程分析
断点调试
进入org.apache.catalina.core.StandardContext#listenerStart
先通过findApplicationListener
,然后实例化这些获取到的listener
将ServletRequestListener
被放在了eventListener
。
通过org.apache.catalina.core.StandardContext#getApplicationEventListeners
使用addAll
方法加入eventListener
中。
也就是将Listener
的列表转换为数组返回。Listener
有两个来源,一是根据web.xml
文件或者@WebListener
注解实例化得到的Listener
;二是applicationEventListenersList
中的Listener
。
可以通过使用org.apache.catalina.core.StandardContext#addApplicationEventListener
为其添加listener
。
后记
这个讲的确实零基础还细,很多讲的点都深入了。