CVE-2020-17523 Shiro权限绕过分析
把一些 CVE 拿来分析学习一下,研究一下这些漏洞是怎么被发现出来的,提高自己的代码审计水平。首先是 Shiro 这个系列,所用到的代码为:Shiro-Vuln-Demo。
漏洞复现
当访问/admin/{用户名}
的时候,判断登录后才能访问内容,否则就会302跳转到/login

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

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

漏洞分析
当用户访问某个地址的时候,会将请求发送到 Shiro 的过滤器中,如果该地址击中程序员设计好的地址,则会进行认证。也就是说如果绕过了对地址的判断,则不会有验证这个过程。
@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 的实现:
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 请求
public Object getObject() throws Exception {
if (instance == null) {
instance = createInstance();
}
return instance;
}
跟进 createInstance
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 这个方法的关键位置
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
下断点,进行调试。

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

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

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

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