问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
Spring WebMvc.fn路由解析与权限绕过浅析
渗透测试
WebMvc.fn 是 Spring Web MVC 的一部分,它提供了一种轻量级的函数式编程模型,允许开发者使用函数来定义路由和处理 HTTP 请求。这种模型是注解编程模型的替代方案。 浅谈其中的权限绕过问题。
0x00 前言 ======= `WebMvc.fn` 是 Spring Web MVC 的一部分,它提供了一种轻量级的函数式编程模型,允许开发者使用函数来定义路由和处理 HTTP 请求。这种模型是注解编程模型的替代方案。 在 `WebMvc.fn` 中,HTTP 请求由 `HandlerFunction` 处理,这是一个接收 `ServerRequest` 并返回 `ServerResponse` 的函数。一般可以通过RouterFunctions.route()方法进行处理。例如下面的例子: ```Java @Configuration public class PersonHandlerConfiguration { @Bean public RouterFunction<ServerResponse> person() { return route().GET("/person", request -> { return ServerResponse.status(HttpStatus.OK).body("Hello World") ; }).build() ; } } ``` 启动SpringApplication成功访问并解析相应的路由:  0x01 WebMvc.fn路由解析过程 ==================== 在SpringMvc中,DispatcherServlet是前端控制器设计模式的实现,提供Spring Web MVC的集中访问点,而且负责职责的分派。同样的WebMvc.fn也是经过DispatcherServlet进行处理的。以2.5.8版本springboot为例,当请求`/person`接口时,看看具体的路由解析过程。 当Spring MVC接收到请求时,Servlet容器会调用DispatcherServlet的service方法(方法的实现在其父类FrameworkServlet中定义)。根据不同的请求方法,会调用对应的processRequest方法,例如GET请求会调用doGet方法。然后在doDispatch方法进行处理,这里会顺序循环调用HandlerMapping的getHandler方法找到合适的HandlerMapping供后续解析处理(WebMvc.fn使用的是RouterFunctionMapping):  RouterFunctionMapping会根据传入的 `HttpServletRequest` 找到相应的处理器函数`HandlerFunction`。主要是在org.springframework.web.servlet.function.support.RouterFunctionMapping#getHandlerInternal方法进行处理:  首先检查 `this.routerFunction` 是否不为 `null`。它定义了应用程序的路由规则,如果 `routerFunction` 存在,会使用 `HttpServletRequest` 创建一个 `ServerRequest` 对象:  然后使用 routerFunction.route(request) 来路由请求。这个方法尝试匹配 ServerRequest 与 RouterFunction 中定义的路由规则。如果找到匹配的路由,则返回一个包含 HandlerFunction 的 Optional 对象:  这里会调用org.springframework.web.servlet.function.RouterFunctionBuilder#route方法进行进一步的处理,这里遍历一系列的路由函数routerFunctions的route方法,尝试找到第一个与当前请求匹配的路由,并返回相应的处理器函数`HandlerFunction`:  RequestPredicates里定义了请求的规则,包括请求参数、请求方式等。上述案例代码会调用DefaultRouterFunction#route方法进行处理,主要是通过 `this.predicate.test(request)` 来检查当前路由函数的`Predicate`是否匹配传入的 `ServerRequest` 对象:  在test方法中,主要做了两个判断。一个是判断当前请求的方法是否在对应路由函数的方法集合中:  另一个是通过PathPattern的matchAndExtract方法进行请求路径的匹配:  两者均匹配成功时,会得到具体的处理器函数`HandlerFunction`,此时即可执行对应的逻辑来进一步解析请求。 上面的案例是只注册了一个RouterFunction,如果容器中有多个RouterFunction类型的bean时,DifferentComposedRouterFunction会逐层包装,将所有RouterFunction组合在一个对象中。然后调用DifferentComposedRouterFunction#route方法,在递归调用过程中,最终会调用到配置类中定义的BuiltRouterFunction#route重复上述的过程来获取目标Handler:  以上是WebMvc.fn大致的路由解析过程。 1.1 静态资源解析 ---------- 除此之外,RouterFunctions可以通过PathResourceLookupFunction在响应式路由中处理静态资源请求。将请求的路径映射到文件系统中的资源,如图片、样式表或 JavaScript 文件等。 解析时主要会调用ResourcesRouterFunction#route方法进行处理,实际调用的是org.springframework.web.servlet.function.PathResourceLookupFunction#apply方法:  首先会使用 this.pattern.matches(pathContainer) 检查请求路径是否与定义的路由模式匹配。如果不匹配,返回一个空的 Optional。如果路径匹配,使用 this.pattern.extractPathWithinPattern(pathContainer)提取模式内的路径部分:  然后会调用 this.processPath(pathContainer.value()) 方法对路径进行进一步处理,若处理后的路径包含`%`,则进行URL解码:  processPath方法的主要作用是确保路径以正斜杠(`/`)开始,并且移除路径中不必要的部分:  判断解码后的路径是否合法,若没问题则通过this.location.createRelative(path) 创建一个 Resource 对象,这个对象代表了请求的资源。然后检查创建的资源是否可读,并且是否位于定义的资源位置下。如果是,返回一个包含该资源的 Optional。否则,返回空:  首先是isInvalidPath方法,这个方法关注安全性,确保路径不会导致不安全的文件访问或路径遍历攻击: - 检查路径字符串 `path` 是否包含 `WEB-INF` 或 `META-INF`,这些目录正常情况下不应该被外部可访问 - 使用 ResourceUtils.isUrl(relativePath) 检查相对路径是否是一个有效的 URL(类似classpath:的调用)。如果是,或者路径以 url: 开头,表明这是一个指向外部资源的路径。也是不合理的 - 检查路径中是否包含 `..`,这可能是尝试进行路径遍历的尝试。通过 `StringUtils.cleanPath(path)` 处理路径后会再次检查是否包含 `../`  isResourceUnderLocation方法,核心是校验请求的资源是否位于定义的资源位置下,并且对对请求内容再次URL解码并检查是否包含`../`危险字符:  相比常见的RequestMappingHandlerMapping解析过程,RouterFunctionMapping会更简洁一些。下面看看在基于请求Path的鉴权场景下有什么潜在权限绕过的风险。 0x02 RouterFunctionMapping权限绕过浅析 ================================ 根据前面的分析可知,获取请求路径的方式主要是`request.requestPath().pathWithinApplication`。在某些低版本还可能用到`request.pathContainer`,然后通过`PathContainer.parsePath`进行处理:  最后在实际路径匹配时都会调用相应解析器的匹配方法进行处理,这点跟RequestMappingHandlerMapping是类似的。以PathPatternParser解析器为例,其会调用removeSemicolonContent移除URL路径中分号,同时进行r匹配时,会对路径中的URL编码进行解码操作。并且还会识别请求path尾部额外的/。也就是说,从路径匹配的角度,常规RequestMappingHandlerMapping解析时的一些权限绕过思路在RouterFunctionMapping也是适用的(`../`在请求中会被识别为非法请求,无需关注),以请求`/person`接口为例,可以看到基本的变形后路径仍可以正常解析并获取对应的资源:  除此之外,前面还提到了,RouterFunctions可以通过PathResourceLookupFunction在响应式路由中处理静态资源请求。并且通过不同的Resolver还可以支持`file://`协议的使用。 下面是一个实际的案例,在特定的场景下,可能需要对日志文件的访问进行严格控制,以确保只有授权的用户才能查看或下载日志文件。实际业务场景中通过`RouterFunction`来快速定义路由,并且通过filter的方式**对特定路径下特定后缀的文件进行了权限的控制**,可以看到下面通过file协议把tomcat的日志目录映射到了web路径中,可以通过`/log`接口就行访问: ```Java @Configuration public class LogRouter { ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); @Bean RouterFunction staticResourceLocator(){ return RouterFunctions.resources("/log/**", resolver.getResource("file:///usr/local/tomcat/logs/")); } } ``` 基于前面的分析,除了在RouterFunction配置的路径映射,还会对实际请求的资源路径做一些处理,在org.springframework.web.servlet.function.PathResourceLookupFunction#apply方法中,原始请求路径中包含`%`时,会进行一次URL解码:  后续就是基础的安全检查,然后调用createRelative创建对应的resource,跟进对应的实现,主要是org.springframework.core.io.FileUrlResource:  比较可惜的是,这里将path中的`#`替换成了对应的URL编码,此时就没法利用类似`file:///usr/local/tomcat/logs/catalina.yyyy-MM-dd.log#png`的方法来绕过类似`*.log`的后缀匹配了:  除了相关的安全检查外,还有一个可读性的检查,主要是通过checkReadable方法实现的,如果是file协议的话,会获取实际的File对象,然后通过canRead方法进行判断:  继续跟进可以看到是通过org.springframework.util.ResourceUtils#getFile方法来获取协议中的文件路径的:  在对应的getFile方法中,实际上会通过URI对象的getSchemeSpecificPart方法再做一层解码:   也就是说,若请求的文件路径是URL双重编码的,在实际判断时,也能通过对应的可读性判断,通过相关的安全检查也是没问题的(实际请求的内容没有类似`../`的穿越符,也没有读取配置资源以外的目录)。这里提供了一个思路,那能否通过URL双重编码的方式来请求实际的目录资源呢?  继续跟进对应的`HandlerFunction`解析,最终会通过org.springframework.core.io.UrlResource#getInputStream方法来获取对应的资源:  在建立请求时可以看到,这里会再对请求路径解码一层,结合org.springframework.web.servlet.function.PathResourceLookupFunction#apply方法的解码,也就是说,在特定场景下,还以可以通过双重URL编码来绕过对应的权限控制:  映证对应的猜想,以读取catalina.out为例,可以看到通过双重URL编码的方式在非登陆状态下读取到了相关的文件内容:  除此以外,对于使用类似shiro/springsecurity等通用鉴权措施也是类似的,可以解析不同场景下解析差异,来达到绕过的效果。 0x03 其他 ======= 除了传统的Filter/intercept的鉴权方式以外,还可以通过使用路由函数生成器上的before、after或filter方法来进行处理,例如`before` 方法允许你在请求处理链的开始处插入逻辑。这通常用于执行一些前置操作,如认证、日志记录或请求预处理。  这类方法方便请求处理的不同阶段插入自定义逻辑。与此同时也绑定了对应的路由处理函数,避免了类似filter/interceptor基于path鉴权措施在特定场景下的绕过问题。此外,WebFlux也有WebFlux.fn的内容。具体实现也是类似的,在实际代码审计过程中可以关注下。
发表于 2024-09-30 10:00:02
阅读 ( 2864 )
分类:
代码审计
0 推荐
收藏
1 条评论
十块修得同船渡
2024-12-05 01:40
没看懂
请先
登录
后评论
请先
登录
后评论
tkswifty
64 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!