Java内存马之Servlet、Filter和Listener的开发基础

前言

看到某个题里面用了,但是还没学过,这里就系统地学习一下Java的内存马,这部分再重学一下基础,并且复习一下开发这部分。跟着调试一下里面的细节。
其中部分内容、学习路线来自于:

GitHub - W01fh4cker/LearnJavaMemshellFromZero: 【三万字原创】完全零基础从0到1掌握Java内存马

讲的真不错,推荐一下。

准备知识

Tomcat 架构原理解析到架构设计借鉴_牛客博客

Tomcat的四种容器中,EngineHostContextWrapper中关系如下。
image.png
如果需要访问一个https://domain1.xx.com:8080/user/list

  • 首先请求到Tomcat服务器,Tomcat的每个连接器都监听着不同的端口。例如HTTP连接器默认监听8080端口,AJP连接器监听8009端口。因此这里被HTTP连接器接收,而HTTP连接器是属于Service组件,而有一个Service中有一个容器组件,Service中的Engine也被选中。
  • ServiceEngine被确定了之后,Mapper(保存着容器组件和访问路径的映射)会通过url中的域名去寻找Host容器。比如这里面的domain1.xx.com对应的Host1容器。
  • Host确定,Mapper根据url的路径匹配web应用。比如这里访问的/user找到Context1
  • Context确定之后,Mapper再根据web.xml配置的Servlet的路径找到WrapperServlet,也就是这里的/list

这个Context拼完地出现在Java中,我对它的理解就是某个时间点的当前所在环境的一个切片,切片中存储着当时的环境变量等信息。
这个图片比较完整,可以通过这个大致了解一下。
image.png

Servlet

环境配置

1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
</dependencies>

简单Servlet组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.natro92;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/hello")
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.getWriter().println("Hello World!");
}
}

配置Tomcat环境:
image.png
image.png
修复构建工件Web Application: Exploded展开型。
image.png
image.png
然后修改项目结构:
image.png
image.png
然后保存运行访问即可:
image.png

Servlet初始化和装载

环境配置

这里不用已经打包好的Tomcat,而是Tomcat的依赖,也就是tomcat-embed-core

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<dependencies>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.83</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>9.0.83</version>
<scope>compile</scope>
</dependency>
</dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package org.example;

import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;
import java.io.File;

public class Main {
public static void main(String[] args) throws LifecycleException {
Tomcat tomcat = new Tomcat();
tomcat.getConnector(); //tomcat 9.0以上需要加这行代码,参考:https://blog.csdn.net/qq_42944840/article/details/116349603
Context context = tomcat.addWebapp("", new File(".").getAbsolutePath());
Tomcat.addServlet(context, "helloServlet", new HelloServlet());
context.addServletMappingDecoded("/hello", "helloServlet");
tomcat.start();
tomcat.getServer().await();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package org.example;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html><body>");
out.println("Hello, World!");
out.println("</body></html>");
}
}

image.png

初始化流程分析

断点在org.apache.catalina.core.StandardWrapper#setServletClass
image.png
Ctrl+Alt+F7查找上层调用,其中的org.apache.catalina.startup.ContextConfig#configureContext
1510行到1568行:
image.png
webxml.getServlets()获取所有Servlet,创建Wrapper,判断加载顺序是否开启,设置Servlet名称。将所有参数加入Wrapper中。中间配置一些设置信息Multipartdef
image.png
设置是否支持异步,是否重写,并且将Wrapper键入上下文。再通过循环将Servleturl映射起来。
也就是Servlet的初始化按照下面几个步骤:

  • 创建Wrapper对象
  • 设置ServletLoadOnStartUp
  • 设置Servlet名称
  • 设置Servletclass
  • Wrapper添加到Context
  • Servleturl映射加入context

装载流程分析

org.apache.catalina.core.StandardWrapper#loadServlet
image.png
startInternal函数中我们可以看到有其中的调用顺序:
image.png
image.png
listener->filter->servlet按照这个顺序load。
这其中调用的org.apache.catalina.core.StandardContext#loadOnStartup
image.png
将传入的Container中的数组中的ServletloadOnStartUp作为键,将Wrapper存入响应列表。如果loadOnStartup是负数,则除了访问,不会加载。如果不是负数,按照loadOnStartup升序加载。

Filter

filter-demo.png
FilterServlet 规范中的一部分,它允许你对请求和响应进行预处理和后处理。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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.natro92;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter("/test")
public class TestFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("TestFilter init");
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("TestFilter doFilter");
}

@Override
public void destroy() {
System.out.println("TestFilter destroy");
}
}

image.png
运行时就输出了初始化,而访问/test路由之后,控制台输出TestFilter doFilter部分。最后在关闭Tomcat时,出现TestFilter destroy
PS:如果关闭之后出现报错:
严重 [main] org.apache.catalina.startup.Catalina.stopServer 未配置关闭端口。通过OS信号关闭服务器。服务器未关闭。则修改Tomcat根目录/conf/web.xml中,将port改为8005即可:
image.png
即可出现销毁事件:
image.png

流程分析

这里需要先配置依赖:

1
2
3
4
5
6
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.100</version>
<scope>provided</scope>
</dependency>

doFilter处下断点:
image.png
跟进org.apache.catalina.core.StandardWrapperValve#invoke
image.png
这里再次展示了FilterChain部分这里的结构:
image.png
下面这段代码中有两次出现,我们直接查看168行部分的。

1
filterChain.doFilter(request.getRequest(), response.getResponse());

image.png
其中的filterChain我们返回到前面实例化位置。
image.png
查看其中的createFilterChain方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
public static ApplicationFilterChain createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet) {

// If there is no servlet to execute, return null
if (servlet == null) {
return null;
}

// Create and initialize a filter chain object
ApplicationFilterChain filterChain = null;
if (request instanceof Request) {
Request req = (Request) request;
if (Globals.IS_SECURITY_ENABLED) {
// Security: Do not recycle
filterChain = new ApplicationFilterChain();
} else {
filterChain = (ApplicationFilterChain) req.getFilterChain();
if (filterChain == null) {
filterChain = new ApplicationFilterChain();
req.setFilterChain(filterChain);
}
}
} else {
// Request dispatcher in use
filterChain = new ApplicationFilterChain();
}

filterChain.setServlet(servlet);
filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());

// Acquire the filter mappings for this Context
StandardContext context = (StandardContext) wrapper.getParent();
FilterMap filterMaps[] = context.findFilterMaps();

// If there are no filter mappings, we are done
if (filterMaps == null || filterMaps.length == 0) {
return filterChain;
}

// Acquire the information we will need to match filter mappings
DispatcherType dispatcher = (DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);

String requestPath = null;
Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR);
if (attribute != null) {
requestPath = attribute.toString();
}

String servletName = wrapper.getName();

// Add the relevant path-mapped filters to this filter chain
for (FilterMap filterMap : filterMaps) {
if (!matchDispatcher(filterMap, dispatcher)) {
continue;
}
if (!matchFiltersURL(filterMap, requestPath)) {
continue;
}
ApplicationFilterConfig filterConfig =
(ApplicationFilterConfig) context.findFilterConfig(filterMap.getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
filterChain.addFilter(filterConfig);
}

// Add filters that match on servlet name second
for (FilterMap filterMap : filterMaps) {
if (!matchDispatcher(filterMap, dispatcher)) {
continue;
}
if (!matchFiltersServlet(filterMap, servletName)) {
continue;
}
ApplicationFilterConfig filterConfig =
(ApplicationFilterConfig) context.findFilterConfig(filterMap.getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
filterChain.addFilter(filterConfig);
}

// Return the completed filter chain
return filterChain;
}

上来先判断servlet是否为空。如果不为空继续下去,根据ServletRequest如果是Request类型,并且开启了IS_SECURITY_ENABLED,则创建新的ApplicationFilterChain,否则尝试获取已有的ApplicationFilterChain(不存在就创建新的)。
image.png
设置ServletServletSupportsAsync属性。从Wrapper中获取父级上下文StandardContext,然后获取filterMaps,遍历获取然后根据DispatcherType,将匹配的过滤器添加到FilterChain,最终返回创建或者更新后的FilterChain
image.png
filterChain.doFilter()进去到org.apache.catalina.core.ApplicationFilterChain#doFilter处。
image.png
其中调用的internalDoFilter里面
image.png
org.apache.catalina.core.ApplicationFilterChain#internalDoFilter拿到filterConfigfilter
image.png
如果需要打入内存马,需要动态创建一个Filter
image.png
image.png
这里的org.apache.catalina.core.StandardContext#findFilterMapsorg.apache.catalina.core.StandardContext#findFilterConfig实现代码:
image.png
image.png
找到现有的上下文,然后插入自定义的恶意过滤器映射和过滤器配置,实现动态添加过滤器。也就是如何添加filterMapfilterConfig
org.apache.catalina.core.StandardContext#addFilterMaporg.apache.catalina.core.StandardContext#addFilterMapBefore可以从
image.png
image.png
这两个前者可以将一个新filter添加到当前filter映射的最后;而后者可以将一个filter移动到最先。
进入到二者最先调用的方法org.apache.catalina.core.StandardContext#validateFilterMap
image.png
这里显示再根据通过filterName查找FilterDef时,需要不报错,也就是存在,就需要自定义一个filterDef并加入filterDefs,可以使用org.apache.catalina.core.StandardContext#addFilterDef实现。
image.png
但是现在没有添加filterConfig的方法,只有filterStartfilterStop这两个方法。
image.png
image.png
可以使用反射来获取并添加属性。

Listener

image.png
Listener作为最先被加载的,因此也可以形成一种内存马。
常见Listener

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

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

简单实现

继续使用上一个项目。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.natro92;

import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;

@WebListener("/listener")
public class TestListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("销毁 TestListener");
}

@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("初始化 TestListener");
}
}

image.png
image.png

流程分析

image.png
断点调试
image.png
进入org.apache.catalina.core.StandardContext#listenerStart先通过findApplicationListener,然后实例化这些获取到的listener
image.png
image.png
ServletRequestListener被放在了eventListener
image.png
通过org.apache.catalina.core.StandardContext#getApplicationEventListeners使用addAll方法加入eventListener中。
image.png
也就是将Listener的列表转换为数组返回。
Listener有两个来源,一是根据web.xml文件或者@WebListener注解实例化得到的Listener;二是applicationEventListenersList中的Listener
可以通过使用org.apache.catalina.core.StandardContext#addApplicationEventListener为其添加listener
image.png

后记

这个讲的确实零基础还细,很多讲的点都深入了。