Apache shiro RegExPatternMatcher 权限绕过漏洞 (CVE-2022-32532)

漏洞描述

2022年6月29日,Apache 官方披露 Apache Shiro (CVE-2022-32532)权限绕过漏洞。

Apache Shiro中使用RegexRequestMatcher进行权限配置,且正则表达式中携带"."时,未经授权的远程攻击者可通过构造恶意数据包绕过身份认证,导致配置的权限验证失效。

相关介绍

Apache Shiro 是一个功能强大且易于使用的 Java 安全框架,它可以执行身份验证、授权、加密和会话管理,可以用于保护任何应用程序------从命令行应用程序、移动应用程序到最大的 web 和企业应用程序。

影响版本

安全版本:Apache Shiro = 1.9.1

受影响版本:Apache Shiro < 1.9.1

漏洞

贴上我遇到的漏洞截图,如下图:

根据提示将相关包的版本升级至1.9.1,打包程序并部署。

发现还是会扫描出该漏洞,依次升级至1.10.01.11.01.12.0,直到1.12.0版本该漏洞未出现了。

本以为是简单的版本升级,结果发现登录请求里的createToken之类的方法每次都执行2次;

跟踪代码

  • Shiro配置类ShiroConfig中的shiroFilterFactoryBean方法
java 复制代码
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager)
    {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // Shiro的核心安全接口,这个属性是必须的
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 身份认证失败,则跳转到登录页面的配置
        shiroFilterFactoryBean.setLoginUrl(loginUrl);
        // 权限认证失败,则跳转到指定页面
        shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);
        // Shiro连接约束配置,即过滤链的定义
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 对静态资源设置匿名访问
        ...........
        return shiroFilterFactoryBean;
    }
  • 进一步追踪
java 复制代码
private void applyGlobalPropertiesIfNecessary(Filter filter) {
        this.applyLoginUrlIfNecessary(filter);
        this.applySuccessUrlIfNecessary(filter);
        this.applyUnauthorizedUrlIfNecessary(filter);
        if (filter instanceof OncePerRequestFilter) {
            ((OncePerRequestFilter)filter).setFilterOncePerRequest(this.filterConfiguration.isFilterOncePerRequest());
        }

    }

    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof Filter) {
            log.debug("Found filter chain candidate filter '{}'", beanName);
            Filter filter = (Filter)bean;
            this.applyGlobalPropertiesIfNecessary(filter);
            this.getFilters().put(beanName, filter);
        } else {
            log.trace("Ignoring non-Filter bean '{}'", beanName);
        }
        return bean;
    }

从上面方法可以看出来postProcessBeforeInitialization方法在bean初始化的时候去执行, 将自定义的登录过滤器中的setFilterOncePerRequest设置为了ShiroFilterConfiguration实例中给定的值;

其值默认是false,未启用OncePerRequestFilter的只执行一次机制

OncePerRequestFilter 类的核心方法(1.9.0和1.12.0版本的区别)

  • Apache Shiro = 1.9.0时,OncePerRequestFilter类源码
java 复制代码
package org.apache.shiro.web.servlet;

import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public abstract class OncePerRequestFilter extends NameableFilter {
    private static final Logger log = LoggerFactory.getLogger(OncePerRequestFilter.class);
    public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";
    private boolean enabled = true;

    public OncePerRequestFilter() {}
    public boolean isEnabled() { return this.enabled; }
    public void setEnabled(boolean enabled) { this.enabled = enabled; }

    public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String alreadyFilteredAttributeName = this.getAlreadyFilteredAttributeName();
        if (request.getAttribute(alreadyFilteredAttributeName) != null) {
            log.trace("Filter '{}' already executed.  Proceeding without invoking this filter.", this.getName());
            filterChain.doFilter(request, response);
        } else if (this.isEnabled(request, response) && !this.shouldNotFilter(request)) {
            log.trace("Filter '{}' not yet executed.  Executing now.", this.getName());
            request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
            try {
                this.doFilterInternal(request, response, filterChain);
            } finally {
                request.removeAttribute(alreadyFilteredAttributeName);
            }
        } else {
            log.debug("Filter '{}' is not enabled for the current request.  Proceeding without invoking this filter.", this.getName());
            filterChain.doFilter(request, response);
        }
    }

    protected boolean isEnabled(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        return this.isEnabled();
    }

    protected String getAlreadyFilteredAttributeName() {
        String name = this.getName();
        if (name == null) {
            name = this.getClass().getName();
        }
        return name + ".FILTERED";
    }

    /** @deprecated */
    @Deprecated
    protected boolean shouldNotFilter(ServletRequest request) throws ServletException {
        return false;
    }

    protected abstract void doFilterInternal(ServletRequest var1, ServletResponse var2, FilterChain var3) throws ServletException, IOException;
}
  • Apache Shiro = 1.12.0时,OncePerRequestFilter 类源码
java 复制代码
package org.apache.shiro.web.servlet;

import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public abstract class OncePerRequestFilter extends NameableFilter {
    private static final Logger log = LoggerFactory.getLogger(OncePerRequestFilter.class);
    public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";
    private boolean enabled = true;
    private boolean filterOncePerRequest = false;
    
    public OncePerRequestFilter() {}
    public boolean isEnabled() { return this.enabled; }
    public void setEnabled(boolean enabled) { this.enabled = enabled; }
    public boolean isFilterOncePerRequest() { return this.filterOncePerRequest; }
    public void setFilterOncePerRequest(boolean filterOncePerRequest) {
        this.filterOncePerRequest = filterOncePerRequest;
    }

    public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String alreadyFilteredAttributeName = this.getAlreadyFilteredAttributeName();
        if (request.getAttribute(alreadyFilteredAttributeName) != null && this.filterOncePerRequest) {
            log.trace("Filter '{}' already executed.  Proceeding without invoking this filter.", this.getName());
            filterChain.doFilter(request, response);
        } else if (this.isEnabled(request, response) && !this.shouldNotFilter(request)) {
            log.trace("Filter '{}' not yet executed.  Executing now.", this.getName());
            request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
            try {
                this.doFilterInternal(request, response, filterChain);
            } finally {
                request.removeAttribute(alreadyFilteredAttributeName);
            }
        } else {
            log.debug("Filter '{}' is not enabled for the current request.  Proceeding without invoking this filter.", this.getName());
            filterChain.doFilter(request, response);
        }
    }

    protected boolean isEnabled(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        return this.isEnabled();
    }

    protected String getAlreadyFilteredAttributeName() {
        String name = this.getName();
        if (name == null) {
            name = this.getClass().getName();
        }
        return name + ".FILTERED";
    }

    /** @deprecated */
    @Deprecated
    protected boolean shouldNotFilter(ServletRequest request) throws ServletException {
        return false;
    }
    
    protected abstract void doFilterInternal(ServletRequest var1, ServletResponse var2, FilterChain var3) throws ServletException, IOException;
}

对比两个版本代码的发现:Apache Shiro = 1.12.0版本时,doFilter方法第三行代码增加了&& filterOncePerRequest判断,这个值就是通过ShiroFilterConfiguration > ShiroFilterFactoryBean一路传进来的,而且他是在构造ShiroFilterFactoryBean之后执行的, 比自定义Filter的构造时间要晚, 所以尝试在自定义过滤器的构造方法或者postxxx, afterxxx之类的方法中去设置为true都是没用的。

只能是构造ShiroFilterFactoryBean对象时, 设置其配置属性来解决问题。

解决方法

  • 创建ShiroFilterFactoryBean的时候, 给他一个ShiroFilterConfiguration实例对象, 并且设置这个实例的setFilterOncePerRequest(true)
java 复制代码
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager)
    {
        ShiroFilterConfiguration config = new ShiroFilterConfiguration();
        //全局配置是否启用OncePerRequestFilter的只执行一次机制
        config.setFilterOncePerRequest(Boolean.TRUE);
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setShiroFilterConfiguration(config);
        // Shiro的核心安全接口,这个属性是必须的
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 身份认证失败,则跳转到登录页面的配置
        shiroFilterFactoryBean.setLoginUrl(loginUrl);
        // 权限认证失败,则跳转到指定页面
        shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);
        // Shiro连接约束配置,即过滤链的定义
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 对静态资源设置匿名访问
        ...........
        return shiroFilterFactoryBean;
    }

总结

Apache Shiro = 1.9.0以前的版本,OncePerRequestFilter过滤器子类型默认只执行一次,

现在可以通过全局配置来选择是否启用OncePerRequestFilter的只执行一次机制.

相关推荐
专注_每天进步一点点9 小时前
mysql-connector-j(8.0 及以上版本,包括你使用的 8.3.0)并非采用 GPL 许可证,因此你在项目中引入该依赖时,不需要遵循 GPL 的开源要求(比如开源你的整个项目)
数据库·mysql·apache
不爱学英文的码字机器9 小时前
Apache RocketMQ+cpolar 让消息服务全网可达
apache·rocketmq
鸽芷咕10 小时前
海量时序数据选型指南:从大数据架构演进看 Apache IoTDB 的崛起
大数据·数据库·架构·apache
D愿你归来仍是少年1 天前
Apache Spark 第 3 章:核心概念 RDD / DataFrame
大数据·spark·apache
D愿你归来仍是少年1 天前
Apache Spark 第 4 章:Spark 整体架构
spark·apache
D愿你归来仍是少年1 天前
Apache Flink 算子(Operator)深度解析
大数据·flink·apache
可涵不会debug1 天前
时序数据库选型指南:Apache IoTDB——大数据时代的优选方案
apache·时序数据库·iotdb
yumgpkpm1 天前
Apache Spark 和 Flink,处理实时大数据流对比(Cloudera CDH、CDP)
flink·spark·apache
羑悻的小杀马特1 天前
Apache IoTDB:开启端边云协同的时序数据新时代
运维·人工智能·apache·iotdb
是馄饨呀2 天前
Apache Tomcat RewriteValve路径遍历漏洞(CVE-2025-55752)修复
java·tomcat·apache