SpringWeb中获取路径前缀的方式与潜在的权限绕过风险

在实际业务中,为了防止越权操作,通常会根据对应的URL进行相关的鉴权操作。除了实际访问的资源路径以外,通过动态配置资源权限时,很多时候在数据库或者权限中台配置的鉴权请求路径通常还会包含路径前缀。浅谈SpringWeb中获取当前请求路径前缀的方式。

0x00 前言

在实际业务中,为了防止越权操作,通常会根据对应的URL进行相关的鉴权操作。SpringWeb中获取当前请求路径的方式可以参考https://forum.butian.net/share/2606。

除了实际访问的资源路径以外,通过动态配置资源权限时,很多时候在数据库或者权限中台配置的鉴权请求路径通常还会包含路径前缀。下面看看SpringWeb路径前缀具体内容。

0x01 SpringWeb路径前缀

DispatcherServlet从Tomcat中获取的Request中包含了完整的URL,并且会按照Servlet的映射路径把路径划分为contextPath、servletPath和pathInfo三部分。

image.png

在Spring MVC中,spring.mvc.servlet.pathserver.servlet.context-path 是两个配置属性,用于配置 contextPath、servletPath的相关信息。

1.1 contextPath

contextPath 是Web应用程序在Web服务器上运行时的上下文路径。在一个Web容器中可以同时运行多个Web应用程序,为了区分它们,每个Web应用都有一个唯一的上下文路径。可以通过server.servlet.context-path属性进行配置。

通过下面的配置,会影响整个应用程序的上下文路径。此时个应用程序将在 /myapp 路径下访问,而不是根路径/:

server.servlet.context-path=/myapp

1.2 servletPath

servletPath 是指请求中用于定位到Servlet的部分路径。在Spring MVC中,DispatcherServlet负责处理请求。一般指的是DispatcherServlet 的路径。可以通过spring.mvc.servlet.path 属性进行配置。(在 SpringBoot 的早期版本中,该属性位于 ServerProperties 类中,名称为 server.servlet-path=/。从 2.1.x 版开始,该属性被移至 WebMvcProperties 类,并更名为 spring.mvc.servlet.path=/

通过下面的配置,主要影响 DispatcherServlet 的路径, DispatcherServlet 的处理将映射到 /api/*,其下的所有请求都将由 DispatcherServlet 处理:

spring.mvc.servlet.path=/api

结合上述的两个配置,DispatcherServlet 将处理 /api/* 的请求,而整个应用程序将在 /myapp 路径下访问。最终通过访问http://localhost:8080/myapp/api/someEndpoint来访问Controller中配置的资源。

0x02 servletPath的检查机制

前面提到,可以通过配置spring.mvc.servlet.path 来影响 DispatcherServlet 的路径。Spring Web解析请求时,高版本会通过PathPattern进行解析。同样的这里会引入对servletPath的检查机制。下面是具体的过程。

当Spring MVC接收到请求时,Servlet容器会调用DispatcherServlet的service方法(方法的实现在其父类FrameworkServlet中定义),这里会根据不同的请求方法,调用processRequest方法,例如GET请求会调用doGet方法。在执行doService方法后,继而调用doDispatch方法处理。

而在doService方法中,根据parseRequestPath的值,会进行对应的处理:

image.png

通过org.springframework.web.servlet.DispatcherServlet#initHandlerMapping可知,当使用了PathPattern进行路径匹配时,该值会设置为true:

image.png

继续跟进对应的处理逻辑,在parseAndCache方法中,会调用ServletRequestPathUtils对请求进行解析:

image.png

在parse中会尝试获取servletPath,如果servletPathPrefix不为null,会处理spring.mvc.servlet.path配置的内容并返回:

image.png

这里主要是通过getServletPathPrefix来获取servletPathPrefix的,这里主要是通过request.getServletPath获取并进行编码操作:

image.png

当存在servletPathPrefix时,会创建ServletRequestPath对象:

image.png

这里将contextPath和servletPathPrefix拼接,然后调用RequestPath.parse方法进行处理:

image.png

在initContextPath方法中,这里调用了validateContextPath方法进行了相关的检查:

image.png

可以看到,当fullPath不以contextPath和servletPathPrefix拼接内容开头时,会抛出Invalid contextPath的异常:

image.png

fullPath是从前面parse方法的request.getRequestURI()方法获取的,没有经过相关归一化的处理:

image.png

那么也就是说,假设当前servletPath配置如下:

spring.mvc.servlet.path=/app

当尝试以/ap%70(%70是p的URL编码)进行访问时,因为获取到的servletPathPrefix是经过URL解码处理的,在validateContextPath方法中会因为匹配不一致而抛出异常:

image.png

同理app;的方式也是一样的。从一定的程度访问了通过编码等方式进行URL权限的绕过。

2.1 其他

在实际的鉴权组件中,通常会获取当前请求的路径进行操作。获取到的请求路径没有经过规范化处理的话,结合对应的鉴权逻辑(白名单,模式匹配等)可能存在绕过的风险。跟资源路径一样,若路径前缀也是匹配的内容,不规范的获取方式同样会存在绕过风险。

例如如下contextPath仍可正常访问对应的Controller资源接口:

server.servlet.context-path=/app/web

image.png

上面的例子是在spring-boot-starter-2.7.12.jar下运行的。这里可能会有一个疑问,高版本SpringWeb在解析时使用的是PathPattern,会因为解析模式的不同导致在路径匹配时经过不同的处理。默认情况下PathPattern是无法处理类似//的情况的,但是上述案例明显正常获取到了对应的资源:

image.png

实际上在匹配路径时,contextPath并不会影响,因为在构建requestPath时会根据contextPath进行路径的分离:

image.png

image.png

而从前面的分析也可以知道,当使用PathPattern进行解析时,会将contextPath和servletPathPrefix进行拼接合并,也就是说不论是contextPath还是servletPath都不会影响后续路径匹配的过程,contextPath的匹配在调用DispatcherServlet之前就已经处理了,所以上述例子中,即使是在contextPath中包含了//,使用PathPattern模式的SpringWeb仍可正常访问。

而当低版本使用AntPathMatcher进行路径匹配时:

image.png

在getPathWithinApplication方法中,同样获取了contextPath进行路径的分离:

image.png

只是这里并没有考虑servletPath:

image.png

但是这里在获取contextPath时仅仅进行了解码操作,而获取requestUri时额外调用了getSanitizedPath方法对多个/进行了处理,也就是说如果contextPath包含多个/的话,可能会导致路径无法匹配:

image.png

而servletPath的处理是主要依赖于alwaysUseFullPath属性,通过getPathWithinServletMapping方法进行额外处理:

image.png

Spring也对类似的问题进行了说明,具体可见https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet/handlermapping-path.html

0x03 获取路径前缀的方式

在SpringWeb中,一般获取当前请求路径前缀主要有以下方式:

  • 通过javax.servlet.http.HttpServletRequest来获取请求的上下文
  • SpringWeb中自带的方法

3.1 使用javax.servlet.http.HttpServletRequest

下面看看通过javax.servlet.http.HttpServletRequest分别是怎么获取对应的contextPath和servletPath的:

3.1.1 contextPath

以如下配置为例,查看不同方法获取到的contextPath的区别:

server.servlet.context-path=/app/web
  • request.getContextPath()

这里获取到的上下文路径默认情况下是没有经过归一化处理的(SpringWeb默认使用tomcat进行解析):

image.png

这种情况下获取到的路径前缀存在风险的,结合类似URL编码的方式可能可以绕过现有的鉴权措施。

  • request.getServletContext().getContextPath()

通过HttpServletRequest对象的getServletContext()方法获取ServletContext,然后再调用getContextPath()方法。

同样是上面的例子,此时可以看到获取到的contextPath已经经过一系列的归一化处理:

image.png

3.1.2 servletPath

以如下配置为例,查看不同方法获取到的servletPath的区别:

spring.mvc.servlet.path=/demo
server.servlet.context-path=/app/web
  • request.getServletPath()

getServletPath() 方法返回请求的Servlet路径。Servlet路径是请求的相对于上下文根的部分,不包括任何额外的路径信息。这个方法通常用于获取处理请求的Servlet路径。

当尝试以畸形前缀进行请求时,可以看到听过request.getServletPath()获取到的servletPath已经经过一系列的归一化处理:

image.png

image.png

3.2 SpringWeb中自带的方法

3.2.1 contextPath

3.2.1.1 RequestContextUtils

org.springframework.web.servlet.support.RequestContextUtils 是 Spring Web MVC 框架中的一个工具类,用于获取当前请求的RequestContext。最常用的方法是findWebApplicationContext(request),一般用于查找当前请求的 WebApplicationContext,其是 Spring Web MVC 应用程序中的一个关键接口,它是Spring IoC容器的一种扩展,用于管理Web层的Bean:

image.png

这里可以获取ServletContext,然后再调用getContextPath()方法:

RequestContextUtils.findWebApplicationContext(request).getServletContext().getContextPath()

得到的contextPath是经过归一化处理的。

3.2.1.2 ServletRequestPathUtils

org.springframework.web.util.ServletRequestPathUtils 是 Spring Framework 提供的一个工具类,用于处理ServletRequest(通常是HttpServletRequest)的请求路径信息。主要用于从请求中获取有关路径的信息,并提供了一些方法来处理和解析路径。

前面在servletPath的检查机制时提到过,当使用PathPattern进行解析时,会进行一系列的处理并且将处理后的结果封装到PATH_ATTRUBYTE属性中:

image.png

而ServletRequestPathUtils可以通过getParsedRequestPath进行获取,并调用对应的方法获取contextPath:

image.png

ServletRequestPathUtils.getParsedRequestPath(request).contextPath().value()

根据前面的分析,在处理过程中并没有对contextPath进行相关的归一化处理,所以通过这种方式获取到的contextPath在某种场景下也是存在风险的:

image.png

同理,直接使用parseAndCache方法处理获取到的contextPath也是没有进行相关的归一化处理的:

ServletRequestPathUtils.parseAndCache(request).contextPath().value()

以上是SpringWeb中常见的获取当前请求路径前缀的方式。在实际代码审计过程中可以根据不同的方式,结合实际场景判断是否存在绕过的可能。

  • 发表于 2024-02-06 10:09:19
  • 阅读 ( 9588 )
  • 分类:代码审计

0 条评论

请先 登录 后评论
tkswifty
tkswifty

64 篇文章

站长统计