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 {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {}@Overridepublic 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 {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println("LoginFilter init");}@Overridepublic 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);}@Overridepublic 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
@Overridepublic 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
@Overridepublic 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 {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println("UrlFilter init");}@Overridepublic 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);}@Overridepublic 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
