浅谈SpringWeb请求解析过程

新闻资讯   2023-07-21 13:49   72   0  

扫码领资料

获黑客教程

免费&进群


0x00前言

在SpringMvc中,DispatcherServlet是前端控制器设计模式的实现,提供Spring Web MVC的集中访问点,而且负责职责的分派。主要职责如下:

  • 文件上传解析,如果请求类型是multipart将通过MultipartResolve进行文件上传解析;

  • 通过HandlerMapping,将请求映射到处理器(返回一个HandlerExecutionChain,它包括一个处理器,多个HandlerIntercept拦截器)

  • 通过HandlerAdapter支持多种类型的处理器(HandlerExecutionChain中的处理器);

  • 通过ViewReslver解析逻辑视图名到具体视图实现;

  • 本地化解析;

  • 渲染具体的视图等;

  • 执行过程中遇到异常将交给HandlerExecutionResolver来解析;

0x01 Spring Web解析过程

以spring-webmvc 5.3.9为例。当向Spring MVC发送一个请求时,看看具体的处理过程是怎么样的。

当Spring MVC接收到请求时,Servlet容器会调用DispatcherServlet的service方法(方法的实现在其父类FrameworkServlet中定义):

这里首先获取request请求的类型,除了PATCH方法以外都会通过HttpServlet的service方法进行处理:

这里实际上是根据不同的请求方法,调用processRequest方法,例如GET请求会调用doGet方法:

在执行doService方法后,继而调用doDispatch方法处理:

在doDispatch方法中,首先会对multipart请求进行处理,然后获取对应的mappedHandler:

在getHandler方法中,按顺序循环调用HandlerMapping的getHandler方法:

常见的HandlerMapping有如下几个,查阅JavaDoc文档可知注解中配置的路由是通过RequestMappingHandlerMapping处理:

在getHandler方法中通过getHandlerInternal获取handler构建HandlerExecutionChain并返回:

getHandlerInternal方法从request对象中获取请求的path并根据path找到handlerMethod:

在initLookupPath方法中,主要用于初始化请求映射的路径:

这里通过UrlPathHelper类进行路径的处理,UrlPathHelper是Spring中的一个帮助类,有很多与URL路径处理有关的方法。后续单独分析。

获取到路径后,调用lookupHandlerMethod方法,首先直接根据路径获取对应的Mapping,获取不到的话调用addMatchingMappings遍历所有的ReuqestMappingInfo对象并进行匹配:

在addMatchingMappings方法中,遍历识别到的ReuqestMappingInfo对象并进行匹配:

核心方法getMatchingMapping实际上调用的是org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#getMatchingCondition方法:

getMatchingCondition不同版本的实现也是不一样的,高版本会使用PathPattern来进行URL匹配(不同版本会有差异,在 2.6之前,默认使用的是AntPathMatcher进行的字符串模式匹配):

在getMatchingCondition中会检查各种条件是否匹配,例如请求方法methods、参数params、请求头headers还有出入参类型等等,其中patternsCondition.getMatchingCondition(request)是核心的路径匹配方法:

然后会调用PatternsRequestCondition#getMatchingPattern方法进行相关的匹配:

查看PatternRequestCondition#getMatchingPattern方法的具体实现,如果模式与路径相等,直接返回模式,否则进行后缀模式匹配,这里涉及到两个属性SuffixPatternMatch&TrailingSlashMatch,根据这两个属性的boolean值会调用pathMatcher#match方法进行进一步的匹配:

查后续获取到url 和 Handler 映射关系后,springMVC就可以根据请求的uri来找到对应的Controller和method,处理和响应请求:

0x02 工具类

2.1 路径处理帮助类UrlPathHelper

UrlPathHelper类是Spring的一个帮助类,主要根据相应的配置解析请求中的路径,里面实现了很多与URL路径处理有关的方法。

以spring-web-5.3.9为例,接前面SpringMvc请求解析过程的分析,当进入到UrlPathHelper时,首先调用resolveAndCacheLookupPath方法:

继续跟进,这里调用了getPathWithinApplication方法:

查看getPathWithinApplication的具体实现,这里分别获取了ContextPath和requestUri然后进行处理:

首先是ContextPath,这里会进行对应的解码操作,相关方法(decodeRequestString->decodeInternal,若设置了解码属性便进行对应的解码操作):

然后是requestUri,这里通过request.getRequestURI()方法获取当前request中的URI/URL,并不会对获取到的内容进行规范化处理,所以UrlPathHelper进行了URI解码、移除分号内容并清理斜线等进一步的处理:

查看decodeAndCleanUriString方法的具体实现,主要有三个方法,看看具体的作用:

首先是removeSemicolonContent,对于当前处理的URI,如果设置了setRemoveSemicolonContent属性为true,则删除分号,否则删除Jsessionid:

然后是decodeRequestString,这里前面说过,如果设置了解码属性便进行对应的解码操作。

最后是getSanitizedPath方法,这个方法主要是将//替换为/:

此时ContextPath和requestUri已经处理完成,继续调用getRemainingPath方法进行处理,这里主要是将mapping字符(实际上传入的是ContextPath)与requestUri字符串相匹配,把requestUri中的分号部分忽略掉:

到这里整个getPathWithinApplication方法处理完成,这时候涉及到一个属性alwaysUseFullPath,不同的值将会决定是否经过getPathWithinServletMapping方法处理(当Spring Boot版本在小于等于2.3.0.RELEASE的情况下,alwaysUseFullPath为默认值false,当前版本会直接返回处理后的pathWithinApp):

到此整个String lookupPath = this.initLookupPath(request);解析完成。

2.1.1 其他

前面提到在initLookupPath方法中,主要用于初始化请求映射的路径,主要会通过UrlPathHelper类进行路径的处理,这里还有一段逻辑,当this.usesPathPatterns()为true时会执行另外一段逻辑:

当使用PathPattern进行解析时,this.usesPathPatterns()为true,以spring-webmvc-5.3.25为例,查看具体的解析过程:

首先从request域中获取PATH_ATTRIBUTE属性的内容,然后使用defaultInstance对象进行处理:

这里会根据removeSemicolonContent的值(默认为true)确定是移除请求URI中的所有分号内容还是只移除jsessionid部分:

到此整个String lookupPath = this.initLookupPath(request);解析完成。

这里并没有前面调用resolveAndCacheLookupPath的逻辑复杂,例如并不会将//处理成/,结合PathPattern的解析逻辑,如果此时Controller配置如下:

@RequestMapping("/admin/page")
@ResponseBody
public String hello() {
return "admin page";
}

那么访问/admin//page是无法匹配的:

0x03 关键属性

3.1 SuffixPatternMatch/TrailingSlashMatch(后缀/结尾匹配模式)

前面提到的模式匹配的两个属性SuffixPatternMatch&TrailingSlashMatch。看看具体的代码实现:

SuffixPatternMatch是后缀匹配模式,用于能以 .xxx 结尾的方式进行匹配。这里46对应的Ascii码是.,根据具体代码可以知道,当启用后缀匹配模式时,例如/hello和/hello.do的匹配结果是一样的:

TrailingSlashMatch为true时,会应用尾部的/匹配,例如/hello和/hello/的匹配结果是一样的:

3.1.1 各版本差异

5.3后相关useSuffixPatternMatch的默认值会由true变为false,参考https://github.com/spring-projects/spring-framework/issues/23915

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping中可以看到对应属性的改变:

  • spring-webmvc:5.3.9

  • spring-webmvc-5.2.22.RELEASE

3.2 alwaysUseFullPath

alwaysUseFullPath主要用于判断是否使用servlet context中的全路径匹配处理器。

3.2.1 各版本差异

WebMvcAutoConfiguration是Spring Boot中关于Spring MVC自动配置类。在org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration#configurePathMatch方法中可以配置URL路径的匹配规则。

主要是这两个分界点:

  • spring-boot-autoconfigure-2.3.0.RELEASE

在2.3.0以及之前版本,在configurePathMatch中,没有对UrlPathHelper的alwaysUseFullPath属性进行设置,默认为False:

  • spring-boot-autoconfigure-2.3.1.RELEAS

在2.3.1及之后版本,在configurePathMatch方法中,通过实例化UrlPathHelper对象并调用对应的setAlwaysUseFullPath方法将alwaysUseFullPath属性设置为true:

3.2.2 getPathWithinServletMapping方法

接之前Spring MVC发接收到请求时的分析,alwaysUseFullPath属性不同的值将会决定是否经过getPathWithinServletMapping方法处理。这里以2.3.0.RELEASE版本为例,其值默认为false,会经过getPathWithinServletMapping方法进行处理,跟进查看具体的过程:

首先会调用getPathWithinApplication方法进行处理,前面已经分析过具体的行为了,主要是进行了URI解码、移除分号内容并清理斜线等一系列操作,不再赘述:

跟getPathWithinApplication不同的是,getPathWithinServletMapping会获取ServletPath并进行对应的处理,这里主要是调用request.getServletPath(主要是对uri标准化处理,例如解码然后处理跨目录等一系列操作)方法:

再往后就是一系列熟悉的操作了,例如调用getSanitizedPath方法将//替换为/。调用getRemainingPath进行处理。还有request.getPathInfo()等一系列的组合后,返回对应的值给lookupPath,然后就是熟悉的操作了,调用lookupHandlerMethod方法,遍历所有的ReuqestMappingInfo对象并进行匹配,进行对应的解析。

3.2.3 与getPathWithinApplication的区别

根据前面的分析,getPathWithinServletMapping会对uri进行标准化处理(也就是说当SpringBoot 版本在小于等于2.3.0.RELEASE时,会对路径进行规范化处理),而getPathWithinApplication是通过request.getRequestURI()方法获取当前request中的URI/URL,并不会对获取到的内容进行规范化处理。

当请求路径中包括类似..的关键词时,调用getPathWithinApplication方法解析后,会因为没有处理跨目录的字符,导致找不到对应的Handler而返回404。

看一个具体的实例,注册的路由如下,尝试访问/file/../hello路径,看看不同版本的解析情况:

@GetMapping({"/hello"})
public String index() {
return "hello";
}

当alwaysUseFullPath为false时,调用了getPathWithinServletMapping进行处理,跨目录字符解码并规范化后,成功匹配对应的handler并访问成功:

当alwaysUseFullPath为true时,调用的是getPathWithinApplication,没有对跨目录进行标准化处理,最终找不到对应的handler,返回404状态码:

这里也解释了类似平时审计中遇到的一些鉴权措施缺陷,为什么没办法结合../进行绕过的原因。例如如下的例子:

在Filter中以/system/login开头的接口是白名单,不需要进行访问控制(登陆页面所有人都可以访问),其他接口都需要进行登陆检查,防止未授权访问:

    String uri = request.getRequestURI();
if(uri.startsWith("/system/login")) {
//登陆接口设置白名单
filterChain.doFilter(request, response);
}
.....
.....

从代码上看确实可以通过构造类似/system/login/../admin/userInfo的方式进行访问,绕过鉴权Filter的处理,但是在后续解析时,若当前alwaysUseFullPath为true时,此时解析调用的是getPathWithinApplication,不会对跨目录进行标准化处理,最终找不到对应的handler,返回404状态码,即使绕过了Filter也没办法进行漏洞利用。

0x04 解析器

4.1 AntPathMatcher&PathPattern

4.1.1 各版本差异

org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration是Spring Boot中关于Spring MVC自动配置类,对比下2.6之前之后的两个版本,可以发现2.6及之后版本多了个PathPatternParser的实现:

此外,WebMvcAutoConfiguration自动配置类中包含了一个静态类WebMvcAutoConfigurationAdapter,通过这里加载的WebMvcProperties内容也可以看出来具体的差异:

  • 在 2.6之前,默认使用的是AntPathMatcher(具体配置在org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties.Pathmatch),查看具体的代码:

  • 2.6.0及之后就变成了PathPattern了:

4.1.2 AntPathMatcher

AntPathMatcher所属模块为spring-core,对应classorg.springframework.util.AntPathMatcher。一般用于类路径、文件系统和其它资源的解析。

查看官方文档,可以知道AntPathMatcher支持的Path匹配规则如下:

规则作用
匹配任意单字符
*匹配0或者任意数量的字符
**匹配0或者任意层级的目录
{spring:正则表达式}匹配到的path内容赋值给spring变量

简单分析下具体的解析过程:

2.6之前的Spring会使用PatternsRequestCondition通过AntPathMatcher来进行URL匹配:

具体的匹配在org.springframework.util.AntPathMatcher#doMatch方法,首先调用tokenizePattern()方法将pattern分割成了String数组,如果是全路径并且区分大小写,那么就通过简单的字符串检查,看看path是否有潜在匹配的可能,没有的话返回false:

然后调用tokenizePath()方法将需要匹配的path分割成string数组,主要是通过java.util 里面的StringTokenizer来处理字符串:

这里有个属性trimTokens(从Spring Framework 4.3.0+开始, AntPathMatcher将 trimTokens 设置为false):

可以看到这个属性主要是用于消除path中的空格(之前由于与SpringSecurity的解析差异导致了CVE-2016-5007、CVE-2020-17523):

后面就是pathDirs和pattDirs两个数组从左到右开始匹配,主要是一些正则的转换还有通配符的匹配。例如/admin/*的*实际上是正则表达式.*通过java.util.regex.compile#matcher进行匹配:

4.1.3 PathPattern

PathPattern是Spring5新增的API,所属模块为spring-web,对应class org.springframework.web.util.pattern.PathPattern

查看官方文档:

Representation of a parsed path pattern. Includes a chain of path elements for fast matching and accumulates computed state for quick comparison of patterns.

PathPattern matches URL paths using the following rules:

  • ? matches one character

  • * matches zero or more characters within a path segment

  • ** matches zero or more path segments until the end of the path

  • {spring} matches a path segment and captures it as a variable named "spring"

  • {spring:[a-z]+} matches the regexp [a-z]+ as a path variable named "spring"

  • {*spring} matches zero or more path segments until the end of the path and captures it as a variable named "spring"

Note: In contrast to [AntPathMatcher](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/AntPathMatcher.html)** is supported only at the end of a pattern. For example /pages/{} is valid but /pages/{}/details is not. The same applies also to the capturing variant {*spring}. The aim is to eliminate ambiguity when comparing patterns for specificity.

根据官方文档的描述,其实**跟AntPathMatcher匹配规则区别不大,PathPattern在保持其匹配规则的基础上,新增了{*spring}的语法支持。**

{*spring}表示匹配余下的path路径部分并将其赋值给名为spring的变量(变量名可以根据实际情况随意命名,与@PathVariable名称对应即可)。同时,**{*spring}是可以匹配剩余所有path的,类似/**,只是功能更强,可以获取到这部分动态匹配到的内容。**

简单分析下具体的解析过程:

2.6以及之后的Spring会使用PathPatternsRequestCondition通过PathPattern来进行URL匹配:

可以看到跟之前版本使用的PatternsRequestCondition不同的是,此时的路径解析已经不受到类似SuffixPatternMatch属性的影响了:

主要在org.springframework.web.util.pattern.PathPattern#matches方法:

首先会根据/将URL拆分成多个PathElement对象,以/admin/index/为例,这里会分割成多个对象,然后根据PathPattern的链式节点中对应的PathElement的matches方法逐个进行匹配:

以Pattern为/admin/*为例,首先第一个元素是分隔符/,会调用SeparatorPathElement的matches方法进行处理:

处理完后pathIndex++,继续遍历下一个元素进行处理,下一个是admin,会通过LiteralPathElement#matches进行处理,同样的最后会对pathindex进行+1,然后继续遍历PathElement元素直到遍历结束为止:

在最后会根据matchOptionalTrailingSeparator(此参数为true时,默认为true)进行一定的处理,如果Pattern尾部没有斜杠,请求路径有尾部斜杠也能成功匹配(类似TrailingSlashMatch的作用):

所以这里/admin/index和/admin/index/都是可以访问到对应的路由的。

除此之外,根据不同Pattern的写法,还有很多PathElement。

4.1.4 两者的区别

首先,PathPattern新增{*spring}语法支持,功能更加的强大。除此以外,相比AntPathMatcher,还有以下区别:

  • PathPattern通配符只能定义在尾部,而AntPathMatcher可以在中间:

  • AntPathMatcher默认使用/作为分隔符。也可以根据实际情况自行指定分隔符(例如windows是\,Linux是/,包名是.),这点从其构造器可以看出:

因为PathPattern的构造器不是public的,只能通过PathPatternParser创建其实例,这里构造方法初始化了pathOptions变量:

查看Options.HTTP_PATH,可以看到跟AntPathMatcher一样,默认使用/作为分隔符:

但是PathPattern只支持两种分隔符(/和.)

原文地址:https://forum.butian.net/share/2214

声明:⽂中所涉及的技术、思路和⼯具仅供以安全为⽬的的学习交流使⽤,任何⼈不得将其⽤于⾮法⽤途以及盈利等⽬的,否则后果⾃⾏承担。所有渗透都需获取授权

@
学习更多渗透技能!体验靶场实战练习


hack视频资料及工具

(部分展示)


往期推荐

给第一次做渗透项目的新手总结的一些感悟

「登陆页面」常见的几种渗透思路与总结!

突破口!入职安服后的经验之谈

红队渗透下的入口权限快速获取

攻防演练|红队手段之将蓝队逼到关站!

CNVD 之5000w通用产品的收集(fofa)

自动化挖掘cnvd证书脚本

Xray捡洞中的高频漏洞

实战|通过供应链一举拿下目标后台权限

实战|一次真实的域渗透拿下域控(内网渗透)

看到这里了,点个“赞”、“再看”吧



文章引用微信公众号"白帽子左一",如有侵权,请联系管理员删除!

博客评论
还没有人评论,赶紧抢个沙发~
发表评论
说明:请文明发言,共建和谐网络,您的个人信息不会被公开显示。