VYPR
Moderate severityNVD Advisory· Published Mar 23, 2023· Updated Feb 25, 2025

CVE-2023-20861

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.

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.

PackageAffected versionsPatched versions
org.springframework:spring-expressionMaven
>= 6.0.0, < 6.0.76.0.7
org.springframework:spring-expressionMaven
>= 5.3.0, < 5.3.265.3.26
org.springframework:spring-expressionMaven
< 5.2.23.RELEASE5.2.23.RELEASE

Affected products

2

Patches

3
430fc25acad2

Increase 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");
    
52c93b1c4b24

Increase 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");
    
935c29e3ddba

Increase 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

News mentions

0

No linked articles in our index yet.