Shiro CVE-2020-1957详细分析

2025-01-13 10 0

Apache Shiro before 1.5.2, when using Apache Shiro with Spring dynamic controllers, a specially crafted request may cause an authentication bypass. [1]

本漏洞有两种攻击方式,一个适用于shiro < 1.5.0 ,另一个是Shiro 1.5.0版本修复补丁考虑不全面而导致,适用于Shiro < 1.5.2

本篇文章将分别讨论这两种攻击方式。(我觉得已经算是两个漏洞了)

shiro < 1.5.0

配置环境:

shiro-1.4.2					  //依赖的还是 javax
spring-boot:2.7.4             //3.xx版本 最低java 17且依赖的是jakarta
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring -->
<dependency>
	<groupId>org.apache.shiro</groupId>
	<artifactId>shiro-spring</artifactId>
	<version>1.4.2</version>
</dependency>

shiro配置
配置分为两部分:

package com.codereview.cve.config;

import java.util.LinkedHashMap;


import org.apache.shiro.realm.Realm;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ShiroConfig {

    @Bean
    public IniRealm getIniRealm() {
        return new IniRealm("classpath:shiro.ini");
    }


    @Bean
    public SimpleCookie rememberMeCookie(){
        return new SimpleCookie("rememberMe");
    }


    @Bean
    DefaultWebSecurityManager securityManager(IniRealm iniRealm) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm((Realm)iniRealm);
        manager.setRememberMeManager(new CookieRememberMeManager());
        return manager;
    }


    @Bean
    ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager);
        bean.setLoginUrl("/login");
        bean.setSuccessUrl("/loginSuccess");
        bean.setUnauthorizedUrl("/unauthorized");
        LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
        //url --> filter1,filter2....
		//对于未设置的url,默认策略是放行,不做任何限制
        map.put("/print", "authc, perms[printer:print]");
        map.put("/query", "authc, perms[printer:query]");
        map.put("/login","authc");
        bean.setFilterChainDefinitionMap(map);
        return bean;
    }



}

shiro.ini:
permission分为:printer 和操作:print,query

# format: roleName = permission1, permission2, ..., permissionN
[roles]
user = printer:print
admin = printer:*


# format: username = password, role1, role2, ..., roleN
[users]
user1 = pswd123, user
admin1 = pswd321, admin

controller
假设存在一个打印机:

package com.codereview.cve.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class PrinterController {

    @GetMapping("/login")
    public String login() {
        return "This is a login page";

    }

    @GetMapping("/hello")
    public String index() {
        return "Hello World";
    }


    @RequestMapping ("/loginSuccess")
    public String loginSuccess() {
        return "login success!";
    }

    @GetMapping("/print")
    public String print() {
        return "you are printing content...";
    }


    @GetMapping("/query")
    public String query() {
        return "you are querying content...";
    }


    @GetMapping("/unauthorized")
    public String unauthorized() {
        return "you are unauthorized";
    }


}

漏洞测试

  • 首先,以user1身份进行登入:

    Shiro CVE-2020-1957详细分析插图根据重定向的Location可知,登入成功(记住这时的Cookie中的sessionid)

  • 然后,访问打印路径:正常打印

    Shiro CVE-2020-1957详细分析插图1

  • 访问查询路径:失败,重定向到未授权路径
    Shiro CVE-2020-1957详细分析插图2

  • 但是,当我们在后面加上一个/符号时:越权访问到了查询操作!!!
    Shiro CVE-2020-1957详细分析插图3

漏洞分析

为什么会导致上述的问题?接下来将从源码角度分析造成该漏洞的原因。

首先我们已知shiro-web的大致流程如下:(详情:shiro-web 软件分析 - FreeBuf网络安全行业门户
Shiro CVE-2020-1957详细分析插图4

图中标红位置,为这此漏洞的入口。

该方法源码如下:

public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
        FilterChainManager filterChainManager = getFilterChainManager();
        if (!filterChainManager.hasChains()) {
            return null;
        }
		//获取请求路径
        String requestURI = getPathWithinApplication(request);

        //遍历filterChainManager管理的所有过滤链的名字
    	//也就是配置文件中配置的"/print","/query","/print"
        for (String pathPattern : filterChainManager.getChainNames()) {

            //检查是否匹配,如果匹配则返回pathPattern对应的过滤链
            if (pathMatches(pathPattern, requestURI)) {
                if (log.isTraceEnabled()) {
                    log.trace("省略");
                }
                return filterChainManager.proxy(originalChain, pathPattern);
            }
        }

        return null;
    }

源码调试
注意看此时requestURI: "/query/"

Shiro CVE-2020-1957详细分析插图5

查看filterChainManager.getChainNames()的返回值:与配置文件吻合
Shiro CVE-2020-1957详细分析插图6
结果毫不意外的没有任何一个匹配:
Shiro CVE-2020-1957详细分析插图7
在shiro中,当前路径没有匹配到配置路径时默认策略是直接放行,所以这个末尾添加了/的恶意路径绕过了shiro的路径匹配。
但**为什么最终又能准确路由到对应的页面呢?**明明路径中多了一个/

那这就是spring-web的路径匹配问题了。[[2]](Apache Shiro权限绕过漏洞CVE-2020-1957-安全狗)

spring web的路径匹配
这一部分就不赘述了,根据引用[2],可知路径匹配入口方法:org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal

匹配方法还是将字符按/分割成数组后按元素匹配,如“/query" 被分割成["/", "query"]

核心代码:

...
...
// 当匹配了"query"这个词后,匹配下一个部分,所以自加后值为2
pathIndex++;   
//但是"query" 已经是pattern(/query) 的最后一部分了
if (isNoMorePattern()) {
    if (matchingContext.determineRemainingPath) {
       matchingContext.remainingPathIndex = pathIndex;
       return true;
    }
    else {
		//很“/query”与“/query/”明显长度不一致
       if (pathIndex == matchingContext.pathLength) {
          return true;
       }
       else {
		//matchingContext是当前要匹配的路径 “/query/”,长度为3 : ["/","query","/"]
		//如果选项允许,加上结尾是分割符则仍然匹配
          return (matchingContext.isMatchOptionalTrailingSeparator() &&
                (pathIndex + 1) == matchingContext.pathLength &&
                matchingContext.isSeparator(pathIndex));
       }
    }
}

选项:

Shiro CVE-2020-1957详细分析插图8

漏洞修复

该漏洞在shiro 1.5.0 时被修复

Shiro CVE-2020-1957详细分析插图9

修复逻辑非常简单,就是在匹配时将请求路径和配置路径末尾的分割符/都去掉

shiro<1.5.2

导致该漏洞的出现是shiro与spring对url的处理不一致导致的:

对象 url处理
shiro 将url中分号;以及后面部分全部去掉
spring 如果分号后还有 带/的路径,则仅去除分号到/之间的内容,保留其余部分 (不包括分号)

配置环境

shiro版本选择的是shiro 1.5.0

controller中加一个路由:

@GetMapping("/admin/info")
    public String adminInfo() {
        return "Admin Info";
    }

路径配置加一条:

@Bean
    ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager);
        bean.setLoginUrl("/login");
        bean.setSuccessUrl("/loginSuccess");
        bean.setUnauthorizedUrl("/unauthorized");
        LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
        //url --> filter1,filter2....
        map.put("/print", "authc, perms[printer:print]");
        map.put("/query", "authc, perms[printer:query]");
        map.put("/admin/info", "authc, roles[admin]");
        map.put("/login","authc");
        bean.setFilterChainDefinitionMap(map);
        return bean;
    }

漏洞测试

  1. 首先,重新登入一次:
    Shiro CVE-2020-1957详细分析插图10

  2. 然后正常访问路径:访问失败,重定向到未授权页面!Shiro CVE-2020-1957详细分析插图11

  3. 在路径中添加一个分号:访问成功,越权!!!
    Shiro CVE-2020-1957详细分析插图12

漏洞分析

shiro

入口还是org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain方法。定位到获取requestURI的代码
Shiro CVE-2020-1957详细分析插图13

跟进
Shiro CVE-2020-1957详细分析插图14
跟进标红函数

Shiro CVE-2020-1957详细分析插图15
跟进decodeAndCleanUriString:
Shiro CVE-2020-1957详细分析插图16
代码逻辑很简单,就是剥离分号后面的部分,因此当我们的请求是/admin;/info时,返回的是/admin,而这个路径是没有配置的,所以shirofilter会放行,交给DispatcherServlet (spring web)

spring
入口:org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal

Shiro CVE-2020-1957详细分析插图17

最后不断跟进initLookupPath(request)找到处理分号的函数:
Shiro CVE-2020-1957详细分析插图18

逻辑很简单,就是循环去除,分号和分割符之间的内容

测试了一下的确如此:
Shiro CVE-2020-1957详细分析插图19

漏洞修复

左边是shiro 1.5.0,右边版本是shiro 1.5.2,

Shiro CVE-2020-1957详细分析插图20

来看一下两边差异:
前者:

Shiro CVE-2020-1957详细分析插图21

后者:

Shiro CVE-2020-1957详细分析插图22

由图可知,request.getServletPath()对url中分号的处理与spring一致。
多的一个/被normalize函数去除:

Shiro CVE-2020-1957详细分析插图23

Reference

[1] Security Reports | Apache Shiro

[2] Apache Shiro权限绕过漏洞CVE-2020-1957-安全狗


4A评测 - 免责申明

本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。

不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。

本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。

如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!

程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。

侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)

相关文章

RequestShield:一款HTTP请求威胁识别与检测工具
2025年十大最佳漏洞管理工具分享
Redis漏洞利用与SSH免密反弹Shell
LIEF:用于解析和修改 ELF, PE 和MachO 格式的跨平台库
网络安全市场的“冬天”还长
终端变“矿场”,挖矿病毒借破解版软件无声“开矿”

发布评论