浅谈Spring Framework身份验证绕过漏洞(CVE-2023-20860)
渗透测试
Spring官方发布了Spring Framework 身份认证绕过漏洞(CVE-2023-20860),当Spring Security使用mvcRequestMatcher配置并将**作为匹配模式时,在Spring Security 和 Spring MVC 之间会发生模式不匹配,最终可能导致身份认证绕过。
0x01 漏洞描述 ========= Spring官方发布了**Spring Framework 身份认证绕过漏洞(CVE-2023-20860)**,当Spring Security使用mvcRequestMatcher配置并将`**`作为匹配模式时,在Spring Security 和 Spring MVC 之间会发生模式不匹配,最终可能导致身份认证绕过。 1.1 影响版本 -------- - Spring Framework 6.0.x <= 6.0.6 - Spring Framework 5.3.x <= 5.3.25 **(其他低于5.3.x的系列版本不受此漏洞影响)** 0x02 原理分析 ========= 2.1 MvcRequestMatcher --------------------- 根据漏洞描述,主要是mvcRequestMatcher的问题。参考https://docs.spring.io/spring-security/reference/servlet/integrations/mvc.html#mvc-requestmatcher 发现mvcRequestMatcher主要使用Spring MVC的HandlerMappingIntrospector来匹配路径并提取变量。相比AntPathRequestMatcher会更严谨。例如mvcMatchers("/index") ,除了匹配/index,对于/index/, /index.html, /index.do也会匹配。避免了AntPathRequestMatcher的绕过一些问题。 在查看mvcRequestMatcher首先简单描述下两个概念: - Pattern: ```java httpSecurity.authorizeRequests().mvcMatchers("admin/**").authenticated(); ``` 这里的`/admin/**`就是Pattern。 - Path:实际请求的路径 以spring-webmvc-5.3.20版本为例,查看具体实现,主要是在org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher#matches方法进行匹配:  首先会在notMatchMethodOrServletPath方法对请求方法以及servletPath进行简单的比对:  然后调用getMapping方法对当前请求进行处理(实际调用的是HandlerMappingIntrospector的getMatchableHandlerMapping方法):   主要是寻找能处理指定请求的HandlerMapping,继续往下跟进会发现实际调用的是org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal方法,然后调用lookupHandlerMethod方法,首先直接根据路径获取对应的Mapping,获取不到的话调用addMatchingMappings遍历所有的ReuqestMappingInfo对象并进行匹配,实际上就是spring web解析的逻辑:  获取到mapping后调用对应的match方法,跟pattern进行匹配,然后将匹配的结果封装在RequestMatchResult中返回:  继续跟进具体的match方法:  实际上调用的是org.springframework.web.servlet.handler.PathPatternMatchableHandlerMapping#match方法:  继续调用的PathPattern(根据影响的版本可以确定对应的Spring使用的是PathPattern进行解析)的matches方法将pattern跟path进行匹配:  而PathPattern首先会根据/将URL拆分成多个**PathElement**对象,以/admin/index/为例,这里会分割成多个对象,然后根据PathPattern的链式节点中对应的PathElement的matches方法逐个进行匹配。 简单地分析了mvcRequestMatcher以后,看看PathPattern的工作原理。 2.2 PathPattern --------------- 2.6以及之后的Spring会使用PathPatternsRequestCondition通过PathPattern来进行URL匹配。 主要在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: - WildcardPathElement(/api/\*) - SingleCharWildcardedPathElement(/api/?) - WildcardTheRestPathElement(/api/\*\*) - CaptureVariablePathElement(/api/{param}) - CaptureTheRestPathElement(/api/{\*param}) - LiteralPathElement(/api/index) - RegexPathElement(/api/.\*) 2.3 绕过分析 -------- 根据前面的分析,因为mvcRequestMatcher主要使用Spring MVC的HandlerMappingIntrospector来匹配路径并提取变量。猜测大致的问题也应该出现在这里。对比修复前后版本的HandlerMappingIntrospector代码: 这里的返回值从PathSettingHandlerMapping变成了LookupPathMatchableHandlerMapping:   结合前面的分析可知,修改的其实是在调用PathPattern的match方法前的处理。对比下代码可以发现,在调用PathPattern的match方法之前多了一个步骤,首先**判断pattern是否以`/`开头,如果不是的话进行补全**: - spring-webmvc-5.3.26  - spring-webmvc-6.0.7  根据前面的分析,做以下猜想,在Spring Controller中,以下两个路由访问是等价的: ```Java @GetMapping("/admin/*") @GetMapping("admin/*") ``` 当Spring Security使用mvcRequestMatcher模式进行权限控制时,如果对应的配置没有以`/`开头,根据前面的分析,猜测会因为解析差异导致绕过的问题。 例如如下配置,admin目录下的路由会经过认证处理,非认证通过的会返回403: ```Java @Override protected void configure(HttpSecurity httpSecurity) throws Exception{ httpSecurity.authorizeRequests().mvcMatchers("admin/**").authenticated(); } ``` Controller: ```Java @GetMapping("/admin/index") public String Manage(){ return "manage"; } ``` 根据前面对PathPattern的matches的分析,这里会根据PathPattern的链式节点中对应的PathElement的matches方法逐个进行匹配,当pattern为`admin/**`时,此时第一个Element是admin,对应LiteralPathElement#matches解析:  此时会获取path的第一个Element(如果访问的path是/admin/index,那么第一个Element是`/`):  而`/`明显不是PathSegment的实例,此时匹配失败会返回false,但是Spring Controller却能正常解析,那么便导致了绕过问题。  0x03 漏洞复现 ========= 根据前面的猜想,同样的是前面的case,当对应的配置没有以`/`开头,会因为解析差异导致绕过的问题,可以看到成功绕过了设置的Spring security规则,访问了/admin/index:  这里再做一个对比,将spring-webmvc切换到5.3.9版本,此时上述的case是无法绕过的:  0x04 其他 ======= 在Spring生态中,除了PathPattern以外,还有一种解析模式是AntPathMatcher。 同样的根据之前的分析,其实主要是PathPattern的解析方式问题,这里再做一个假设,同样是存在缺陷的版本spring-webmvc-5.3.20版本,此时通过properties配置切换匹配模式为AntPathMatcher: ```php spring.mvc.pathmatch.matching-strategy = ant_path_matcher ``` 此时发现没办法绕过了:  简单说下原因,主要是AntPathMatcher的实现,其大概方式是将需要匹配的path和Pattern分割成string数组,分别是pathDirs和pattDirs两个数组,然后从左到右开始匹配,主要是一些正则的转换还有通配符的匹配。例如/admin/\*的`*`实际上是正则表达式`.*`,然后通过java.util.regex.compile#matcher进行匹配,这里进行分割时不会将`/`作为内容引入分析,从调试信息也可以看到,在此之前也将`/`进行了补全:  将`/`进行了补全的操作主要是在RequestMappingHandlerMapping#match方法中进行的:  这里实际上调用的是RequestMappingInfo#build方法:  这里对Pattern进行了重新处理,在调用PatternsRequestCondition的构造方法的时候,调用了initPatterns方法:  如果Pattern不是以`/`开头会进行补全:  0x05 修复方式 ========= 目前官方已有可更新版本,建议升级至: - Spring Framework 6.0.x >= 6.0.7 - Spring Framework 5.3.x >= 5.3.26 同样是上面的case,将spring-webmvc版本升级到5.3.26后,上述case已无法绕过: ```XML <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.26</version> </dependency> ``` 
发表于 2023-03-30 09:00:02
阅读 ( 17589 )
分类:
漏洞分析
1 推荐
收藏
1 条评论
tkswifty
64 篇文章
×
温馨提示
您当前没有「奇安信攻防社区」的账号,注册后可获取更多的使用权限。
×
温馨提示
您当前没有「奇安信攻防社区」的账号,注册后可获取更多的使用权限。
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!