vlambda博客
学习文章列表

​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一共有三个接口:

image-20200619094341403

前两个分别代表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的都需要鉴权访问,而不是这类的直接放行通过。正常访问如下:

​Tomcat getRequestURI()使用不当绕过鉴权

image-20200619113150565

具体的鉴权这里没有具体实现,可以自行实现。

Uri path绕过

接下里测试如下的payload:

//filter/admin/hello /filter/..;/admin/hello/filter/./././admin/hello/filter/xxx/../admin/hello

第一种方式可以绕过:

​Tomcat getRequestURI()使用不当绕过鉴权

image-20200619121814044

成功获得admin接口的内容。

1 //filter/admin/hello:


​Tomcat getRequestURI()使用不当绕过鉴权

image-20200619121920373

这里对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:

​Tomcat getRequestURI()使用不当绕过鉴权

image-20200619143113668

​Tomcat getRequestURI()使用不当绕过鉴权

image-20200619143154451

3 /filter/;xxx/admin/hello:

​Tomcat getRequestURI()使用不当绕过鉴权

image-20200619143640149

​Tomcat getRequestURI()使用不当绕过鉴权

image-20200619143712769

4 /filter/./././admin/hello:

​Tomcat getRequestURI()使用不当绕过鉴权

image-20200619143840237

​Tomcat getRequestURI()使用不当绕过鉴权

image-20200619143905318

从上面四个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:

image-20200619162918094

多个Filter怎么处理?

spring mvc的顺序:

多个过滤器的话,比如myservlet同时匹配到AfilterBfilterAfilterdofilter()之前的代码为A1dofilter()之后的代码为A2Bfilterdofilter()之前的代码为B1dofilter()之后的代码为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