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

古城微笑少年丶 2024-03-02 10:02 110阅读 0赞

漏洞描述

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方法

    @Bean

    1. public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager)
    2. {
    3. ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    4. // Shiro的核心安全接口,这个属性是必须的
    5. shiroFilterFactoryBean.setSecurityManager(securityManager);
    6. // 身份认证失败,则跳转到登录页面的配置
    7. shiroFilterFactoryBean.setLoginUrl(loginUrl);
    8. // 权限认证失败,则跳转到指定页面
    9. shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);
    10. // Shiro连接约束配置,即过滤链的定义
    11. LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
    12. // 对静态资源设置匿名访问
    13. ...........
    14. return shiroFilterFactoryBean;
    15. }
  • 进一步追踪

    private void applyGlobalPropertiesIfNecessary(Filter filter) {

    1. this.applyLoginUrlIfNecessary(filter);
    2. this.applySuccessUrlIfNecessary(filter);
    3. this.applyUnauthorizedUrlIfNecessary(filter);
    4. if (filter instanceof OncePerRequestFilter) {
    5. ((OncePerRequestFilter)filter).setFilterOncePerRequest(this.filterConfiguration.isFilterOncePerRequest());
    6. }
    7. }
    8. public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    9. if (bean instanceof Filter) {
    10. log.debug("Found filter chain candidate filter '{}'", beanName);
    11. Filter filter = (Filter)bean;
    12. this.applyGlobalPropertiesIfNecessary(filter);
    13. this.getFilters().put(beanName, filter);
    14. } else {
    15. log.trace("Ignoring non-Filter bean '{}'", beanName);
    16. }
    17. return bean;
    18. }

从上面方法可以看出来postProcessBeforeInitialization方法在bean初始化的时候去执行, 将自定义的登录过滤器中的setFilterOncePerRequest设置为了ShiroFilterConfiguration实例中给定的值;
其值默认是false,未启用OncePerRequestFilter的只执行一次机制

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

  • Apache Shiro = 1.9.0时,OncePerRequestFilter类源码

    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 {

    1. private static final Logger log = LoggerFactory.getLogger(OncePerRequestFilter.class);
    2. public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";
    3. private boolean enabled = true;
    4. public OncePerRequestFilter() {
    5. }
    6. public boolean isEnabled() {
    7. return this.enabled; }
    8. public void setEnabled(boolean enabled) {
    9. this.enabled = enabled; }
    10. public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    11. String alreadyFilteredAttributeName = this.getAlreadyFilteredAttributeName();
    12. if (request.getAttribute(alreadyFilteredAttributeName) != null) {
    13. log.trace("Filter '{}' already executed. Proceeding without invoking this filter.", this.getName());
    14. filterChain.doFilter(request, response);
    15. } else if (this.isEnabled(request, response) && !this.shouldNotFilter(request)) {
    16. log.trace("Filter '{}' not yet executed. Executing now.", this.getName());
    17. request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
    18. try {
    19. this.doFilterInternal(request, response, filterChain);
    20. } finally {
    21. request.removeAttribute(alreadyFilteredAttributeName);
    22. }
    23. } else {
    24. log.debug("Filter '{}' is not enabled for the current request. Proceeding without invoking this filter.", this.getName());
    25. filterChain.doFilter(request, response);
    26. }
    27. }
    28. protected boolean isEnabled(ServletRequest request, ServletResponse response) throws ServletException, IOException {
    29. return this.isEnabled();
    30. }
    31. protected String getAlreadyFilteredAttributeName() {
    32. String name = this.getName();
    33. if (name == null) {
    34. name = this.getClass().getName();
    35. }
    36. return name + ".FILTERED";
    37. }
    38. /** @deprecated */
    39. @Deprecated
    40. protected boolean shouldNotFilter(ServletRequest request) throws ServletException {
    41. return false;
    42. }
    43. protected abstract void doFilterInternal(ServletRequest var1, ServletResponse var2, FilterChain var3) throws ServletException, IOException;

    }

  • Apache Shiro = 1.12.0时,OncePerRequestFilter 类源码

    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 {

    1. private static final Logger log = LoggerFactory.getLogger(OncePerRequestFilter.class);
    2. public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";
    3. private boolean enabled = true;
    4. private boolean filterOncePerRequest = false;
    5. public OncePerRequestFilter() {
    6. }
    7. public boolean isEnabled() {
    8. return this.enabled; }
    9. public void setEnabled(boolean enabled) {
    10. this.enabled = enabled; }
    11. public boolean isFilterOncePerRequest() {
    12. return this.filterOncePerRequest; }
    13. public void setFilterOncePerRequest(boolean filterOncePerRequest) {
    14. this.filterOncePerRequest = filterOncePerRequest;
    15. }
    16. public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    17. String alreadyFilteredAttributeName = this.getAlreadyFilteredAttributeName();
    18. if (request.getAttribute(alreadyFilteredAttributeName) != null && this.filterOncePerRequest) {
    19. log.trace("Filter '{}' already executed. Proceeding without invoking this filter.", this.getName());
    20. filterChain.doFilter(request, response);
    21. } else if (this.isEnabled(request, response) && !this.shouldNotFilter(request)) {
    22. log.trace("Filter '{}' not yet executed. Executing now.", this.getName());
    23. request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
    24. try {
    25. this.doFilterInternal(request, response, filterChain);
    26. } finally {
    27. request.removeAttribute(alreadyFilteredAttributeName);
    28. }
    29. } else {
    30. log.debug("Filter '{}' is not enabled for the current request. Proceeding without invoking this filter.", this.getName());
    31. filterChain.doFilter(request, response);
    32. }
    33. }
    34. protected boolean isEnabled(ServletRequest request, ServletResponse response) throws ServletException, IOException {
    35. return this.isEnabled();
    36. }
    37. protected String getAlreadyFilteredAttributeName() {
    38. String name = this.getName();
    39. if (name == null) {
    40. name = this.getClass().getName();
    41. }
    42. return name + ".FILTERED";
    43. }
    44. /** @deprecated */
    45. @Deprecated
    46. protected boolean shouldNotFilter(ServletRequest request) throws ServletException {
    47. return false;
    48. }
    49. 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)

    @Bean

    1. public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager)
    2. {
    3. ShiroFilterConfiguration config = new ShiroFilterConfiguration();
    4. //全局配置是否启用OncePerRequestFilter的只执行一次机制
    5. config.setFilterOncePerRequest(Boolean.TRUE);
    6. ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    7. shiroFilterFactoryBean.setShiroFilterConfiguration(config);
    8. // Shiro的核心安全接口,这个属性是必须的
    9. shiroFilterFactoryBean.setSecurityManager(securityManager);
    10. // 身份认证失败,则跳转到登录页面的配置
    11. shiroFilterFactoryBean.setLoginUrl(loginUrl);
    12. // 权限认证失败,则跳转到指定页面
    13. shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);
    14. // Shiro连接约束配置,即过滤链的定义
    15. LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
    16. // 对静态资源设置匿名访问
    17. ...........
    18. return shiroFilterFactoryBean;
    19. }

总结

Apache Shiro = 1.9.0以前的版本,OncePerRequestFilter过滤器子类型默认只执行一次,
现在可以通过全局配置来选择是否启用OncePerRequestFilter的只执行一次机制.

发表评论

表情:
评论列表 (有 0 条评论,110人围观)

还没有评论,来说两句吧...

相关阅读