转载

getRequestURI()带来的安全问题

背景

最近碰到几个漏洞都是在获取HTTP请求中URI时,使用 getRequestURI() 进行权限校验,而非 getServletPath() 导致。

漏洞描述:当应用存在静态资源目录,比如 /css/ 目录,在权限校验时一般会选择放行,即不校验权限。研发同学用 getRequestURI() 获取URI后,判断是否包含 /css/ 字符串,如果包含则不校验权限。此时如果URI为 /css/../hello ,用 getRequestURI() 获取的URI是 /css/../hello ,包含 /css/ 字符串,所以不校验权限。但是此时后端的路由为 /hello ,导致权限绕过。

并且今天刚看到shiro的一个新CVE,也是因为 getRequestURI() 导致, 相关Github Commit记录 。

环境

  • SpringBoot 1.5.1.RELEASE
  • SpringBoot非内嵌tomcat 8.5.53(截止2020年3月tomcat8最新版本)
@RequestMapping(value = "/hello")
    @ResponseBody
    private String index(HttpServletRequest request, HttpServletResponse response) {
        String uri = request.getRequestURI();
        String serlvetPath = request.getServletPath();
        return "uri: " + uri + "/t" + "serlvetPath: " + serlvetPath;
    }

利用 ../ 进行权限绕过。

当用curl发起 ../ 的请求,curl会默认rebuid该请求,所以加了 --path-as-is 让其保持原请求。

curl -v 'localhost:8080/css/../hello' --path-as-is
curl -v 'localhost:8080/css/%2e%2e/hello' --path-as-is

分别返回

uri: /css/../hello    serlvetPath: /hello
uri: /css/%2e%2e/hello    serlvetPath: /hello

利用 ..; 进行权限绕过:

curl -v 'localhost:8080/css/..;/hello' --path-as-is
curl -v 'localhost:8080/css/..;name=joychou/hello' --path-as-is
curl -v 'localhost:8080/css/%2e%2e;name=joychou/hello' --path-as-is

分别返回:

uri: /css/..;/hello    serlvetPath: /hello
uri: /css/..;name=joychou/hello    serlvetPath: /hello
uri: /css/%2e%2e;name=joychou/hello    serlvetPath: /hello

二者区别

让研发同学使用从getRequestURI改为getServletPath,研发同学考虑到稳定性还是存在顾虑。网上搜索确实二者会有结果不一致的情况,但未提及是什么情况。

我们用单独打个war包,放在tomcat的webapps目录下运行,看到这种情况二者的结果不一样。

顾名思义,在getServletPath()方法的注释中:

Returns the part of this request's URL that calls the servlet.

getRequestURI()带来的安全问题

getRequestURI实现

源码就没必要看了,因为URI是什么, getRuquestURI() 就返回什么,并且不会进行URL解码。

getServletPath实现

Tomcat的 postParseRequest() 方法会对HTTP请求进行预处理,我们关心的逻辑:

parsePathParameters()
normalize()

getRequestURI()带来的安全问题

先来看看 parsePathParameters() 的处理逻辑:

curl -v 'localhost:8080/css/..;name=joychou/hello' --path-as-is

判断是否存在分号

getRequestURI()带来的安全问题

获取;和/之间的内容name=joychou,对其用=分隔,并添加到addPathParameter方法中,并从URI中去掉 ;name=joychou 内容。这种用法一般用来获取sessionid,比如 http://xx/index.html;jsessionid=1234

可以看到,新的URI已经变成了 /css/../hello
getRequestURI()带来的安全问题

getRequestURI()带来的安全问题

getRequestURI()带来的安全问题

接着就是对URI进行标准化(normalize),先对URI进行URLDecode,如果存在 /../ ,将其返回到上一级目录,即/css/../hello处理为/hello,并将新的Path设置为servletPath。

getRequestURI()带来的安全问题

tomcat对uri和参数的限制

前几天有个业务方反馈发布回滚了,原因是因为HTTP请求的GET参数中存在 [xx] 字符串,升级docker后tomcat版本发生改变,tomcat对URL处理逻辑改变,导致 [] 字符串被认为是不安全字符。所以业务方在升级tomcat存在稳定性的风险,一定要做好相关测试。tomcat该安全性功能的判断,直接解析HTTP请求内容,不会对URL进行URL解码。

IS_NOT_REQUEST_TARGET 变量控制HTTP请求的URI,使其不允许出现 空格"#<>/^ {|}`和非可见字符 (ASCII码0-31和127)。

getRequestURI()带来的安全问题

getRequestURI()带来的安全问题

IS_QUERY_RELAXED 控制HTTP请求内容中第一行出现的参数,使其不允许出现 空格"#<>[/]^ {|} 和非可见字符 (ASCII码0-31和127)。 IS_QUERY_RELAXED`数组中值为false表示非法字符,一共有46个非法字母(128-82个合法字符)。

82个合法字符如下:

getRequestURI()带来的安全问题

getRequestURI()带来的安全问题

扩展的小彩弹:

tomcat提供一个 tomcat.util.http.parser.HttpParser.requestTargetAllow 参数属性,该参数只能在 {}| 中任选,设置后允许设置的字符出现在整个URL中(包括URI+HTTP请求内容中第一行出现的参数)。

getRequestURI()带来的安全问题

Q&A

什么是HTTP请求内容中第一行的参数?

简单理解就是POST的参数就不是第一行参数。因为POST的参数位置在HTTP请求内容Header的下面。

本文由JoyChou 创作,采用 知识共享署名4.0 国际许可协议进行许可

本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名

最后编辑时间为: Mar 25, 2020 at 11:08 am

原文  https://joychou.org/web/getRequestURI.html
正文到此结束
Loading...