SpringSecurity(CVE-2022-22978)权限校验分析
date
Jun 8, 2022
slug
SpringSecurity-CVE-2022-22978
status
Published
tags
Java安全
安全研究
summary
关于SpringSecurity权限校验的一个洞,可以拿来当一个trick之类的吧,之前没整过SpringSecurity,自己搭建一次熟悉一下,影响了5.5.7 之前的 5.5.x5.6.4 之前的 5.6.x早期不支持的版本
type
Post
关于SpringSecurity权限校验的一个洞,可以拿来当一个trick之类的吧,之前没整过SpringSecurity,自己搭建一次熟悉一下,影响了5.5.7 之前的 5.5.x 5.6.4 之前的 5.6.x早期不支持的版本
在漏洞公告上说是RegexRequestMatcher这个东西出了问题
然后看github上的diff,发现只修了一个文件,然后添加了两个测试案例,根据上一次Spring Cloud Function RCE 的调试经验来看,这个test案例里面内容就是绕过检测的测试样例了,使用了%0a或者%0d进行绕过
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F06b819be-0824-4488-9dc4-01d0fdf163f2%2FUntitled.png%3Fid%3De7c8e466-30c7-43a2-99ae-1243d4aa0a02%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DW5bkFpqwOHTKKlOMb_xFJnj9zPIHJ_7vygEhVuE4SMQ?table=block&id=e7c8e466-30c7-43a2-99ae-1243d4aa0a02&cache=v2)
搭建环境 && 复现
新建一个Springboot项目,然后在pom文件中引入SpringSecurity
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
将SpringSecurity版本切换至5.6.3版本
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F0e0bae6d-2edd-483b-8a37-b48004c3c08d%2FUntitled.png%3Fid%3Df920f4d6-3fd0-47e4-8a1e-5fa6be4f7ad8%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3Di7FHV2bVx_-GNMn4QU786H2QsGlTuQ4siMdpsEgurek?table=block&id=f920f4d6-3fd0-47e4-8a1e-5fa6be4f7ad8&cache=v2)
创建controller
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Ffbeefc0f-16f3-4ede-8559-4414d29b0bfb%2FUntitled.png%3Fid%3D61096308-8bb1-4c3a-a4d3-cbb3fc03e9eb%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DcmEXBnkNj0BkwQh7MxBkRBJm2PLC0r9b5wjJ8ziO4Sw?table=block&id=61096308-8bb1-4c3a-a4d3-cbb3fc03e9eb&cache=v2)
使用SpringSecurity设置相关权限,这个跟Shiro的Relam有点像,但是这里我们需要继承
WebSecurityConfigurerAdapter
并实现其中的configure
方法![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F5f2b4731-c22e-4851-850f-b6de180a1703%2FUntitled.png%3Fid%3D78046e1f-44e8-4140-91e3-9c1e6bef5b71%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DfPikIpcKQQrsIj6IIzn4MvRjxnmbk2C-TqQEKSWE8xo?table=block&id=78046e1f-44e8-4140-91e3-9c1e6bef5b71&cache=v2)
这里使用了正则去匹配admin路由下的内容,涉及到的函数:
http.authorizeRequests()
:主要是对url进行访问权限控制,通过这个方法来实现url授权操作。authenticated()
:是访问控制方法的一种,表示所匹配的URL都需要被认证才能访问
此时授权与未授权的接口访问是不一样的
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Fef2f71d2-c8ca-4b81-a5b9-cdbddda732a5%2FUntitled.png%3Fid%3D029c9780-a39a-469f-a5d3-6ef3c67238ac%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3D_plhjVNOO8rWyxk8lH16bp50rWAP6qWmUQcx5gP0L24?table=block&id=029c9780-a39a-469f-a5d3-6ef3c67238ac&cache=v2)
根据单元测试里面的payload,在路由最后添加上%0a或者%0d就能够绕过了
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Fbe2433a3-ebb2-40f3-93fb-5ad29040aca9%2FUntitled.png%3Fid%3D17c0bd7d-75f5-4df1-9f32-7557e0412510%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DDaBgt2jWorZ0Fnqq3WSHY3Bej89MpfcX3F2LtHwjvMw?table=block&id=17c0bd7d-75f5-4df1-9f32-7557e0412510&cache=v2)
修复分析
观察修复内容,在
RegexRequestMatcher.java
文件的修复是增加了对换行符的匹配以及忽略大小写Pattern.DOTALL
:表示更改.
的含义,使它与每一个字符匹配(包括换行符\n),默认情况下, 正则表达式中点(.
)不会匹配换行符, 设置了Pattern.DOTALL
模式, 才会匹配所有字符包括换行符。Pattern.CASE_INSENSITIVE
:忽略大小写。
写个测试样例看一下,可以观察到非
Pattern.DOTALL
模式下是直接给忽略掉了中间匹配的内容,最后导致并没有匹配输出,这也是导致bypass的本质原因,而Pattern.DOTALL
就能正常匹配内容了,所以修复方案使用了这种方式,保证\n\r
匹配上![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F2a89f0fa-b59d-4edc-a80d-b89850e4a999%2FUntitled.png%3Fid%3Dbbefd042-8a8c-4579-a7a8-7fc3bbea1d56%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DHssjU5k0uTfExT7l-St5tLz0x6cgy3eAeC0QFPkyeu0?table=block&id=bbefd042-8a8c-4579-a7a8-7fc3bbea1d56&cache=v2)
StrictHttpFirewall
在看到这位老哥的分析文章的时候看到
StrictHttpFirewall
这个名词,又学到一个新鲜玩意了这东西好像是SpringSecurity自带的东西,参考:Spring Security 自带防火墙!你都不知道自己的系统有多安全! - 简书 (jianshu.com)
在 SpringSecurity 中提供了一个
HttpFirewall
接口,是一个请求防火墙,它可以自动处理掉一些非法请求,该接口具体实现了两个类,一个是严格模式的防火墙设置(StrictHttpFirewall),还有一个默认防火墙设置(DefaultHttpFirewall),Spring Security 中默认使用的是 StrictHttpFirewall。![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Fcdcb6de8-d874-486e-8c4b-f74046cd8ed9%2FUntitled.png%3Fid%3D05bd6cbf-8eb8-4917-830a-2487669474e8%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DpNu9yYGJH8uTVojYPgX6BD354-CrmFeIYVth3xh7Sjk?table=block&id=05bd6cbf-8eb8-4917-830a-2487669474e8&cache=v2)
这个类里面设置一堆的限制内容,具体地可以查看源码:
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F1c65370b-8bde-49bf-9d8c-470bae9e0701%2FUntitled.png%3Fid%3Dfecd1ba3-94ba-46db-9a03-5b97f6187865%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DFmDG6SL0Q2EwlBS55cUYv7EFzwkPRx_vmyO_XTfqrNE?table=block&id=fecd1ba3-94ba-46db-9a03-5b97f6187865&cache=v2)
如果需要修改这些限制,只需要自己提供一个 StrictHttpFirewall 实例,大都是这样的格式,具体地也在那篇文章里描述了
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setxxxxxxxxxxx(true);
总的来说,这个东西限制住了很多东西,比如平常在Tomcat场景下的一些绕过方法比如
/../
,./
,;/
不允许出现,就算这些东西编码了,也还是会被杀掉,感觉这东西还是比较牛逼的,但是%0a,%0d
好像并没有出现在这段代码里面,可能这就是这个东西在这个CVE场景下没有其作用的原因吧再仔细瞅瞅SpringSecurity是怎么处理路由的
调试过程关于
SpringSecurity
认证处理的部分![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F0bd04b54-1dd5-46dc-af4e-f8090bf5f804%2FUntitled.png%3Fid%3Dcc17bbb7-54d4-4291-b639-038169879379%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DOjb5xjSRuDoM4IQjLW6wxBPZdWfEmqTD5LmDCaVfxzM?table=block&id=cc17bbb7-54d4-4291-b639-038169879379&cache=v2)
数据流会从
org.springframework.security.web.access.intercept.RequestMatcherDelegatingAuthorizationManager#check
函数进入,而SpringSecurity 路径正则匹配的关键点在org.springframework.security.web.util.matcher.RegexRequestMatcher#matches
函数里面,注释也说了这个函数的作用:Performs the match of the request URL (servletPath + pathInfo + queryString ) against the compiled pattern. If the query string is present, a question mark will be prepended.
其实本质上又是用了
HttpServletRequest
那几个函数进行重新构造然后再合成字符串,然后再去正则匹配,前面的处理没问题,这个CVE最后问题是出现正则匹配上![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F701a1f92-5403-43a4-959b-6ce1210383ce%2FUntitled.png%3Fid%3Dfb751361-984a-47f3-bae8-3793d21e6799%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DynV8KCIelhcQHMVMdcJQRpuejMnn--dEsT9ASDmIBO0?table=block&id=fb751361-984a-47f3-bae8-3793d21e6799&cache=v2)
总结
- %0a,%0d的Bypass方式,前提是正则匹配规则是类似这样的形式
RegexRequestMatcher("/aaa/.*", null)
- SpringSecurity自身的一些特性,比如
Firewall