getRequestURI 导致的安全问题
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。