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。

posted @ 2022-04-09 23:10  depycode  阅读(1766)  评论(0编辑  收藏  举报