https://boogipop.com/2023/03/02/Tomcat%E5%86%85%E5%AD%98%E9%A9%AC%E5%9B%9E%E6%98%BE%E6%8A%80%E6%9C%AF/

https://goodapple.top/archives/1355

ThreadLocal

原理:tomcat处理请求的过程中,中途会调用一个方法,若满足某个条件,则将该次请求的request和response放到一个地方。我们只要去这个地方拿到request对象就行。

中途会调用的方法是ApplicationFilterChain#internalDoFilter

要满足的条件是ApplicationDispatcher.WRAP_SAME_OBJECT == true

存放request和response的地方分别是ApplicationFilterChain.lastServicedRequestApplicationFilterChain.lastServicedResponse

以上3个属性都是static final的,要进行处理。

所以获取request的完整流程是:第一次请求,修改static final属性,使之满足tomcat存放request的条件。第二次请求,从存放request的地方取出request。

局限性:如果漏洞点在ApplicationFilterChain#internalDoFilter之前,则无法利用,因为此时tomcat还没往那里放request,自然就获取不到。

完整代码

try {
Field WRAP_SAME_OBJECT_FIELD = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
Field lastServicedRequestField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");
Field lastServicedResponseField = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse");

//使用modifiersField反射修改final型变量
java.lang.reflect.Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() & ~Modifier.FINAL);
modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() & ~Modifier.FINAL);
modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() & ~Modifier.FINAL);
WRAP_SAME_OBJECT_FIELD.setAccessible(true);
lastServicedRequestField.setAccessible(true);
lastServicedResponseField.setAccessible(true);

//将变量WRAP_SAME_OBJECT_FIELD设置为true,并初始化lastServicedRequest和lastServicedResponse变量
if (!WRAP_SAME_OBJECT_FIELD.getBoolean(null)) {
WRAP_SAME_OBJECT_FIELD.setBoolean(null, true);
}

if (lastServicedRequestField.get(null) == null && lastServicedResponseField.get(null) == null) {
lastServicedRequestField.set(null, new ThreadLocal<>());
lastServicedResponseField.set(null, new ThreadLocal<>());
}else {
//获取request变量
ThreadLocal threadLocal = (ThreadLocal) lastServicedRequestField.get(null);
ServletRequest servletRequest = (ServletRequest) threadLocal.get();
System.out.println(servletRequest);
System.out.println((HttpServletRequest) servletRequest == req);
}
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}

全局Response

获取request的链子,自己找挺难找的,只能说师傅们太强大了。

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

如何获取StandardService,和Tomcat的类加载机制有关。

Tomcat的类加载机制

众所周知,Tomcat使用的并不是传统的类加载机制,我们来看下面的例子

我们知道,Tomcat中的一个个Webapp就是一个个Web应用,如果WebAPP A依赖了common-collection 3.1,而WebApp B依赖了common-collection 3.2。这样在加载的时候由于全限定名相同,因此不能同时加载,所以必须对各个Webapp进行隔离,如果使用双亲委派机制,那么在加载一个类的时候会先去他的父加载器加载,这样就无法实现隔离。

Tomcat隔离的实现方式是每个WebApp用一个独有的ClassLoader实例来优先处理加载,并不会传递给父加载器。这个定制的ClassLoader就是WebappClassLoader

那么我们又如何将原有的父加载器和WebappClassLoader联系起来呢?这里Tomcat使用的机制是线程上下文类加载器Thread ContextClassLoader。

Thread类中有getContextClassLoader()setContextClassLoader(ClassLoader cl)方法用来获取和设置上下文类加载器。如果没有setContextClassLoader(ClassLoader cl)方法通过设置类加载器,那么线程将继承父线程的上下文类加载器,如果在应用程序的全局范围内都没有设置的话,那么这个上下文类加载器默认就是应用程序类加载器。对于Tomcat来说ContextClassLoader被设置为WebAppClassLoader(在一些框架中可能是继承了public abstract WebappClassLoaderBase的其他Loader)。

因此WebappClassLoaderBase就是我们寻找的Thread和Tomcat 运行上下文的联系之一。

这里通过调试,我们能够看到这里的线程类加载器是继承了WebAppClassLoaderParallelWebAppClassLoader

获取当前线程类加载器看看有什么东西。

image-20240207105754047.png

这样就拿到了SandardService。

image-20240207105915089.png

完整exp构造,直接用的师傅们写好的,不过我有个点不一样:

image-20240207111243481.png

我的WebappClassLoaderBase的resources是通过反射获取的,因为我这里的getResources方法已经deprecated了,返回结果是null。

image-20240207111355291.png