filter設計缺陷導致的權限繞過

  • 1.1 權限控制的本質

一般來說,爲了防止越權操作,通常會結合filter進⾏相關接⼝的鑑權操作。其中不不外乎就是對每⼀個接口(通俗來說就是我們的URI/URL)進行業務梳理,然後判斷當前URI/URL是否具有相應的業務權限。

  • 1.2 常見權限控制的實現

一般情況下,通常是獲取到當前URI/URL,然後跟需要鑑權的接口進行⽐對,或者直接結合startsWith()或者endsWith()方法,設置對應的校驗名單。

例如下⾯的過濾器實現,以/login開頭的不需要校驗(登陸業務每個人都可以訪問),所有.do/.action結尾的接⼝均需要做登陸檢查,防止未授權訪問等。

String uri = request.getRequestURI();
if(uri.endsWith(".do")||uri.endsWith(".action")) {
//檢測當前用戶是否登陸
User user =(User) request.getSession().getAttribute("user");
if(user==null|| "".equals(user)) {
errorResponse(response, paramN, "未授權訪問");
return;
}
}

但是,在Java中獲取當前request中的URI/URL通常會使用request.getRequestURL()和request.getRequestURI()這兩個方法,但是如果沒有進⾏相關的處理的話,有可能導致權限控制繞過的風險。

  • 1.3 繞過方式

當權限過濾器獲取當前request中的URI/URL使用request.getRequestURL()和request.getRequestURI()這兩個方法時,可以考慮以下三種⽅式進行權限繞過:

  • 1.3.1 非標準化繞過

  • 相關場景:

例如/system/login開頭的接口是白名單,不需要進行訪問控制(登陸頁面所有人都可以訪問),其他接⼝都需要進⾏登陸檢查,防止未授權訪問:

String uri = request.getRequestURI();
if(uri.startsWith("/system/login")) {
//登陸接口設置⽩白名單
filterChain.doFilter(request, response);
}
else if(uri.endsWith(".do")||uri.endsWith(".action")) {
//檢測當前⽤戶是否登陸
User user =(User) request.getSession().getAttribute("user");
if(user==null|| "".equals(user)) {
errorResponse(response, paramN, "未授權訪問");
return;
}
}
  • 相關效果如下:

當未登錄直接訪問UserInfoSearch.do接口時,顯示未授權訪問:

  • 相關原理:

中間件在進⾏解析時,會對我們URI中的../進行相關處理從⽽得到相關的servlet。也就是說嘗試對我們訪問的URL引入../,中間件是可以正常解析並完成正常業務的,以tomcat中的examples目錄中的案例servlet訪問爲例,嘗試訪問一個不存在的目錄login,然後通過../回到正常目錄下,正常解析:

  • 繞過分析:

使用request.getRequestURL()和request.getRequestURI()這兩個方法進⾏訪問接口的獲取時,是不會對類似../等進⾏規範化處理的,也就是說剛剛我們訪問的/system/login/../UserInfoSearch.do際獲取到的URI爲:

同樣是前⾯的例子,那麼我們可以通過在URI中寫⼊/login/../,使得權限過濾器認爲我們當前訪問的接⼝爲白名單接口,從而繞過權限控制,使得系統認爲我們當前訪問的接⼝是登陸login,不需要進行權限校驗:

  • 繞過方法:

在URI引⼊入類似/login(白名單接口,可以通過測試得出,一般登陸都是不需要權限校驗的)/../的
樣式,僞造⽩名單接口。
  • 1.3.2 URL截斷繞過

  • 相關場景:

例如/system/login開頭的接⼝是⽩名單,不需要進行訪問控制(登陸⻚面所有人都可以訪問),其他接口都需要進行登陸檢查,防止未授權訪問,但是考慮到了../的非法訪問問題:

String uri = request.getRequestURI();
if(uri.contains("./")){
errorResponse(response, paramN, "⾮非法訪問");
return;
}
else if(uri.startsWith("/system/login")) {
//登陸接口設置⽩白名單
filterChain.doFilter(request, response);
}
else if(uri.endsWith(".do")||uri.endsWith(".action")) {
//檢測當前用戶是否登陸
User user =(User) request.getSession().getAttribute("user");
if(user==null|| "".equals(user)) {
errorResponse(response, paramN, "未授權訪問");
return;
}
}
  • 相關效果如下:

當未登錄直接訪問UserInfoSearch.do接⼝時,顯示未授權訪問:

嘗試結合../僞造白名單,失敗:

  • 相關原理:

URL中有一個保留字符分號(;),主要作爲參數分隔符進行使用,有時候是請求中傳遞的參數太多了,所以使用分號(;)將參數對(key=value)連接起來作爲一個請求參數進⾏傳遞。

直接在URI中引入分隔符,正常來說是不會對實際接口的訪問造成影響的。

  • 繞過分析:

對於request.getRequestURL()和request.getRequestURI()來說,使用&連接的參數鍵值對,其是獲取不到的,但是參數分隔符(;)及內容是可以獲取到的:

同樣是前面的例子,訪問.do結尾的接口需要進行登陸檢查,否則認爲未授權訪問,那麼此時可以利用分隔符,繞過endsWith()檢測,使得權限過濾器認爲我們訪問的接口不是業務接口,從而達到繞過權限控制的效果:

  • 繞過⽅法:

在URI引⼊入參數分隔符;,進⾏切割URI繞過限制,例例
如/system;Bypass/UserSearch.do;Bypass
  • 1.3.3 URL編碼繞過

  • 相關場景:

例如/system/UserInfoSearch.do接⼝是管理員才能訪問的接口,需要進⾏⽤戶檢查,防止越權訪問:

if(uri.equals("/system/UserInfoSearch.do")){
User user =(User) request.getSession().getAttribute("user");
String role = user.getRole();
if(role.equals("admin")) {
//當前⽤用戶爲admin,允許訪問該接⼝
filterChain.doFilter(request, response);
}
else {
errorResponse(response, paramN, "越權訪問");
return;
}
}
  • 相關效果如下:

若不是admin用戶登陸,拒絕訪問UserInfoSearch.do接口:

否則返回當前系統存在的用戶名:

  • 相關原理:

當filter處理完相關的流程後,中間件會對請求的URL進行一次URL解碼操作,然後再找到對應的Servlet進行訪問。

也就是說嘗試對我們訪問的URL進行一次URL編碼,中間件是可以正常解析並完成正常業務的,以tomcat中的examples⽬目錄中的案例servlet訪問爲例,嘗試將HelloWorldExample進行URL編碼再進行訪問,正常解析:

  • 繞過分析:

除了前面介紹的兩種方式以外,這里存在一個問題,使用request.getRequestURL()和request.getRequestURI()這兩個⽅法進⾏行行訪問接口的獲取時,是不會進行URL解碼操作的,也就是說剛剛我們訪問的

/system/%55%73%65%72%49%6e%66%6f%53%65%61%72%63%68%2e%64%6f實際獲取到的URI爲:

那麼我們可以通過對URI進行URL編碼,此時filter中得到的uri並不是正常的/system/UserInfoSearch.do,⽽是編碼後的,但是filter轉發請求後瀏覽器可以解碼並正常解析,從而達到以低權限用戶繞過權限控制訪問管理員接口的效果:

  • 繞過⽅法:

對URI進行URL編碼/多重URL編碼,嘗試繞過。
  • 1.3.4 Spring Web的動態Controller追加/繞過

  • 相關場景:

一般情況下,通常是獲取到當前URI/URL,然後跟需要鑑權的接⼝進行比對,結合endsWith()方法,設置對應的校驗名單。

例如下面的過濾器實現,所有.do、.action結尾的接口均需要做登陸檢查,防⽌止未授權訪問等。

String uri = request.getServletPath()+(request.getPathInfo() == null ?
"" : request.getPathInfo());
if(uri.endsWith(".do")||uri.endsWith(".action")) {
//檢測當前用戶是否登陸
User user =(User) request.getSession().getAttribute("user");
if(user==null|| "".equals(user)) {
errorResponse(response, paramN, "未授權訪問");
return;
}
}
  • 相關原理:

特定情況下,Spring web在匹配url接⼝的時候會容錯後⾯額外的/。

以Spring MVC爲例例,如下配置的話,在實際接口訪問的時候會容錯後⾯額外的/:

<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

例如以下兩種訪問方式效果都是一樣的:

/admin/info.do?param=value
/admin/info.do/?param=value
  • 繞過分析:

考慮到上⾯使用request.getRequestURL()和request.getRequestURI()這兩個⽅方法進行訪問接⼝的獲取時存在的安全隱患,這里使用 request.getServletPath()+(request.getPathInfo() == null ? "" :

request.getPathInfo())進行路徑的獲取,但是如果在接口URI後追加額外的/,還是可以獲取到的:

 根據上⾯的filter代碼,正常情況下非登陸訪問/admin/info.do,會提示未授權訪問:

嘗試在接口後追加額外的/,成功繞過權限校驗:

  • 繞過方法:

嘗試在對應的URL接口後加入/,以繞過權限控制。

  • 1.4 修復建議及防禦方式

  •   以Java爲例:

1.使用如下⽅法進⾏相關路路徑的獲取:

request.getServletPath()+(request.getPathInfo() == null ? "" :
request.getPathInfo());

2.對相關的接口訪問進行標準化處理,剔除不相關的元素,例如../,分隔符(;)後的內容以及接⼝後不必要的/等;

3.使用ESAPI的canonicalize方法進行規範化處理:

ESAPI.encoder().canonicalize(URI)

同時在對應的配置⽂文件ESAPI.properties禁⽤用雙重uri編碼(默認開啓):

Encoder.AllowMultipleEncoding=false

具體效果如下,當接收到雙重url編碼時,會觸發報錯:

4.儘量不要使用類似startsWith()、endsWith()進⾏判斷。

5.使用成熟的權限控制框架進行權限校驗。(Spring Security、Shiro等)

  • 1.5 思維導圖

  • 相關實操練習:Springboot未授權訪問 ——Actuator 是 springboot 提供的用來對應用系統進行自省和監控的功能模塊,非法用戶可通過訪問默認的執行器端點(endpoints)來獲取應用系統中的監控信息從而導致信息泄露的事件發生。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章