聲明
由于傳播、利用此文所提供的信息而造成的任何直接或者間接的后果及損失,均由使用者本人負責,雷神眾測以及文章作者不為此承擔任何責任。
雷神眾測擁有對此文章的修改和解釋權。如欲轉載或傳播此文章,必須保證此文章的完整性,包括版權聲明等全部內容。未經雷神眾測允許,不得任意修改或者增減此文章內容,不得以任何方式將其用于商業目的。
No.1
簡述
前段時間太忙了,忙到很多東西,只是記錄了筆記,沒有成文,剛好最近階段又出來了shiro權限繞過漏洞,因此本文將這三個權限繞過的洞進行對比,他們的編號分別是 CVE-2020-1957、CVE-2020-11989、CVE-2020-13933 。
No.2
漏洞細節
1、CVE-2020-1957
原理

首先在 admin 位置下斷點,可以看到,我們網絡請求,是先經過shiro 處理之后,再轉發到springboot進行路由分發工作。

這里直接定位到 shiro處理url的方法位置:WebUtils# getPathWithinApplication
public static String getPathWithinApplication(HttpServletRequest request) {
String contextPath = getContextPath(request);
String requestUri = getRequestUri(request);
if (StringUtils.startsWithIgnoreCase(requestUri, contextPath)) {
// Normal case: URI contains context path.
String path = requestUri.substring(contextPath.length);
return (StringUtils.hasText(path) ? path : "/");
} else {
// Special case: rather unusual.
return requestUri;
}
}
實際上繼續跟進 getRequestUri(request);這個方法,可以清楚的看到,實際上調用的是getRequestURI 方法來獲取路由中的URI 請求。

這里的 URI就是我們傳入的/xxx/..;/hello/aaaa,也就是說回到 getRequestUri(request);當中,會帶著這個傳入的URI進入decodeAndCleanUriString 進行處理。
public static String getRequestUri(HttpServletRequest request) {
String uri = (String) request.getAttribute(INCLUDE_REQUEST_URI_ATTRIBUTE);
if (uri == ) {
uri = request.getRequestURI;
}
return normalize(decodeAndCleanUriString(request, uri));
}
在 decodeAndCleanUriString方法中會根據我們的傳入的URI中;進行截斷處理,也就是說經過處理之后,返回的結果變成了/xxx/..

而 normalize 方法就會對我們傳入的path進行一些處理,從注釋上,也能知道這部分代碼處理了什么東西:
?替換\為/
?替換/./為/
?替換/../為/
?...
private static String normalize(String path, boolean replaceBackSlash) {
if (path == )
return ;
// Create a place for the normalized path
String normalized = path;
if (replaceBackSlash && normalized.indexOf('\') >= 0)
normalized = normalized.replace('\', '/');
if (normalized.equals("/."))
return "/";
// Add a leading "/" if necessary
if (!normalized.startsWith("/"))
normalized = "/" + normalized;
// Resolve occurrences of "//" in the normalized path
while (true) {
int index = normalized.indexOf("//");
if (index < 0)
break;
normalized = normalized.substring(0, index) +
normalized.substring(index + 1);
}
// Resolve occurrences of "/./" in the normalized path
while (true) {
int index = normalized.indexOf("/./");
if (index < 0)
break;
normalized = normalized.substring(0, index) +
normalized.substring(index + 2);
}
// Resolve occurrences of "/../" in the normalized path
while (true) {
int index = normalized.indexOf("/../");
if (index < 0)
break;
if (index == 0)
return ; // Trying to go outside our context
int index2 = normalized.lastIndexOf('/', index - 1);
normalized = normalized.substring(0, index2) +
normalized.substring(index + 3);
}
// Return the normalized path that we have completed
return (normalized);
}
而這里經過處理,我們的 URI 依然是/xxx/..,接著就會回到PathMatchingFilterChainResolver# getChain方法,進行權限匹配,我們的路徑是/hello/**下需要進行權限認證,由于路徑不匹配,所以權限校驗自然過了。

這里在提一嘴,可以看看 PathMatchingFilterChainResolver# getChain方法這一小段代碼,這一段代碼修復的是 Shiro-682 (https://issues.Apache.org/jira/browse/SHIRO-682),具體描述可以點入鏈接查看。簡單翻譯一下就是在spring web下,通過請求 /resource/menus和/resource/menus/都是能夠訪問到資源的,但是shiro的路徑正則只會匹配到/resource/menus,忽略了 /resource/menus/ ,所以這就繞過了。


好了,這里提一下這個地方,再回到我們剛剛上面的情況里,由于我們傳入的 URI/xxx/..與權限認證的URI/hello/**不匹配,繞過了權限驗證之后,進入 springboot當中進行路由分發,而在spring當中UrlPathHelper# getPathWithinServletMapping 這個方法負責處理我們傳入的URI:xxx/..;/hello/aaaa,結果是返回servletPath。
public String getPathWithinServletMapping(HttpServletRequest request) {
String pathWithinApp = getPathWithinApplication(request);
String servletPath = getServletPath(request);
String sanitizedPathWithinApp = getSanitizedPath(pathWithinApp);
String path;
// If the app container sanitized the servletPath, check against the sanitized version
if (servletPath.contains(sanitizedPathWithinApp)) {
path = getRemainingPath(sanitizedPathWithinApp, servletPath, false);
}
else {
path = getRemainingPath(pathWithinApp, servletPath, false);
}
...
// Otherwise, use the full servlet path.
return servletPath;
}
}
看看 servletPath是怎么來的,這玩意的取值是通過request.getServletPath;獲取到的,也就是說這里的結果是/hello/aaaa。這里通過springboot進行分發,自然獲取到后臺接口內容,整個流程:
用戶發起請求/xxx/..;/hello/aaaa----->shiro處理之后返回/xxx/..通過校驗的----->springboot處理/xxx/..;/hello/aaaa返回/hello/aaaa,最后訪問到需要權限校驗的資源。
public String getServletPath(HttpServletRequest request) {
String servletPath = (String) request.getAttribute(WebUtils.INCLUDE_SERVLET_PATH_ATTRIBUTE);
if (servletPath == ) {
servletPath = request.getServletPath;
}
if (servletPath.length > 1 && servletPath.endsWith("/") && shouldRemoveTrailingServletPathSlash(request)) {
// On WebSphere, in non-compliant mode, for a "/foo/" case that would be "/foo"
// on all other servlet containers: removing trailing slash, proceeding with
// that remaining slash as final lookup path...
servletPath = servletPath.substring(0, servletPath.length - 1);
}
return servletPath;
}

修復
shiro在1.5.2當中把之前的通過 getRequestURI獲取URI的方式變成了getContextPath 、getServletPath 、getPathInfo 的組合。

這么處理之后自然變成了想要的東西。

2、CVE-2020-11989
原理
這里的 shiro攔截器需要變成map.put("/hello/*", "authc");,這里有兩種poc,都是可以繞過
/hello/a%25%32%66a
/;/test/hello/aaa
我們知道在shiro中的WebUtils# getPathWithinApplication這里會處理我們傳入的url,在 getRequestUri方法會調用decodeAndCleanUriString進行處理。
public static String getRequestUri(HttpServletRequest request) {
String uri = (String) request.getAttribute(INCLUDE_REQUEST_URI_ATTRIBUTE);
if (uri == ) {
uri = valueOrEmpty(request.getContextPath) + "/" +valueOrEmpty(request.getServletPath) +valueOrEmpty(request.getPathInfo);
}
return normalize(decodeAndCleanUriString(request, uri));
}
在 decodeAndCleanUriString當中會調用decodeRequestString針對URI進行一次URL解碼。
private static String decodeAndCleanUriString(HttpServletRequest request, String uri) {
uri = decodeRequestString(request, uri);
int semicolonIndex = uri.indexOf(';');
return (semicolonIndex != -1 ? uri.substring(0, semicolonIndex) : uri);
}
public static String decodeRequestString(HttpServletRequest request, String source) {
String enc = determineEncoding(request);
try {
return URLDecoder.decode(source, enc);
} catch (UnsupportedEncodingException ex) {
if (log.isWarnEnabled) {
...
}
return URLDecoder.decode(source);
}
}
所以這里的poc/hello/a%25%32%66a------>傳入到shiro自動解碼一次變成//hello/a%2fa------>經過 decodeRequestString 變成//hello/a/a由于這里我們的攔截器是map.put("/hello/*", "authc");,這里需要了解一下shiro的URL是ant格式,路徑是支持通配符表示的
?:匹配一個字符
*:匹配零個或多個字符串
**:匹配路徑中的零個或多個路徑
/*只能命中/hello/aaa這種格式,無法命中/hello/a/a,所以經過 shiro 進行權限判斷的時候自然無法命中。

而在spring當中,理解的 servletPath是/hello/a%2fa,所以自然命中@GetMapping("/hello/{name}")這個mapping,又springboot轉發到響應的路由當中。

另一種利用方式來自這里《Apache Shiro權限繞過漏洞分析(CVE-2020-11989)》(https://xz.aliyun.com/t/7964),這里提到了
1.應用不能部署在根目錄,也就是需要context-path,server.servlet.context-path=/test,如果為根目錄則context-path為空,就會被CVE-2020-1957的patch將URL格式化,值得注意的是若Shiro版本小于1.5.2的話那么該條件就不需要。

這里原因在于需要繞過 getRequestUri當中的格式化uri,當context-path為空的時候,處理結果為//hello/aaaa
public static String getRequestUri(HttpServletRequest request) {
String uri = (String) request.getAttribute(INCLUDE_REQUEST_URI_ATTRIBUTE);
if (uri == ) {
uri = valueOrEmpty(request.getContextPath) + "/" +
valueOrEmpty(request.getServletPath) +
valueOrEmpty(request.getPathInfo);
}
return normalize(decodeAndCleanUriString(request, uri));
}

當 context-path不為空的時候,處理結果為/;/test/hello/aaaa,然后我們知道decodeAndCleanUriString會根據;進行截斷,截斷之后的結果是/自然無法命中攔截器map.put("/hello/*", "authc");,所以自然就繞過了。


修復
在1.5.3版本,采用標準的 getServletPath和getPathInfo進行uri處理,同時取消了url解碼。
public static String getPathWithinApplication(HttpServletRequest request) {
return normalize(removeSemicolon(getServletPath(request) + getPathInfo(request)));
}

3、CVE-2020-13933
原理
/hello/%3baaaa
上面的代碼進來之后,通過 getPathWithinApplication處理之后變成了/hello/;aaaa

而 removeSemicolon會根據;進行截斷,返回的uri自然是/hello/
private static String removeSemicolon(String uri) {
int semicolonIndex = uri.indexOf(';');
return (semicolonIndex != -1 ? uri.substring(0, semicolonIndex) : uri);
}
這個 uri自然無法命中攔截器map.put("/hello/*", "authc");自然就過了

修復
加了一個 filter類InvalidRequestFilter來針對一些東西進行處理。
private static final List<String> SEMICOLON = Collections.unmodifiableList(Arrays.asList(";", "%3b", "%3B"));
private static final List<String> BACKSLASH = Collections.unmodifiableList(Arrays.asList("\", "%5c", "%5C"));

No.3
小結
總結來看,就是利用 shiro 解析uri 和spring解析uri之間的差異來挖這個洞。
招聘啟事
安恒雷神眾測SRC運營(實習生)
————————
【職責描述】
1. 負責SRC的微博、微信公眾號等線上新媒體的運營工作,保持用戶活躍度,提高站點訪問量;
2. 負責白帽子提交漏洞的漏洞審核、Rank評級、漏洞修復處理等相關溝通工作,促進審核人員與白帽子之間友好協作溝通;
3. 參與策劃、組織和落實針對白帽子的線下活動,如沙龍、發布會、技術交流論壇等;
4. 積極參與雷神眾測的品牌推廣工作,協助技術人員輸出優質的技術文章;
5. 積極參與公司媒體、行業內相關媒體及其他市場資源的工作溝通工作。
【任職要求】
1. 責任心強,性格活潑,具備良好的人際交往能力;
2. 對網絡安全感興趣,對行業有基本了解;
3. 良好的文案寫作能力和活動組織協調能力。
簡歷投遞至 [email protected]
設計師(實習生)
————————
【職位描述】
負責設計公司日常宣傳圖片、軟文等與設計相關工作,負責產品品牌設計。
【職位要求】
1、從事平面設計相關工作1年以上,熟悉印刷工藝;具有敏銳的觀察力及審美能力,及優異的創意設計能力;有 VI 設計、廣告設計、畫冊設計等專長;
2、有良好的美術功底,審美能力和創意,色彩感強;精通Photoshop/illustrator/coreldrew/等設計制作軟件;
3、有品牌傳播、產品設計或新媒體視覺工作經歷;
【關于崗位的其他信息】
企業名稱:杭州安恒信息技術股份有限公司
辦公地點:杭州市濱江區安恒大廈19樓
學歷要求:本科及以上
工作年限:1年及以上,條件優秀者可放寬
簡歷投遞至 [email protected]
安全招聘
————————
公司:安恒信息
崗位:Web安全 安全研究員
部門:戰略支援部
薪資:13-30K
工作年限:1年+
工作地點:杭州(總部)、廣州、成都、上海、北京
工作環境:一座大廈,健身場所,醫師,帥哥,美女,高級食堂…
【崗位職責】
1.定期面向部門、全公司技術分享;
2.前沿攻防技術研究、跟蹤國內外安全領域的安全動態、漏洞披露并落地沉淀;
3.負責完成部門滲透測試、紅藍對抗業務;
4.負責自動化平臺建設
5.負責針對常見WAF產品規則進行測試并落地bypass方案
【崗位要求】
1.至少1年安全領域工作經驗;
2.熟悉HTTP協議相關技術
3.擁有大型產品、CMS、廠商漏洞挖掘案例;
4.熟練掌握php、JAVA、asp.net代碼審計基礎(一種或多種)
5.精通Web Fuzz模糊測試漏洞挖掘技術
6.精通OWASP TOP 10安全漏洞原理并熟悉漏洞利用方法
7.有過獨立分析漏洞的經驗,熟悉各種Web調試技巧
8.熟悉常見編程語言中的至少一種(Asp.net、Python、php、java)
【加分項】
1.具備良好的英語文檔閱讀能力;
2.曾參加過技術沙龍擔任嘉賓進行技術分享;
3.具有CISSP、CISA、cssLP、ISO27001、ITIL、PMP、COBIT、Security+、CISP、OSCP等安全相關資質者;
4.具有大型SRC漏洞提交經驗、獲得年度表彰、大型CTF奪得名次者;
5.開發過安全相關的開源項目;
6.具備良好的人際溝通、協調能力、分析和解決問題的能力者優先;
7.個人技術博客;
8.在優質社區投稿過文章;
崗位:安全紅隊武器自動化工程師
薪資:13-30K
工作年限:2年+
工作地點:杭州(總部)
【崗位職責】
1.負責紅藍對抗中的武器化落地與研究;
2.平臺化建設;
3.安全研究落地。
【崗位要求】
1.熟練使用Python、java、c/c++等至少一門語言作為主要開發語言;
2.熟練使用Django、flask 等常用web開發框架、以及熟練使用MySQL、mongoDB、redis等數據存儲方案;
3:熟悉域安全以及內網橫向滲透、常見web等漏洞原理;
4.對安全技術有濃厚的興趣及熱情,有主觀研究和學習的動力;
5.具備正向價值觀、良好的團隊協作能力和較強的問題解決能力,善于溝通、樂于分享。
【加分項】
1.有高并發tcp服務、分布式等相關經驗者優先;
2.在github上有開源安全產品優先;
3:有過安全開發經驗、獨自分析過相關開源安全工具、以及參與開發過相關后滲透框架等優先;
4.在freebuf、安全客、先知等安全平臺分享過相關技術文章優先;
5.具備良好的英語文檔閱讀能力。
簡歷投遞至 [email protected]
崗位:紅隊武器化Golang開發工程師
薪資:13-30K
工作年限:2年+
工作地點:杭州(總部)
【崗位職責】
1.負責紅藍對抗中的武器化落地與研究;
2.平臺化建設;
3.安全研究落地。
【崗位要求】
1.掌握C/C++/Java/Go/Python/JavaScript等至少一門語言作為主要開發語言;
2.熟練使用Gin、Beego、Echo等常用web開發框架、熟悉MySQL、Redis、MongoDB等主流數據庫結構的設計,有獨立部署調優經驗;
3.了解Docker,能進行簡單的項目部署;
3.熟悉常見web漏洞原理,并能寫出對應的利用工具;
4.熟悉TCP/IP協議的基本運作原理;
5.對安全技術與開發技術有濃厚的興趣及熱情,有主觀研究和學習的動力,具備正向價值觀、良好的團隊協作能力和較強的問題解決能力,善于溝通、樂于分享。
【加分項】
1.有高并發tcp服務、分布式、消息隊列等相關經驗者優先;
2.在github上有開源安全產品優先;
3:有過安全開發經驗、獨自分析過相關開源安全工具、以及參與開發過相關后滲透框架等優先;
4.在freebuf、安全客、先知等安全平臺分享過相關技術文章優先;
5.具備良好的英語文檔閱讀能力。
簡歷投遞至 [email protected]