CVE-2026-41851

Content Spoofing
Affects
Spring Framework
in
Spring
No items found.
Versions
>=4.3.0 <=4.3.30, >=5.3.0 <=5.3.48, >=6.1.0 <=6.1.27, >=6.2.0 <=6.2.18, >=7.0.0 <=7.0.7
Exclamation circle icon
Patch Available

This Vulnerability has been fixed in the Never-Ending Support (NES) version offered by HeroDevs.

Overview

Spring Framework is the foundational application framework for the Java ecosystem, providing dependency injection, data access, web, and expression-evaluation support that underpins Spring Boot and most of the Spring portfolio. One of its components is the Spring Expression Language (SpEL), a runtime expression language used throughout Spring (for example in annotations such as @Value, in bean definitions, and in security and data-binding rules) and shipped in the spring-expression module. SpEL supports a matches operator that tests a string against a Java regular expression, and to avoid recompiling the same regex on every evaluation it caches the compiled java.util.regex.Pattern objects.

A medium-severity vulnerability (CVE-2026-41851) has been identified in that pattern cache. Applications that accept and evaluate user-supplied SpEL expressions are exposed because the cache that holds compiled regex patterns is an unbounded ConcurrentHashMap keyed by the regex string. Every distinct right-hand-side regex an attacker supplies to the matches operator becomes a new, permanent cache entry. By submitting a large number of distinct expressions, an attacker drives unbounded growth of that cache, exhausting the JVM heap and degrading or disabling the application (an OutOfMemoryError-style memory-exhaustion Denial of Service).

Per OWASP, a Denial of Service (DoS) attack is focused on making a resource (site, application, server) unavailable for the purpose it was designed; there are many ways to make a service unavailable for legitimate users by manipulating network packets, programming, logical, or resource-handling vulnerabilities, among others. Here the abused resource is heap memory: an unbounded cache that grows with the number of distinct attacker-supplied regular expressions is exactly the resource-handling failure mode that this category describes.

The CVSS v3.1 base score for this vulnerability is Medium with vector AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L. The attack vector is Network, attack complexity and privileges required are Low and None because an application that evaluates untrusted SpEL exposes the cache directly, and there is no user interaction. The impact is Availability-only and Low (memory pressure that degrades or eventually halts the application); there is no confidentiality or integrity impact because the cache only grows, it does not leak or alter data.

This issue affects Spring Framework >=4.3.0 <=4.3.30, >=5.3.0 <=5.3.48, >=6.1.0 <=6.1.27, >=6.2.0 <=6.2.18, and >=7.0.0 <=7.0.7. The vulnerability is only reachable in applications that evaluate user-supplied SpEL expressions; applications that only evaluate trusted, developer-authored expressions are not exposed.

Details

Module Info

Vulnerability Info

The vulnerability is in the SpEL matches operator, implemented by OperatorMatches in the spring-expression module, and in the shared pattern cache held by InternalSpelExpressionParser. When a SpEL expression of the form someInput matches someRegex is evaluated, the operator compiles the regex into a java.util.regex.Pattern and stores it in a cache keyed by the regex string so the next evaluation of the same regex can reuse it. The scope of the issue is limited to applications that evaluate user-supplied SpEL expressions; an application that only ever evaluates trusted expressions never feeds attacker-controlled regex strings into the cache.

Pre-fix, the cache was an unbounded ConcurrentHashMap. OperatorMatches held the cache as a ConcurrentMap and the no-arg constructor created a fresh ConcurrentHashMap, while InternalSpelExpressionParser held a shared ConcurrentHashMap for compiled patterns:

// OperatorMatches.java (pre-fix)
private final ConcurrentMap<String, Pattern> patternCache;

@Deprecated
public OperatorMatches(int startPos, int endPos, SpelNodeImpl... operands) {
    this(new ConcurrentHashMap<>(), startPos, endPos, operands);
}

// in getValueInternal(...):
Pattern pattern = this.patternCache.get(regex);
if (pattern == null) {
    checkRegexLength(regex);
    pattern = Pattern.compile(regex);
    this.patternCache.putIfAbsent(regex, pattern);
}


Because the cache has no maximum size and never evicts entries, every distinct regex string becomes a permanent entry. There was already a per-regex length limit (MAX_REGEX_LENGTH, 1000 characters), but that only caps the size of any single entry, not the number of entries. An attacker who can supply many distinct regex strings (each one well under the length limit, but each a unique cache key) drives the cache to grow without bound until the heap is exhausted.

The fix replaces the unbounded ConcurrentHashMap with a bounded ConcurrentLruCache capped at a fixed maximum number of compiled patterns, in both OperatorMatches and InternalSpelExpressionParser:

// OperatorMatches.java (post-fix)
/**
 * Maximum number of compiled regular expressions in the pattern cache: {@value}.
 */
public static final int MAX_PATTERN_CACHE_SIZE = 256;

private final ConcurrentLruCache<String, Pattern> patternCache;

@Deprecated(since = "5.2.23", forRemoval = true)
public OperatorMatches(int startPos, int endPos, SpelNodeImpl... operands) {
    this(new ConcurrentLruCache<>(MAX_PATTERN_CACHE_SIZE, Pattern::compile), startPos, endPos, operands);
}

// in getValueInternal(...): the regex length is checked up front, then the
// bounded LRU cache compiles and evicts entries beyond the cap:
if (regex.length() > MAX_REGEX_LENGTH) {
    throw new SpelEvaluationException(rightOp.getStartPosition(),
            SpelMessage.MAX_REGEX_LENGTH_EXCEEDED, MAX_REGEX_LENGTH);
}
Pattern pattern = this.patternCache.get(regex);


With the bounded ConcurrentLruCache, the cache holds at most MAX_PATTERN_CACHE_SIZE (256) compiled patterns and evicts the least-recently-used entry when full, so a stream of distinct attacker-supplied regexes can no longer grow memory without limit. The deprecated OperatorMatches(ConcurrentMap, ...) constructor is retained for binary compatibility but now ignores the supplied map and delegates to the bounded cache. The unbounded ConcurrentMap pattern cache has been part of OperatorMatches across the 5.3.x, 6.1.x, 6.2.x, and 7.0.x lines (the matches operator itself dates to Spring Framework 3.0). The advisory's listed range starting at 5.3.0 reflects Spring's currently-supported lines; older lines, including the lines covered by NES support, carry the identical unbounded cache and are also affected.

Steps To Reproduce

1. Build a minimal application that evaluates an untrusted SpEL expression supplied by the caller, for example:

   import org.springframework.expression.Expression;
   import org.springframework.expression.spel.standard.SpelExpressionParser;

   SpelExpressionParser parser = new SpelExpressionParser();

   void evaluateUserExpression(String userSuppliedExpression) {
       Expression expression = parser.parseExpression(userSuppliedExpression);
       expression.getValue();
   }


2. Drive the endpoint with many distinct matches expressions, each carrying a unique regular expression (each well under the 1000-character regex length limit), so that every request inserts a new, unique key into the pattern cache:

   for (int i = 0; i < 5_000_000; i++) {
       // Each iteration supplies a distinct regex string, so each becomes a
       // new permanent entry in the unbounded pattern cache.
       evaluateUserExpression("'x' matches 'attacker" + i + ".*'");
   }


3. Observe the JVM heap. On a pre-fix version the pattern cache grows without bound, retaining one compiled java.util.regex.Pattern per distinct regex string, until the application slows under GC pressure and ultimately fails with java.lang.OutOfMemoryError: Java heap space.

4. Repeat against a fixed version (6.2.19, 7.0.8, or a HeroDevs NES build). The pattern cache is now a bounded ConcurrentLruCache capped at 256 entries, so memory stays flat regardless of how many distinct regexes are supplied and no OutOfMemoryError occurs.

Mitigation

Only recent versions of Spring Framework receive community support and updates. Older versions, including the 5.3.x and 6.1.x lines, have no publicly available open source fix for this vulnerability.

Users of the affected components should apply one of the following mitigations:

  • Upgrade to a currently supported version of Spring Framework. The OSS fix ships in Spring Framework 6.2.19 (6.2.x line) and 7.0.8 (7.0.x line).
  • As an interim hardening step, avoid evaluating user-supplied SpEL expressions. Treat SpEL as code: restrict expression evaluation to trusted, developer-authored expressions, and do not pass untrusted input through parseExpression. This removes the attacker's ability to populate the cache with arbitrary distinct regexes.
  • Leverage a commercial support partner like HeroDevs for post-EOL security support through Never-Ending Support (NES) for Spring Framework, which provides patched builds of the 4.3.x, 5.3.x, and 6.1.x lines that include the bounded pattern cache.
Vulnerability Details
Severity
Level
CVSS Assessment
Low
>=0 <4
Medium
>=4 <6
High
>=6 <8
Critical
>=8 <10
Medium
ID
CVE-2026-41851
PROJECT Affected
Spring Framework
Versions Affected
>=4.3.0 <=4.3.30, >=5.3.0 <=5.3.48, >=6.1.0 <=6.1.27, >=6.2.0 <=6.2.18, >=7.0.0 <=7.0.7
NES Versions Affected
Published date
June 11, 2026
≈ Fix date
June 10, 2026
Category
Content Spoofing
Vex Document
Download VEXHow do I use it?
Sign up for the latest vulnerability alerts fixed in
NES for Spring
Rss feed icon
Subscribe via RSS
or

By clicking “submit” I acknowledge receipt of our Privacy Policy.

Thanks for signing up for our Newsletter! We look forward to connecting with you.
Oops! Something went wrong while submitting the form.