我們有時候會在Filter中對某些URL進行權限校驗。若使用getRequestURI與getRequestURL方法獲取URL,可能會導致權限繞過的風險。
漏洞原理:請求URL中可以填充一些特殊字符,來跳過權限校驗。
1. 測試代碼
權限驗證器代碼:
@Slf4j
@Service
public class URIRiskInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//校驗數據
String requestURI = request.getRequestURI();
String requestURL = request.getRequestURL().toString();
String servletPath = request.getServletPath();
log.info("輸出數據requestURI:{}\n,requestURL:{}\n,servletPath:{}\n", requestURI, requestURL, servletPath);
return true;
}
}
Controller層代碼:
@Slf4j
@RestController
public class FirstController {
@RequestMapping(value = "/test")
public String test(@RequestParam("id") Long id, @RequestParam("name") String name) {
log.info("test,請求進來了");
return id + "-" + name + " is success";
}
}
2. 繞過方式
2.1 非標準化繞過
例如/system/login開頭的接口是白名單,不需要進行訪問控制(登陸頁面所有人都可以訪問),其他接⼝都需要進⾏登陸檢查,防止未授權訪問:
@Slf4j
@Service
public class URIRiskInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//校驗數據
String requestURI = request.getRequestURI();
String requestURL = request.getRequestURL().toString();
String servletPath = request.getServletPath();
log.info("輸出數據requestURI:{}\n,requestURL:{}\n,servletPath:{}\n", requestURI, requestURL, servletPath);
//放行登錄請求
if (requestURI.startsWith("/system/login")) {
log.info("yes,跳過校驗{}", requestURI);
} else {
log.info("no,進行校驗{}", requestURI);
}
return true;
}
}
執行結果:
2021-12-07 16:13:23.114 INFO 11917 --- [nio-8080-exec-1] c.tellme.Interceptor.URIRiskInterceptor : 輸出數據requestURI:/system/login/../../test
,requestURL:http://localhost:8080/system/login/../../test
,servletPath:/test
2021-12-07 16:13:23.115 INFO 11917 --- [nio-8080-exec-1] c.tellme.Interceptor.URIRiskInterceptor : yes,跳過校驗/system/login/../../test
2021-12-07 16:13:23.127 INFO 11917 --- [nio-8080-exec-1] com.tellme.controller.FirstController : test,請求進來了
可以看到:當訪問http://localhost:8080/system/login/../../test
是可以訪問到Controller方法的,且跳過了驗證。
2.2 URL截斷繞過
針對的是String.endsWith()
的權限校驗。
@Slf4j
@Service
public class URIRiskInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//校驗數據
String requestURI = request.getRequestURI();
String requestURL = request.getRequestURL().toString();
String servletPath = request.getServletPath();
log.info("輸出數據requestURI:{}\n,requestURL:{}\n,servletPath:{}\n", requestURI, requestURL, servletPath);
//若結尾爲.do或者.action的請求進行校驗,但是SpringMVC很少有這種後綴的。
if (requestURI.endsWith("test")) {
log.info("no,進行校驗{}", requestURI);
} else {
log.info("yes,跳過校驗{}", requestURI);
}
return true;
}
}
測試結果:
2021-12-07 17:03:03.299 INFO 15815 --- [nio-8080-exec-1] c.tellme.Interceptor.URIRiskInterceptor : 輸出數據requestURI:/test;123
,requestURL:http://localhost:8080/test;123
,servletPath:/test
2021-12-07 17:03:03.299 INFO 15815 --- [nio-8080-exec-1] c.tellme.Interceptor.URIRiskInterceptor : yes,跳過校驗/test;123
2021-12-07 17:03:03.314 INFO 15815 --- [nio-8080-exec-1] com.tellme.controller.FirstController : test,請求進來了
可以看到:當訪問http://localhost:8080/test;123
是可以訪問到Controller方法的,且跳過了驗證。
3. 預防措施
- 未設置context-path的項目使用getServletPath來代替getRequestURI;
- 在設置context-path的項目確定哪些api需要校驗時,使用去掉context-path的路徑;