目录

CVE-2020-17523 Shiro权限绕过分析

前言

突发奇想,把一些 CVE 拿来分析学习一下,研究一下这些漏洞是怎么被发现出来的,提高自己的代码审计水平。首先是 Shiro 这个系列,所用到的代码为:Shiro-Vuln-Demo

漏洞复现

当访问/admin/{用户名}的时候,判断登录后才能访问内容,否则就会302跳转到/login

https://cdn.bingbingzi.cn/blog/20211213005114.png
/admin/test

以下为/login的页面内容,提示用户登录后才能访问

https://cdn.bingbingzi.cn/blog/20211213005038.png
/login

而如果访问/admin/%20则会绕过鉴权的过程,直接访问登录成功后的内容

https://cdn.bingbingzi.cn/blog/20211213005448.png
/admin/%20

漏洞分析

当用户访问某个地址的时候,会将请求发送到 Shiro 的过滤器中,如果该地址击中程序员设计好的地址,则会进行认证。也就是说如果绕过了对地址的判断,则不会有验证这个过程。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Bean
ShiroFilterFactoryBean shiroFilterFactoryBean(){
    ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
    bean.setSecurityManager(securityManager());
    bean.setLoginUrl("/login"); //设置登录失败的uri
    bean.setSuccessUrl("/index"); //设置登录成功的uri
    bean.setUnauthorizedUrl("/unauthorizedurl"); //设置授权失败的uri
    Map<String, String> map = new LinkedHashMap<>();
    map.put("/doLogin/", "anon");
    map.put("/admin/*", "authc");
    // anon 指定排除认证的uri,authc 指定需要认证的uri
    bean.setFilterChainDefinitionMap(map);
    return  bean;
}

阅读上面代码:

URI 功能
/login 登录失败的uri
/index 登录成功的uri
/unauthorizedurl 授权失败的uri
/doLogin/ 对此uri不做认证处理
/admin/* 对/admin/下所有请求都做认证处理

睡觉睡觉明天研究

没搞完睡不着,起床继续分析

睡觉睡觉,要猝死了呜呜呜呜呜呜

ShiroFilterFactoryBean实现了 SpringBoot 的 BeanPostProcessor 和 FactoryBean 这两个接口,先看 BeanPostProcessor 的实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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;
        applyGlobalPropertiesIfNecessary(filter);
        getFilters().put(beanName, filter);
    } else {
        log.trace("Ignoring non-Filter bean '{}'", beanName);
    }
    return bean;
}

然后再看看 BeanFactory 接口中的方法实现,这个接口目的是生成一个 Shiro 的过滤器,拦截每一个 request 请求

1
2
3
4
5
6
    public Object getObject() throws Exception {
        if (instance == null) {
            instance = createInstance();
        }
        return instance;
    }

跟进 createInstance

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
protected AbstractShiroFilter createInstance() throws Exception {
    log.debug("Creating Shiro Filter instance.");
    SecurityManager securityManager = getSecurityManager();
    if (securityManager == null) {
        String msg = "SecurityManager property must be set.";
        throw new BeanInitializationException(msg);
    }
    if (!(securityManager instanceof WebSecurityManager)) {
        String msg = "The security manager does not implement the WebSecurityManager interface.";
        throw new BeanInitializationException(msg);
    }
    FilterChainManager manager = createFilterChainManager();
    PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
    chainResolver.setFilterChainManager(manager);
    return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
}

首先是创建了一个 securityManager,然后应用 PathMatchingFilterChainResolver,跟进 PathMatchingFilterChainResolver 定位到 getChain 这个方法的关键位置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
String requestURI = this.getPathWithinApplication(request);
if (requestURI != null && !"/".equals(requestURI) && requestURI.endsWith("/")) {
    requestURI = requestURI.substring(0, requestURI.length() - 1);
}
Iterator var6 = filterChainManager.getChainNames().iterator();
String pathPattern;
do {
    if (!var6.hasNext()) {
        return null;
    }
    pathPattern = (String)var6.next();
    if (pathPattern != null && !"/".equals(pathPattern) && pathPattern.endsWith("/")) {
        pathPattern = pathPattern.substring(0, pathPattern.length() - 1);
    }
} while(!this.pathMatches(pathPattern, requestURI));
if (log.isTraceEnabled()) {
    log.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + Encode.forHtml(requestURI) + "].  Utilizing corresponding filter chain...");
}
return filterChainManager.proxy(originalChain, pathPattern);

这就是 request 过滤的具体实现方法,这里面调用了 pathMatches 方法,如果返回 true 则进行认证,如果返回 false 则通行。这边对 !this.pathMatches(pathPattern, requestURI下断点,进行调试。

https://cdn.bingbingzi.cn/blog/20211213102322.png
Picture

当对 SpingBoot 网站发起请求的时候,进行 pathMatches 判断

https://cdn.bingbingzi.cn/blog/20211213102548.png
Picture

访问一个需要认证的 URI 看看在判断认证的过程中发生了什么 GET /admin/test,漏洞的产生原因在于这个 token.trim(),它会将请求 URI 的空格删除掉

https://cdn.bingbingzi.cn/blog/20211213103008.png
Picture

这就会带来一个问题,加入传入/admin/%20,经过token.trim()操作后,会变成 /admin/

https://cdn.bingbingzi.cn/blog/20211213103651.png
Picture

如图所示,变成了访问/admin/,在返回getChain的时候会将末尾的/去掉,此时 Shiro 就会判断不在过滤列表中,认为此地址不需要进行认证,不会进行认证操作则直接返回认证成功的页面。

参考

  1. https://github.com/xhycccc/Shiro-Vuln-Demo
  2. https://blog.csdn.net/u013995395/article/details/90715272
  3. https://zhuanlan.zhihu.com/p/357214438