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.2
0x02 Shiro攔截器
Shiro框架通過攔截器功能來實現對用戶訪問權限的控制和攔截。Shiro中常見的攔截器有anon,authc等攔截器。
1.anon為匿名攔截器,不需要登錄就能訪問,一般用於靜態資源,或者移動端接口
2.authc為登錄攔截器,需要登錄認證才能訪問的資源。
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
導入idea
Shiro版本1.4.2
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.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

docker images

docker run -d -p 8080:8080 -v /var/run/docker.sock:/var/run/docker.sock -e VUL_IP=192.168.1.14 29136b1d3c61

-v /var/run/docker.sock:/var/run/docker.sock 為 docker 交互連接。
-e DOCKER_URL 為 Docker 連接方式,默認通過 unix://var/run/docker.sock 進行連接,也可以通過 tcp://xxx.xxx.xxx.xxx:2375 進行連接(必須開放 2375 端口)。
-v /vulfocus-api/db.sqlite3:db.sqlite3 映射數據庫為本地文件。
-e VUL_IP=xxx.xxx.xxx.xxx 為 Docker 服務器 IP ,不能為 127.0.0.1。
http://192.168.1.14:8080/login

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

訪問/hello/1/,成功繞過authc攔截器,獲取到了資源。

2、Shiro1.4.2版本繞過漏洞分析
漏洞初始成因可以定位到 PathMatchingFilterChainResolver的getChain函數下,該函數作用根據URL路徑匹配中配置的url路徑表達式來匹配輸入的URL,判斷是否匹配攔截器,匹配成功將會返回響應的攔截器執行鏈,讓ShiroFither執行權限操作的。
其對於URL路徑表達式和輸入URL的匹配主要通過pathMathches函數進行匹配。

pathMatches函數其最終會調用shiro.util.AntPathMatcher類中doMatch的對於ant格式的pathPattern和requestURI進行匹配。
//pathMatches:135, PathMatchingFilterChainResolver (org.apache.shiro.web.filter.mgt) 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
能獲取到相同的資源。

3、Shiro≤1.5.1版本繞過
在1.5.1版本中,/hello/會直接跳轉到登錄

繞過payload,
/fdsf;/../hello/1
,成功繞過。

或者其他payload,xxxx/..;
/hello/1
,成功繞過(shiro的1.5.1及其之前的版本)

其中以上處理過程:
- 客戶端請求URL: /xxxx/..;/hello/1
- shrio 內部處理得到校驗URL為 /xxxx/..,校驗通過
- springboot 處理 /xxxx/..;/hello/1 , 最終請求 /hello, 成功訪問了后台請求.
4.Shiro≤1.5.1版本漏洞分析
問題同樣可以定位到getChain函數中對於requestURI的獲取中,如下圖所示,
this.getPathWithinApplication(request)
獲取的requestURI為
/fdsf
,而不是我們輸入的
/fdsf;/../hello/1
,從而導致后面的URI路徑模式匹配返回False,從而再次繞過了shiro攔截器。

getPathWithinApplication函數中會調用WebUtils (org.apache.shiro.web.util)中的getRequestUri函數獲取RequestUri。
public static String getRequestUri(HttpServletRequest request) {
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
。

5.漏洞
總結
在web容器中,Shiro的攔截器是先與spring(Servlet)執行,兩者攔截器對於URI模式匹配的差異,導致Shiro攔截器的繞過,而Shiro對其進行了兩次修復,其第一次在shiro1.4.2版本出現漏洞后為刪除requestURI后面的
/
號進行URL路徑匹配,算是簡單的修復了添加
/
號繞過的方式,而后在1.5.2版本中通過requestURI自主拼接的方式修復了
/fdsf;/../hello/1/
等使用了;號方式的繞過
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路徑表達式。
0x06 參考文獻
https://paper.seebug.org/1196/
https://xz.aliyun.com/t/8281