0x00 前言
事情起因是 最近在看 orange 大佬在black hat 发的一篇关于路径穿越的议题
PDF:https://i.blackhat.com/us-18/Wed-August-8/us-18-Orange-Tsai-Breaking-Parser-Logic-Take-Your-Path-Normalization-Off-And-Pop-0days-Out-2.pdf
里面谈到了反向代理和tomcat 二义性问题导致路径穿越:
然后本地搭建tomcat ,直接在浏览器中用 …;/来进行路径穿越会返回 400 bad request (不是404,按理说这里应该转化为http://127.0.0.1:8080/websocket/index.html从而报404错误)。而且之前在打CTF 的时候,利用…/来进行路径穿越测试的时候也会返回400 bad request 的响应。
不是很理解,所以打算从源码来看看tomcat 是怎么处理url请求的特殊字符串 的,以及为什么到底为什么会响应400。
另外 [WUSTCTF2020]Train Yourself To Be Godly 这道题,也是利用nginx和tomcat 对’..;’ 处理的二义性来解的。
0x01 环境搭建
这次依旧选择的是tomcat 7.0.99 版本,调试tomcat 源码可以看我之前发的关于CVE-2020-1938 的文章中有介绍:
https://blog.csdn.net/qq_41891666/article/details/106477674
0x02 调试分析
首先在 org.apache.catalina.connector.CoyoteAdapter.java 中的service() 函数下断点
关键点1: postParseRequest() 函数
调用 postParseRequest() 可以看到,只有返回为true ,才会calling the container 。
我们跟进postParseRequests() 函数:
从注释中就可以知道这个函数的作用是在解析为http头之后,执行必要的处理,使得请求/响应 对可以传到容器管道进行处理。
看到725 行,parsePathParameters() 函数,注释写着会删掉path parameters ,并且把decodeURI 转化为bytes:
具体则是解析 以’;’ 作为分隔符的参数。
添加链接描述
进入这个函数,看到1047 行到1050行正是删除 path param 。
那么加入我们输入的是 /examples/websocket/123;.456/index.xhtml
删除之后就变为 /examples/websocket/123/index.xhtml
[这个特性在错误的使用getRequestURI() 函数来进行权限校验的时候,可以进入绕过,参考: https://joychou.org/web/security-of-getRequestURI.html]
关键点2: req.getURLDecoder().convert(decodedURI, false); 对url 进行解码。
在730 行会调用 req.getURLDecoder().convert(decodedURI, false);
从注释中可以看出是进行URL 解码。
如果调用这个函数抛出异常,就会返回400 。
我们跟进convert()函数:
会进入255行的convert() 函数,继续跟进:
可以看到130行,对解码后的字符进行判断,如果解密之后是’/’ ,就会抛出异常。这也就是为什么我们请求的url 中有’%2f’会导致返回400。
关键点3: normalize() 函数
739行,处理完上面的url 解码之后会调用normalize() 函数进行规范化
可以看到如果规范化失败,也会返回400。
我们跟进normalize ,注释中可以看出这个方法时对’/’ ,’//’ ,’/./’,’/…/’ 进行处理
替换’‘为’/’,并且判断如果存在空字符,则直接返回false ,也就是之后的400。
替换 ‘//’ 为 ‘/’:
对结尾的’/.’ 和’/…’ 加上’/’:
然后处理’./’:
然后处理’…/’, 并且检查如果处理完后开头是’…/'的话就返回false,这也防止了路径穿越。
0x03 回到原来的问题
现在回到原来的问题上,如果使用nginx 作为反向代理,然后后端又是tomcat 的时候,为什么利用下面这个图可以路径穿越:
nginx 源码现在就没看了??,nginx 在匹配location 的时候也会先进行url 解码,normalize ‘./’ ‘…/’ 等工作,但是 nginx 把/…;/当作一个正常的directory
nginx 配置应该形如是这样:
1 2 3 4 5 6 7 8 9 | 1 server {<!-- --> 2 listen 80; 3 server_name www.123.com; 4 5 location /examples/ {<!-- --> 6 proxy_pass http://127.0.0.1:8080/examples/; 7 index index.html index.htm index.jsp; 8 } 9 } |
那么问题就来了,返回 http://127.0.0.1:8080/examples/…;/manager/html 的时候,更据第5行的location 会反向代理到tomcat 中,传给tomcat 的请求为:http://127.0.0.1:8080/examples/…;/manager/html
而通过上面分析,对于url 含有’;’ ,tomcat 经过postParseRequest() 函数处理,会删掉’;’ 到’/‘后面的字符,比如’/…;123/’ 会变为’…/’
然后经过normalize() 处理会变为:http://127.0.0.1:8080/manager/html
成功实现路径穿越。
对于一开始那个为什么直接用http://127.0.0.1:8080/…;/examples/websocket/index.xhtml 会报400错误,原因就是在normalize() 函数中进行了判断,如果normalize 之后 …/ 是 url 开头的话 ,会返回400。
0x03 总结
tomcat 对url 特殊字符的处理主要是三个步骤对于三个函数:
先是postParseRequest() 函数对’;’ 分号进行处理。比如把’/123;456/’ 替换为’/123/’。
然后是调用req.getURLDecoder().convert() 函数对URL 中进行了URL编码的字符进行解码,其中如果解码之后的结果是’/’ ,也就是输入的URL 含有’%2f’ 就会返回400。
接着是调用normalize() 函数对URL 进行规范化,比如处理’/…/’ ,’/./’ ,’\’,’//’ 。另外URL中如果含有空字符串,则会返回400.如果规范化后的url 是以’…/'开头的,也会返回400。
0x04 参考
http://www.mi1k7ea.com/2020/04/01/Tomcat-URL%E8%A7%A3%E6%9E%90%E5%B7%AE%E5%BC%82%E6%80%A7%E5%8F%8A%E5%88%A9%E7%94%A8 Tomcat URL解析差异性导致的安全问题
https://www.jianshu.com/p/b010c9302cd0 nginx 之 proxy_pass详解
https://segmentfault.com/a/1190000013267839 一文弄懂Nginx的location匹配