CVE-2023-20861
Description
Spring Framework SpEL 'matches' operator can be exploited with crafted expressions to cause denial-of-service via inefficient regex pattern compilation.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Spring Framework SpEL 'matches' operator can be exploited with crafted expressions to cause denial-of-service via inefficient regex pattern compilation.
Vulnerability
Description CVE-2023-20861 is a denial-of-service (DoS) vulnerability in the Spring Framework's SpEL (Spring Expression Language) implementation. The flaw resides in the OperatorMatches class, which handles the matches operator for regex evaluation. Prior to the fix, each matches operation compiled a new Pattern object without caching, allowing an attacker to craft a SpEL expression that repeatedly triggers regex compilation, leading to excessive CPU and memory consumption [1]. The official advisory notes that versions 6.0.0-6.0.6, 5.3.0-5.3.25, 5.2.0.RELEASE-5.2.22.RELEASE, and older unsupported releases are affected [1].
Exploitation
Conditions The vulnerability can be triggered when an attacker can supply a malicious SpEL expression to an application that evaluates Spring expressions, such as those using @Value annotations, XML configuration, or template rendering. The attacker does not need authentication if the application exposes expression evaluation to unauthenticated users. A single HTTP request containing a SpEL expression that includes many matches operations, especially with complex regex patterns, can cause the server to spend significant time compiling patterns, leading to resource exhaustion [2][3][4]. The fix addresses this by introducing a shared pattern cache (ConcurrentHashMap) that reduces redundant compilations.
Impact
Successful exploitation results in a denial-of-service condition, making the application unresponsive or crashing due to high CPU usage and memory exhaustion. The severity is rated as high (CVSS score 7.5) because it can be exploited remotely with low complexity and requires no privileges [1].
Mitigation
Users should upgrade to Spring Framework versions 6.0.7, 5.3.26, 5.2.23.RELEASE, or later, where the shared pattern cache for the matches operator is implemented [2][3][4]. No workaround is available; patching is the recommended action.
- NVD - CVE-2023-20861
- Increase scope of regex pattern cache for the SpEL `matches` operator · spring-projects/spring-framework@52c93b1
- Increase scope of regex pattern cache for the SpEL `matches` operator · spring-projects/spring-framework@430fc25
- Increase scope of regex pattern cache for the SpEL `matches` operator · spring-projects/spring-framework@935c29e
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.springframework:spring-expressionMaven | >= 6.0.0, < 6.0.7 | 6.0.7 |
org.springframework:spring-expressionMaven | >= 5.3.0, < 5.3.26 | 5.3.26 |
org.springframework:spring-expressionMaven | < 5.2.23.RELEASE | 5.2.23.RELEASE |
Affected products
2Patches
3430fc25acad2Increase scope of regex pattern cache for the SpEL `matches` operator
2 files changed · +28 −6
spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorMatches.java+20 −4 modified@@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,19 +36,35 @@ * * @author Andy Clement * @author Juergen Hoeller + * @author Sam Brannen * @since 3.0 */ public class OperatorMatches extends Operator { private static final int PATTERN_ACCESS_THRESHOLD = 1000000; - private final ConcurrentMap<String, Pattern> patternCache = new ConcurrentHashMap<>(); + private final ConcurrentMap<String, Pattern> patternCache; + /** + * Create a new {@link OperatorMatches} instance. + * @deprecated as of Spring Framework 5.3.26 in favor of invoking + * {@link #OperatorMatches(ConcurrentMap, int, int, SpelNodeImpl...)} + * with a shared pattern cache instead + */ + @Deprecated public OperatorMatches(int startPos, int endPos, SpelNodeImpl... operands) { - super("matches", startPos, endPos, operands); + this(new ConcurrentHashMap<>(), startPos, endPos, operands); } + /** + * Create a new {@link OperatorMatches} instance with a shared pattern cache. + * @since 5.3.26 + */ + public OperatorMatches(ConcurrentMap<String, Pattern> patternCache, int startPos, int endPos, SpelNodeImpl... operands) { + super("matches", startPos, endPos, operands); + this.patternCache = patternCache; + } /** * Check the first operand matches the regex specified as the second operand. @@ -63,7 +79,7 @@ public BooleanTypedValue getValueInternal(ExpressionState state) throws Evaluati SpelNodeImpl leftOp = getLeftOperand(); SpelNodeImpl rightOp = getRightOperand(); String left = leftOp.getValue(state, String.class); - Object right = getRightOperand().getValue(state); + Object right = rightOp.getValue(state); if (left == null) { throw new SpelEvaluationException(leftOp.getStartPosition(),
spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java+8 −2 modified@@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ import java.util.Collections; import java.util.Deque; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.regex.Pattern; import org.springframework.expression.ParseException; @@ -83,6 +85,7 @@ * @author Andy Clement * @author Juergen Hoeller * @author Phillip Webb + * @author Sam Brannen * @since 3.0 */ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { @@ -95,6 +98,9 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { // For rules that build nodes, they are stacked here for return private final Deque<SpelNodeImpl> constructedNodes = new ArrayDeque<>(); + // Shared cache for compiled regex patterns + private final ConcurrentMap<String, Pattern> patternCache = new ConcurrentHashMap<>(); + // The expression being parsed private String expressionString = ""; @@ -248,7 +254,7 @@ private SpelNodeImpl eatRelationalExpression() { } if (tk == TokenKind.MATCHES) { - return new OperatorMatches(t.startPos, t.endPos, expr, rhExpr); + return new OperatorMatches(this.patternCache, t.startPos, t.endPos, expr, rhExpr); } Assert.isTrue(tk == TokenKind.BETWEEN, "Between token expected");
52c93b1c4b24Increase scope of regex pattern cache for the SpEL `matches` operator
2 files changed · +28 −6
spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorMatches.java+20 −4 modified@@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,19 +36,35 @@ * * @author Andy Clement * @author Juergen Hoeller + * @author Sam Brannen * @since 3.0 */ public class OperatorMatches extends Operator { private static final int PATTERN_ACCESS_THRESHOLD = 1000000; - private final ConcurrentMap<String, Pattern> patternCache = new ConcurrentHashMap<>(); + private final ConcurrentMap<String, Pattern> patternCache; + /** + * Create a new {@link OperatorMatches} instance. + * @deprecated as of Spring Framework 5.2.23 in favor of invoking + * {@link #OperatorMatches(ConcurrentMap, int, int, SpelNodeImpl...)} + * with a shared pattern cache instead + */ + @Deprecated public OperatorMatches(int startPos, int endPos, SpelNodeImpl... operands) { - super("matches", startPos, endPos, operands); + this(new ConcurrentHashMap<>(), startPos, endPos, operands); } + /** + * Create a new {@link OperatorMatches} instance with a shared pattern cache. + * @since 5.2.23 + */ + public OperatorMatches(ConcurrentMap<String, Pattern> patternCache, int startPos, int endPos, SpelNodeImpl... operands) { + super("matches", startPos, endPos, operands); + this.patternCache = patternCache; + } /** * Check the first operand matches the regex specified as the second operand. @@ -63,7 +79,7 @@ public BooleanTypedValue getValueInternal(ExpressionState state) throws Evaluati SpelNodeImpl leftOp = getLeftOperand(); SpelNodeImpl rightOp = getRightOperand(); String left = leftOp.getValue(state, String.class); - Object right = getRightOperand().getValue(state); + Object right = rightOp.getValue(state); if (left == null) { throw new SpelEvaluationException(leftOp.getStartPosition(),
spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java+8 −2 modified@@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ import java.util.Collections; import java.util.Deque; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.regex.Pattern; import org.springframework.expression.ParseException; @@ -83,6 +85,7 @@ * @author Andy Clement * @author Juergen Hoeller * @author Phillip Webb + * @author Sam Brannen * @since 3.0 */ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { @@ -95,6 +98,9 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { // For rules that build nodes, they are stacked here for return private final Deque<SpelNodeImpl> constructedNodes = new ArrayDeque<>(); + // Shared cache for compiled regex patterns + private final ConcurrentMap<String, Pattern> patternCache = new ConcurrentHashMap<>(); + // The expression being parsed private String expressionString = ""; @@ -248,7 +254,7 @@ private SpelNodeImpl eatRelationalExpression() { } if (tk == TokenKind.MATCHES) { - return new OperatorMatches(t.startPos, t.endPos, expr, rhExpr); + return new OperatorMatches(this.patternCache, t.startPos, t.endPos, expr, rhExpr); } Assert.isTrue(tk == TokenKind.BETWEEN, "Between token expected");
935c29e3ddbaIncrease scope of regex pattern cache for the SpEL `matches` operator
2 files changed · +26 −5
spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorMatches.java+18 −3 modified@@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,13 +43,28 @@ public class OperatorMatches extends Operator { private static final int PATTERN_ACCESS_THRESHOLD = 1000000; - private final ConcurrentMap<String, Pattern> patternCache = new ConcurrentHashMap<>(); + private final ConcurrentMap<String, Pattern> patternCache; + /** + * Create a new {@link OperatorMatches} instance. + * @deprecated as of Spring Framework 5.3.26 in favor of invoking + * {@link #OperatorMatches(ConcurrentMap, int, int, SpelNodeImpl...)} + * with a shared pattern cache instead + */ + @Deprecated(since = "5.3.26") public OperatorMatches(int startPos, int endPos, SpelNodeImpl... operands) { - super("matches", startPos, endPos, operands); + this(new ConcurrentHashMap<>(), startPos, endPos, operands); } + /** + * Create a new {@link OperatorMatches} instance with a shared pattern cache. + * @since 5.3.26 + */ + public OperatorMatches(ConcurrentMap<String, Pattern> patternCache, int startPos, int endPos, SpelNodeImpl... operands) { + super("matches", startPos, endPos, operands); + this.patternCache = patternCache; + } /** * Check the first operand matches the regex specified as the second operand.
spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java+8 −2 modified@@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ import java.util.Collections; import java.util.Deque; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.regex.Pattern; import org.springframework.expression.ParseException; @@ -83,6 +85,7 @@ * @author Andy Clement * @author Juergen Hoeller * @author Phillip Webb + * @author Sam Brannen * @since 3.0 */ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { @@ -95,6 +98,9 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { // For rules that build nodes, they are stacked here for return private final Deque<SpelNodeImpl> constructedNodes = new ArrayDeque<>(); + // Shared cache for compiled regex patterns + private final ConcurrentMap<String, Pattern> patternCache = new ConcurrentHashMap<>(); + // The expression being parsed private String expressionString = ""; @@ -248,7 +254,7 @@ private SpelNodeImpl eatRelationalExpression() { } if (tk == TokenKind.MATCHES) { - return new OperatorMatches(t.startPos, t.endPos, expr, rhExpr); + return new OperatorMatches(this.patternCache, t.startPos, t.endPos, expr, rhExpr); } Assert.isTrue(tk == TokenKind.BETWEEN, "Between token expected");
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- github.com/advisories/GHSA-564r-hj7v-mcr5ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-20861ghsaADVISORY
- github.com/spring-projects/spring-framework/commit/430fc25acad2e85cbdddcd52b64481691f03ebd1ghsaWEB
- github.com/spring-projects/spring-framework/commit/52c93b1c4b24d70de233a958e60e7c5822bd274fghsaWEB
- github.com/spring-projects/spring-framework/commit/935c29e3ddba5b19951e54f6685c70ed45d9cbe5ghsaWEB
- security.netapp.com/advisory/ntap-20230420-0007ghsaWEB
- spring.io/security/cve-2023-20861ghsaWEB
- security.netapp.com/advisory/ntap-20230420-0007/mitre
News mentions
0No linked articles in our index yet.