getRequestURI 導致的安全問題
上圖是現在最常見的web架構。
HttpServletRequest 的幾個API
@ResponseBody
@RequestMapping(value = "index")
public String index(HttpServletRequest req){
String requestURL = req.getRequestURL().toString();
String requestURI = req.getRequestURI();
String contextPath = req.getContextPath();
String servletPath = req.getServletPath();
return "getRequestURL: " + requestURL + "\n" +
"getRequestURI: " + requestURI + "\n" +
"getServletPath: " + servletPath;
}
首先我們搭建一個web服務來測試下這幾個api。
相關版本信息:
- apache-tomcat-8.5.56
- nginx-1.20.2
browser -> tomcat
經過測試有如下結果
payload | getRequestURL | getRequestURI | getServletPath |
---|---|---|---|
/index |
http://127.0.0.1:8081/index |
/index |
/index |
/./index |
http://127.0.0.1:8081/./index |
/./index |
/index |
/.;/index |
http://127.0.0.1:8081/.;/index |
/.;/index |
/index |
/a/../index |
http://127.0.0.1:8081/a/../index |
/a/../index |
/index |
/a/..;/index |
http://127.0.0.1:8081/a/..;/index |
/a/..;/index |
/index |
/;/index |
http://127.0.0.1:8081/;/index |
/;/index |
/index |
/;a/index |
http://127.0.0.1:8081/;a/index |
/;a/index |
/index |
/%2e/index |
http://127.0.0.1:8081/%2e/index |
/%2e/index |
/index |
/inde%78 |
http://127.0.0.1:8081/inde%78 |
/inde%78 |
/index |
可以看出 getServletPath 會對獲取的字符進行url解碼
browser -> nginx -> tomcat
這里要分兩種情況,proxy_pass 結尾帶斜杠和結尾不帶斜杠。
proxy_pass 結尾不帶斜杠
nginx 配置如下
upstream tomcat {
server 127.0.0.1:8081;
}
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
proxy_pass http://tomcat; #結尾不帶斜杠
root html;
index index.html index.htm;
}
}
測試結果如下
payload | getRequestURL | getRequestURI | getServletPath |
---|---|---|---|
/index |
http://tomcat/index |
/index |
/index |
/./index |
http://tomcat/./index |
/./index |
/index |
/.;/index |
http://tomcat/.;/index |
/.;/index |
/index |
/a/../index |
http://tomcat/a/../index |
/a/../index |
/index |
/a/..;/index |
http://tomcat/a/..;/index |
/a/..;/index |
/index |
/;/index |
http://tomcat/;/index |
/;/index |
/index |
/;a/index |
http://tomcat/;a/index |
/;a/index |
/index |
/%2e/index |
http://tomcat/%2e/index |
/%2e/index |
/index |
/inde%78 |
http://tomcat/inde%78 |
/inde%78 |
/index |
看起來跟上面 browser -> tomcat 沒什么區別
proxy_pass 結尾帶斜杠
upstream tomcat {
server 127.0.0.1:8081;
}
server {
listen 81;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
proxy_pass http://tomcat/; #結尾帶斜杠
root html;
index index.html index.htm;
}
}
payload | getRequestURL | getRequestURI | getServletPath |
---|---|---|---|
/index |
http://tomcat/index |
/index |
/index |
/./index |
http://tomcat/index |
/index |
/index |
/.;/index |
http://tomcat/.;/index |
/.;/index |
/index |
/a/../index |
http://tomcat/index |
/index |
/index |
/a/..;/index |
http://tomcat/a/..;/index |
/a/..;/index |
/index |
/;/index |
http://tomcat/;/index |
/;/index |
/index |
/;a/index |
http://tomcat/;a/index |
/;a/index |
/index |
/%2e/index |
http://tomcat/index |
/index |
/index |
/inde%78 |
http://tomcat/index |
/index |
/index |
通過對比可以看出proxy_pass 結尾帶斜杠nginx會做如下處理
- 將 ../ ./ 進行規范化處理,轉成絕對路徑
- 會進行url解碼
寫法問題
日常工作中經常看到開發這樣對url進行權限控制
比如,api, login 是不需要無限制訪問的,admin是需要權限訪問的
@ResponseBody
@RequestMapping(value = "api")
public String api(){
return "Api Page";
}
@ResponseBody
@RequestMapping(value = "login")
public String login(){
return "Login Page";
}
@ResponseBody
@RequestMapping(value = "admin")
public String admin(){
return "Admin Page";
}
public class MyFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
//白名單url
String[] whiteUrl = new String[]{"/api","/login","/index"};
String uri = httpServletRequest.getRequestURI();
boolean doFilter = true;
for (int i = 0; i < whiteUrl.length; i++) {
if(uri.startsWith(whiteUrl[i])){
doFilter = false;
break;
}
}
if(doFilter){
httpServletResponse.sendRedirect("/login");
}else{
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
}
<filter>
<filter-name>myFilter</filter-name>
<filter-class>com.test.filter.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>myFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
可以看到上面使用了 getRequestURI 來獲取uri ,這種獲取uri的方式會被繞過。
總結
getRequestURL()
和getRequestURI()
這兩個API解析的URL是包含特殊字符的,當使用不當時會存在安全問題,我們應該進行使用getServletPath()
來獲取URI。