taibeihacker
Moderator
0x00 漏洞描述
Apache Shiro 1.5.2之前版本中存在安全漏洞。攻擊者可藉助特製的請求利用該漏洞繞過身份驗證。 Shiro框架通過攔截器功能來對用戶訪問權限進行控制,如anon, authc等攔截器。 anon為匿名攔截器,不需要登錄即可訪問;authc為登錄攔截器,需要登錄才可以訪問。 Shiro的URL路徑表達式為Ant格式,路徑通配符*表示匹配零個或多個字符串,/*可以匹配/hello,但是匹配不到/hello/,因為*通配符無法匹配路徑。假設/hello接口設置了authc攔截器,訪問/hello會進行權限判斷,但如果訪問的是/hello/,那麼將無法正確匹配URL,直接放行,進入到spring攔截器。 spring中的/hello和/hello/形式的URL訪問的資源是一樣的,從而實現了權限繞過。0x01 漏洞影响
Apache Shiro 1.5.20x02 Shiro拦截器
Shiro框架通過攔截器功能來實現對用戶訪問權限的控制和攔截。 Shiro中常見的攔截器有anon,authc等攔截器。 1.anon為匿名攔截器,不需要登錄就能訪問,一般用於靜態資源,或者移動端接口2.authc為登錄攔截器,需要登錄認證才能訪問的資源。用戶可以在Shiro.ini編寫匹配URL配置,將會攔截匹配的URL,並執行響應的攔截器。從而實現對URL的訪問控制,URL路徑表達式通常為ANT格式。如下配置,訪問/index.html主頁的時候,Shiro將不會對其進行登錄判斷,anon攔截器不需要登錄就能進行訪問。而對於/user/xiaoming 等/user/xiaogang等接口,authc攔截器將會對其進行登錄判斷,有登錄認證才能訪問資源。 [urls]
/index.html=anon
/user/**=authc
Shiro的URL路徑表達式為Ant 格式,路徑通配符支持?***。匹配一個字符
*:匹配零個或多個字符串
**:匹配路徑中的零個或多個路徑
其中*表示匹配零個或多個字符串,/*可以匹配/hello,但匹配不到/hello/因為*通配符無法匹配路徑。假設/hello接口設置了authc攔截器,訪問/hello將會被進行權限判斷,如果請求的URI為/hello/呢,/*URL路徑表達式將無法正確匹配,放行。然後進入到spring(Servlet)攔截器,spring中/hello形式和/hello/形式的URL訪問的資源是一樣的。
0x03 环境搭建
下載demo代碼:https://github.com/lenve/javaboy-code-samples/tree/master/shiro/shiro-basic導入ideaShiro版本1.4.2 dependency groupIdorg.apache.shiro/groupId artifactIdshiro-web/artifactId version1.4.2/version /dependency dependency groupIdorg.apache.shiro/groupId artifactIdshiro-spring/artifactId version1.4.2/version /dependency修改ShiroConfig配置文件,添加authc攔截器的攔截正則@Bean ShiroFilterFactoryBean shiroFilterFactoryBean() { ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean(); //map.put('/*', 'authc'); map.put('/hello/*', 'authc'); bean.setFilterChainDefinitionMap(map); return bean; }修改路由控制器方法@GetMapping('/hello/{currentPage}') public String hello(@PathVariable Integer currentPage) { return 'hello';}通過idea進行編譯,可獲得war包這里通過docker快速搭建漏洞環境:docker pull vulfocus/shiro-cve_2020_1957



0x04 漏洞复现
1、Shiro1.4.2版本繞過權限訪問/hello/1接口,可以看到被authc攔截器攔截了,將會跳轉到登錄接口進行登錄。


protected boolean pathMatches(String pattern, String path) {
PatternMatcher pathMatcher=this.getPathMatcher();
return pathMatcher.matches(pattern, path);
}
doMatch:109, AntPathMatcher (org.apache.shiro.util),當Shiro 的Ant格式的pathPattern 中的的*通配符是不支持匹配路徑的,所以/hello/*不能成功匹配/hello/1/,也就不會觸發authc攔截器進行權限攔截。從而成功繞過了Shiro攔截器,而後再進入到spring攔截器中,/hello/1/與/hello/1能獲取到相同的資源。





String uri=(String)request.getAttribute('javax.servlet.include.request_uri');
if (uri==null) {
uri=request.getRequestURI();
}
return normalize(decodeAndCleanUriString(request, uri));
}
RequestUri函數中最終調用decodeAndCleanUriString函數對URI進行清洗。
private static String decodeAndCleanUriString(HttpServletRequest request, String uri) {
uri=decodeRequestString(request, uri);
int semicolonIndex=uri.indexOf(59);//獲取;號的位置
return semicolonIndex !=-1 ? uri.substring(0, semicolonIndex) : uri;
}
如果URI中存在;號的話,則會刪除其後面的所有字符。 /fdsf;/./hello/1/最終也就變成了/fdsf。

0x05 修复方案
1.升級1.5.2版本及以上在shiro1.5.2版本已加入的過濾器規則:@Test void testGetRequestUriWithServlet() { dotTestGetPathWithinApplicationFromRequest('/', '/servlet', '/foobar', '/servlet/foobar') dotTestGetPathWithinApplicationFromRequest('', '/servlet', '/foobar', '/servlet/foobar') dotTestGetPathWithinApplicationFromRequest('', 'servlet', '/foobar', '/servlet/foobar') dotTestGetPathWithinApplicationFromRequest('/', 'servlet', '/foobar', '/servlet/foobar') dotTestGetPathWithinApplicationFromRequest('//', 'servlet', '/foobar', '/servlet/foobar') dotTestGetPathWithinApplicationFromRequest('//', '//servlet', '//foobar', '/servlet/foobar') dotTestGetPathWithinApplicationFromRequest('/context-path', '/servlet', '/foobar', '/servlet/foobar') dotTestGetPathWithinApplicationFromRequest('//context-path', '//servlet', '//foobar', '/servlet/foobar') dotTestGetPathWithinApplicationFromRequest('//context-path', '/servlet', '/./servlet/other', '/servlet/other') dotTestGetPathWithinApplicationFromRequest('//context-path', '/asdf', '/./servlet/other', '/servlet/other') dotTestGetPathWithinApplicationFromRequest('//context-path', '/asdf', ';/./servlet/other', '/asdf') dotTestGetPathWithinApplicationFromRequest('/context%2525path', '/servlet', '/foobar', '/servlet/foobar') dotTestGetPathWithinApplicationFromRequest('/c%6Fntext%20path', '/servlet', '/foobar', '/servlet/foobar') dotTestGetPathWithinApplicationFromRequest('/context path', '/servlet', '/foobar', '/servlet/foobar') dotTestGetPathWithinApplicationFromRequest('', null, null, '/') dotTestGetPathWithinApplicationFromRequest('', 'index.jsp', null, '/index.jsp') }2.盡量避免使用*通配符作為動態路由攔截器的URL路徑表達式。