作者:jweny@360云安全
文章首發于安全客:https://www.anquanke.com/post/id/230935
0x01 漏洞描述
Apache Shiro是一個強大且易用的Java安全框架,執行身份驗證、授權、密碼和會話管理。使用Shiro的易于理解的API,您可以快速、輕松地獲得任何應用程序,從最小的移動應用程序到最大的網絡和企業應用程序。
當它和 Spring 結合使用時,在一定權限匹配規則下,攻擊者可通過構造特殊的 HTTP 請求包完成身份認證繞過。
影響范圍:Apache Shiro < 1.7.1
0x02 漏洞環境搭建
shiro 1.7.0
https://github.com/jweny/shiro-cve-2020-17523 兩種姿勢的漏洞環境均已更新。
0x03 poc測試
姿勢一:
http://127.0.0.1:8080/admin/%20 或 http://127.0.0.1:8080/admin/%20/
使用空格等空字符,可繞過shiro身份驗證。

姿勢二:
經過和p0desta師傅交流,發現還有另一種特殊場景下的利用方式。
http://127.0.0.1:8080/admin/%2e 或 http://127.0.0.1:8080/admin/%2e/
但是.(還有/)在Spring的路徑匹配的規則中是代表路徑分隔符的,不作為普通字符進行匹配。因此在默認條件下訪問 /admin/.會返回404。
但是在開啟全路徑的場景下setAlwaysUseFullPath(true)是可以正常匹配的。

0x04 漏洞分析
Shiro中對于URL的獲取及匹配在org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain
先簡單看下這個getChain方法:


該方法先檢查requestURI是否以/結尾,如果是,就刪掉最后一個/。
然后在匹配路徑的循環中,會先判斷下路徑規則pathPattern是否以/結尾,如果是也會刪除。然后再去調用pathMatches()方法進行路徑匹配。
因此兩種利用方式中,是否以/結尾都沒有關系,因為開始經過getChain方法就會被刪除。
4.1 空格繞過分析
關注下pathMatches()方法:
調出Evaluate,分別計算一下pathMatches("/admin/*","/admin/1")和pathMatches("/admin/*","/admin/ "),前者正常匹配,后者匹配失敗。


開始調試,調試開始會經過一陣漫長的F7。一直到doMatch("/admin/*","/admin/ ")。可見,tokenizeToStringArray返回的pathDirs已經沒有第二層路徑了。因此會導致/admin/*和/admin不匹配。

跟一下tokenizeToStringArray方法,發現其調用tokenizeToStringArray方法時的trimTokens參數為true。

而tokenizeToStringArray方法,在參數trimTokens為true時,會經過trim()處理,因此導致空格被清除。再次返回getChain時最后一個/被刪除。因此tokenizeToStringArray返回的pathDirs沒有第二層路徑。

總結一下:存在漏洞的shiro版本,由于調用tokenizeToStringArray方法時,trimTokens參數默認為true,空格會經過trim()處理,因此導致空格被清除。再次返回getChain時最后一個/被刪除,所以/admin與/admin/*匹配失敗,導致鑒權繞過。而Spring接受到的訪問路徑為/admin/%20,按照正常邏輯返回響應,因此導致權限被繞過。
4.2 /./繞過分析
看到第二種姿勢的/.和/./,是不是想起了某個熟悉方法?沒錯,就是normalize()。

簡單翻譯下就是:
| 條件 | 示例 |
|---|---|
| 正斜杠處理成反斜杠 | \ -> / |
| 雙反斜杠處理成反斜杠 | // -> / |
| 以/.或者/..結尾,則在結尾添加/ | /. -> /./ /.. -> /../ |
| 歸一化處理/./ | /./ -> / |
| 路徑跳躍 | /aaa/../bbb -> /bbb |
所以/admin/.在被處理成/admin/./之后變成了/admin/。

在經過org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain處理,由于/結尾,如果是,就刪掉最后一個/,變成了/admin。`/admin與/admin/*不匹配,因此繞過了shiro鑒權。

而此時Spring收到的請求為/admin/.。如果沒有開啟全路徑匹配的話,在Spring中.和/是作為路徑分隔符的,不參與路徑匹配。因此會匹配不到mapping,返回404。

開啟全路徑匹配的話,會匹配整個url,因此Spring返回200。
這里附上開啟全路徑匹配的代碼:
@SpringBootApplication
public class SpringbootShiroApplication extends SpringBootServletInitializer implements BeanPostProcessor {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(SpringbootShiroApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(SpringbootShiroApplication.class, args);
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof RequestMappingHandlerMapping) {
((RequestMappingHandlerMapping) bean).setAlwaysUseFullPath(true);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
}
0x05 官方的修復方案
經過以上的分析,造成shiro權限繞過的原因有兩個:
tokenizeToStringArray函數沒有正確處理空格。- 處理最后一個
/的邏輯,不應在循環匹配路徑的邏輯之前。
因此官方的修復方案為:
https://github.com/apache/shiro/commit/0842c27fa72d0da5de0c5723a66d402fe20903df
- 將
tokenizeToStringArray的trimTokens參數置為false。
- 調整刪除最后一個
/的邏輯。修改成先匹配原始路徑,匹配失敗后再去走刪除最后一個/的邏輯。
0x06 關于trim
原理上來說trim()會清空字符串前后所有的whitespace,空格只是其中的一種,但是在測試中發現除了空格以外的其他whitespace,例如%08、%09、%0a,spring+tomcat 處理時都會返回400。
因此第一種姿勢除了空格,尚未發現其他好用的payload。
0x07 參考
https://github.com/apache/shiro/commit/0842c27fa72d0da5de0c5723a66d402fe20903df
https://www.anquanke.com/post/id/216096
https://www.cnblogs.com/syp172654682/p/9257282.html
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1478/