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的只执行一次机制.

相关推荐
learning_tom8 小时前
微信小程序功能实现:页面导航与跳转
前端·javascript·apache
有个人神神叨叨12 小时前
【Apache Olingo】全面深入分析报告-OData
apache·ea·odata
阿湯哥12 小时前
Apache Camel 中 ProducerTemplate
apache
墨菲安全17 小时前
Apache OFBiz Scrum 组件命令注入漏洞
apache·scrum·命令注入·apache ofbiz·scrum组件
lang201509281 天前
Apache Ignite的流处理(Streaming)示例程序
开发语言·apache·ignite
失因1 天前
Linux systemd 服务管理与 Firewall 防火墙配置
linux·运维·服务器·centos·apache
SeaTunnel1 天前
从《中国开源年度报告》看中国开源力量的十年变迁中,Apache SeaTunnel 的跃迁
开源·apache
xingzizhanlan1 天前
apache-tomcat-11.0.9安装及环境变量配置
java·tomcat·apache
smileNicky1 天前
从 LinkedIn 到 Apache:Kafka 的架构设计与应用场景
kafka·apache·linq
SelectDB技术团队2 天前
ApacheCon Asia 2025 中国开源年度报告:Apache Doris 国内第一
开源·apache·数据库开发·doris·实时分析