Analysis Java Spring Spring-security authorization bypass CVE-2022-22978 analysis S0cke3t 2022-06-01 2022-06-01 前言 Spring Security
是 Spring
家族中的一个安全管理框架。在 Spring Security
特定版本中存在一处身份认证绕过漏洞(CVE-2022-22978)。由于RegexRequestMatcher
正则表达式配置权限的特性,当在Spring Security
中使用RegexRequestMatcher
且规则中包含带点号的正则表达式时,攻击者可以通过构造恶意数据包绕过身份认证
影响版本 Spring Security 5.5.x < 5.5.7 Spring Security 5.6.x < 5.6.4
环境构建 目录结构
cc.saferoad.controller.Demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package cc.saferoad.controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;@RestController public class Demo { @GetMapping("/admin/*") public String Manage () { return "Manage page" ; } @GetMapping("/") public String User () { return package cc.saferoad.controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;@RestController public class Demo { @GetMapping("/admin/*") public String Manage () { return "Manage page" ; } @GetMapping("/") public String User () { return "Hello bro" ; } }
cc.saferoad.config.SpringSecurityConfig
自定义配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package cc.saferoad.config;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;@Configuration @EnableWebSecurity public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure (HttpSecurity httpSecurity) throws Exception{ httpSecurity.authorizeRequests().regexMatchers(package cc.saferoad.config;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;@Configuration @EnableWebSecurity public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure (HttpSecurity httpSecurity) throws Exception{ httpSecurity.authorizeRequests().regexMatchers("/admin/.*" ).authenticated(); } }
cc.saferoad.cve202222978.Cve202222978Application
1 2 3 4 5 6 7 8 9 10 11 12 13 package cc.saferoad.cve202222978;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication(scanBasePackages = {"cc.saferoad"}) public class Cve202222978Application { public static void main package cc.saferoad.cve202222978;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication(scanBasePackages = {"cc.saferoad"}) public class Cve202222978Application { public static void main (String[] args) { SpringApplication.run(Cve202222978Application.class, args); } }
cc.saferoad.cve202222978.RegexRequestMatcherTests
单元测试类,用于后面具体分析流程代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package cc.saferoad.cve202222978;import org.junit.jupiter.api.Test;import org.springframework.mock.web.MockHttpServletRequest;import org.springframework.security.web.util.matcher.RegexRequestMatcher;import static org.assertj.core.api.Assertions.assertThat;public class RegexRequestMatcherTests { @Test public void matchesWithLineFeed () { RegexRequestMatcher matcher = new RegexRequestMatcher (".*" , null ); MockHttpServletRequest request = new MockHttpServletRequest ("GET" , "/blah%0d" ); request.setServletPath(package cc.saferoad.cve202222978;import org.junit.jupiter.api.Test;import org.springframework.mock.web.MockHttpServletRequest;import org.springframework.security.web.util.matcher.RegexRequestMatcher;import static org.assertj.core.api.Assertions.assertThat;public class RegexRequestMatcherTests { @Test public void matchesWithLineFeed () { RegexRequestMatcher matcher = new RegexRequestMatcher (".*" , null ); MockHttpServletRequest request = new MockHttpServletRequest ("GET" , "/blah%0d" ); request.setServletPath("/blah\r" ); assertThat(matcher.matches(request)).isTrue(); } }
pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.7.0</version > <relativePath /> </parent > <groupId > cc.saferoad</groupId > <artifactId > CVE-2022-22978</artifactId > <version > 0.0.1-SNAPSHOT</version > <packaging > war</packaging > <name > CVE-2022-22978</name > <description > CVE-2022-22978</description > <properties > <java.version > 1.8</java.version > <spring-security.version > 5.6.3</spring-security.version > </properties > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-tomcat</artifactId > <scope > provided</scope > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-security</artifactId > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > <configuration > <jvmArguments > -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8989 </jvmArguments > </configuration > </plugin > </plugins > </build > </<?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.7.0</version > <relativePath /> </parent > <groupId > cc.saferoad</groupId > <artifactId > CVE-2022-22978</artifactId > <version > 0.0.1-SNAPSHOT</version > <packaging > war</packaging > <name > CVE-2022-22978</name > <description > CVE-2022-22978</description > <properties > <java.version > 1.8</java.version > <spring-security.version > 5.6.3</spring-security.version > </properties > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-tomcat</artifactId > <scope > provided</scope > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-security</artifactId > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > <configuration > <jvmArguments > -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8989 </jvmArguments > </configuration > </plugin > </plugins > </build > </project >
配置完成后,启动SpringBoot应用.访问http://localhost:8080
访问/admin/
路由会提示403
或者使用笔者构建的环境 https://github.com/DeEpinGh0st/CVE-2022-22978
漏洞复现 正常访问/admin/
下任何路由均会提示403
此时在/admin/anything
路由中插入%0a
或者%0d
,即可绕过验证访问页面
漏洞分析 使用上文中的单元测试样例,首先进行RegexRequestMatcher
实例化
根据构造参数初始化正则表达式及httpMethod
属性,注意此处Pattern.compile
参数值,对照JDK API
文档看一下具体参数含义
其中第一个参数表示要编译的表达式,而第二个参数则是指定表达式的具体匹配模式,具体可选值有
CASE_INSENSITIVE, MULTILINE, DOTALL, UNICODE_CASE, CANON_EQ, UNIX_LINES, LITERAL, UNICODE_CHARACTER_CLASS and COMMENTS
在RegexRequestMatcher
中由于caseInsensitive
设置为了false
,所以此处的值为0 代表使用默认状态
随后进入RegexRequestMatcher:matches
中在获取到传入的路由后进行路由匹配
此处我们需要注意一个点
在默认情况下正则表达式中的.
是不会匹配\r\n
换行符的,所以RegexRequestMatcher
在进行正则匹配时不会处理\r\n
从而可以绕过需要身份认证的页面
修复 在清楚具体绕过原理后,来看一下官方提交的修复措施
在5.6.4
的diff中官方将DEFAULT
默认匹配模式改为了Pattern.DOTALL
点阵模式
在点阵模式下表达式会匹配\r\n
等终止符,而在API文档中官方也进行了说明 默认情况下,此表达式与行终止符不匹配
而后也将Pattern.DOTALL
在开启大小写区分的情况下进行了组合,这样无论是否开启大小写模式均使用点阵模式进行匹配
参考 JDK8 API
5.6.3…5.6.4 Diff
Regexp-syntax