tomcat对url请求中的特殊字符处理 分析

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匹配