Tomcat getRequestURI()使用不当绕过鉴权
Servlet处理URL请求的路径:
- request.getRequestURL():返回全路径;
- request.getRequestURI():返回除去Host(域名或IP)部分的路径;
- request.getContextPath():返回工程名部分,如果工程映射为`/`,则返回为空;
- request.getServletPath():返回除去Host和工程名部分的路径;
- request.getPathInfo():仅返回传递到Servlet的路径,如果没有传递额外的路径信息,则此返回Null;
那么如果开发使用getRequestURI()来获取路径,并实现Filter,会出现什么类型绕过?
Springboot Filter:
我们使用springboot来实现filter下具体的功能:
filterbypass一共有三个接口:
前两个分别代表admin能访问的,operator能访问的,而最后一个接口主要是方便来比较这上面的几个方法的差异。
@RestController
@RequestMapping("/filter")
public class FilterByPass {
@RequestMapping("/admin/hello")
public String admin()
{
return "i am admin role.";
}
@RequestMapping("/operator/hello")
public String operator()
{
return "i am operator role.";
}
@RequestMapping("/uri")
public ResponseResult<String> uri(HttpServletRequest request)
{
String getRequestURI=request.getRequestURI();
String getContextPath=request.getContextPath();
String getServletPath=request.getServletPath();
String result="getRequestURI: "+getRequestURI+"\n"
+"getContextPath: "+getContextPath+"\n"
+"getServletPath: "+getServletPath+"\n";
if (result != null) {
return new ResponseResult<>(result, "执行成功", 200);
}
return new ResponseResult<>("result is null", "执行成功", 200);
}
}
这里简单说下Filter的作用,其他大家百度详细的了解下spring 和springboot的Filter的差别和使用方法。
filter中的chain.dofilter() 方法表示过滤器的放行,只用执行了这个方法,才能执行过滤之后的内容,最后再回来执行dofiler()之后的内容。
接来我们来实现一个登陆的Filter:
public class loginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
}
@Override
public void destroy() {
}
}
Filter这三个方法的作用一定要记清楚:
init(Filterconfig)
:代表filter对象初始化方法 filter对象创建时执行;
doFilter(ServletRequest,ServletResponse,FilterChain)
:代表filter执行过滤的核心方法,如果某资源在已经被配置到这个filter进行过滤的话,那么每次访问这个资源都会执行doFilter方法;destory()
:代表是filter销毁方法 当filter对象销毁时执行该方法。
那么具体实现两种常见的鉴权方式,一种是根据uri来判断该不该鉴权,另一种是根据后缀来判断该不该鉴权等。
@WebFilter(urlPatterns = "/*",filterName = "loginfilter")
public class loginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("LoginFilter init");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//具体拦截方式
HttpServletRequest uri=(HttpServletRequest) request;
String requestUri=uri.getRequestURI();//用户路径
String servletPath=uri.getServletPath();//实际路径
if(requestUri.startsWith("/filter/admin/hello")){
//Auth 鉴权函数,这里省略
System.out.println(" Need Auth ");
return;
}
if (requestUri.endsWith("*.action"))
{
//对某类重要的接口鉴权
System.out.println(" Need Auth Action");
}
System.out.println(" by pass ");
chain.doFilter(request,response);
}
@Override
public void destroy() {
System.out.println("LoginFilter init");
}
}
这里其实使用的是springboot,如果是spring mvc ,那么我们规则其实配置都是在web.xml中。但是springboot基本上都是靠注解类来解决,springboot通过扫描注解,@ServletComponentScan和@WebFilter实现。
参考:
/**
* springboot 整合filter方式之一
*
* 以前web.xml配置:
* <filter>
* <filter-name>LoginFilter</filter-name>
* <filter-class>com.xxx.filter.LoginFilter</filter-class>
* </filter>
*
* <filter-mapping>
* <filter-name>LoginFilter</filter-name>
* <url-pattern>/LoginFilter</url-pattern>
* </filter-mapping>
*
* FirstFilter
**/
getRequestURI()和getServletPath()对比:
上面Filter的大体流程,对接口/filter/admin/hello和后缀是.action的都需要鉴权访问,而不是这类的直接放行通过。正常访问如下:
具体的鉴权这里没有具体实现,可以自行实现。
Uri path绕过
接下里测试如下的payload:
//filter/admin/hello
/filter/..;/admin/hello
/filter/./././admin/hello
/filter/xxx/../admin/hello
第一种方式可以绕过:
成功获得admin接口的内容。
1 //filter/admin/hello
:
这里对getRequestURI()的源码分析下:
org/apache/tomcat/embed/tomcat-embed-core/9.0.27/tomcat-embed-core-9.0.27-sources.jar!/org/apache/catalina/connector/RequestFacade.java
@Override
public String getRequestURI() {
if (request == null) {
throw new IllegalStateException(
sm.getString("requestFacade.nullRequest"));
}
return request.getRequestURI();
}
继续看下实现方法:
org/apache/tomcat/embed/tomcat-embed-core/9.0.27/tomcat-embed-core-9.0.27-sources.jar!/org/apache/catalina/connector/Request.java
@Override
public String getRequestURI() {
return coyoteRequest.requestURI().toString();
}
org/apache/tomcat/embed/tomcat-embed-core/9.0.27/tomcat-embed-core-9.0.27-sources.jar!/org/apache/coyote/Request.java
private final MessageBytes uriMB = MessageBytes.newInstance();
···
public MessageBytes requestURI() {
return uriMB;
}
而关于MessageBytes的了解可以参考深入理解Tomcat(12)拾遗-MessageBytes
coyoteRequest.requestURI().toString()直接返回请求的URL内容,没有做任何处理以及URL解码。
接来继续验证剩余的payload:
2 /filter/xxx/../admin/hello
:
3 /filter/;xxx/admin/hello
:
4 /filter/./././admin/hello
:
从上面四个payload都能访问成功,而可以看出来Tomcat不是以getRequestURI()获取的uri作为访问,那以什么方法为主呢?
前段时间爆出的Apache Shiro的CVE中,就是使用getRequestURI()函数导致的,这里可以看到人家的补丁是怎么打的,其实就是用getPathInfo()替换掉就OK了:https://github.com/apache/shiro/commit/3708d7907016bf2fa12691dff6ff0def1249b8ce
Uri后缀绕过:
package com.range.demo.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@WebFilter(urlPatterns = "*.action",filterName = "urlfilter")
public class UrlFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("UrlFilter init");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println(" enter UrlFilter ");
//具体拦截方式
HttpServletRequest uri=(HttpServletRequest) request;
String requestUri=uri.getRequestURI();//用户路径
String servletPath=uri.getServletPath();//实际路径
String getPathInfo=uri.getPathInfo();
System.out.println("getRequestURI: "+requestUri);
System.out.println("getServletPath: "+servletPath);
System.out.println("getPathInfo: "+getPathInfo);
if(requestUri.startsWith("/filter/admin/")&&requestUri.endsWith(".action")){
//Auth 鉴权函数,这里省略
System.out.println("UrlFilter Need Auth ");
return;
}
System.out.println("UrlFilter by pass ");
chain.doFilter(request,response);
}
@Override
public void destroy() {
System.out.println("UrlFilter init");
}
}
绕过方法:
/filter/admin/xxxx.action;xxx.js
:
多个Filter怎么处理?
spring mvc的顺序:
多个过滤器的话,比如myservlet同时匹配到Afilter和Bfilter,
Afilter中dofilter()之前的代码为A1,dofilter()之后的代码为A2;
Bfilter中dofilter()之前的代码为B1,dofilter()之后的代码为B2.
web.xml中的filter-mapping配置顺序为Bfilter在上,Afilter在下。
那么执行顺序为B1 - A1 - myservlet - A2 - B2。
而springboot是没有web.xml文件,那么是有两种方式来完成,第一种Filter类上加@WebFilter;顺序默认:
后缀filter 先加载,url后缀范围大的在前,范围小的在后
第二种配置能保证Filter的执行先后顺序在启动类添加FilterRegistrationBean,设置优先级。
xxx.setOrder(2);#order值越小,Filter越早经过
源码请关注公号并回复关键字[getRequestURI]....
参考
1 https://www.jianshu.com/p/49531ee13c21
2 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/
3 https://www.jianshu.com/p/cb27c8da1543