Skip to content
Publicly disclosedUncontrolled Resource Consumption / Denial of Service

CVE-2026-73635: Request-Locale Cache Exhaustion in Apache Struts

Request-controlled locale values could drive unbounded growth of Apache Struts localized-text caches and exhaust the Java heap when no fixed locale was configured.

Published

Advisory metadata
Product
Apache Struts
Component
Localized-text caches and request locale handling
Vulnerability class
Uncontrolled Resource Consumption / Denial of Service
Trust boundary
An unauthenticated request locale influencing process-wide localized-text cache keys.
Attack preconditions
A network-reachable Struts application that does not configure a fixed locale.
Impact
Unbounded cache growth, Java heap exhaustion, and denial of service to other users.
Root cause
Request-derived locales were used for localized-text lookups without bounding the corresponding caches.
Tested versions
Affected ranges documented in Apache S2-074
Affected versions
2.0.0–2.3.37, 2.5.0–2.5.33, 6.0.0–6.10.0, 7.0.0–7.2.1
Fixed versions
6.11.0, 7.3.0
Disclosure status
Published by Apache Software Foundation
CVE
CVE-2026-73635
JVN
JVN#08517956
Credit
Kuniyoshi Noguchi (野口 晋義), (@KuniNogu)

TL;DR

Apache Struts S2-074 documents a denial-of-service vulnerability in localized-text handling. When an application did not configure a fixed locale, Struts used the locale from the incoming request for lookups such as type-conversion and validation messages. The corresponding caches could grow without bound. An unauthenticated remote client could vary the request locale, exhaust the Java heap, and deny service to other users.

The affected ranges are 2.0.0–2.3.37, 2.5.0–2.5.33, 6.0.0–6.10.0, and 7.0.0–7.2.1. Upgrade to 6.11.0 or 7.3.0 at minimum. Apache's workaround is to set struts.locale to a fixed supported value.

JVN#08517956 records CVSS v4.0 8.7 and independently confirms the public reporter credit.

The important detection feature is not only request rate. It is the cardinality of request-derived values that create cache entries, correlated with heap growth and garbage-collection pressure.

Why it matters

This issue is a useful example of asymmetric resource consumption. A request can be small and valid-looking while causing the server to retain a new cache entry. Repeating the pattern with distinct values grows process-wide state even when total traffic is well below a conventional volumetric DoS threshold.

The same design risk appears in caches keyed by tenant IDs, model names, tool identifiers, callback IDs, header values, or user-selected locales. Any attacker-influenced key space needs both semantic validation and a hard resource bound. Authentication alone is not a memory-management control; S2-074 is reachable without authentication under the affected configuration.

Trust Boundary

The relevant data flow is:

remote request
  -> servlet request locale
  -> Struts localized-text lookup
  -> process-wide cache key / entry
  -> retained Java heap

When struts.locale is not fixed, the request locale crosses from a per-request, remote-controlled context into process-wide retained state. The trust boundary is therefore not just HTTP input parsing. It is the transition from ephemeral input to a cache whose lifetime and resource cost extend beyond the request.

Apache states that applications with a fixed locale are not affected because localized-text lookups no longer use the request-provided locale. On typical servlet stacks the request locale is derived from locale negotiation such as Accept-Language, but defenders should log the effective locale seen by the application, rather than assume one particular proxy or container behavior.

Root Cause

Two normal features composed unsafely:

  1. Struts selected a request-derived locale when no fixed locale was configured.
  2. Localized-text results were retained in internal caches without a size bound.

The vulnerability arose because an unauthenticated client could drive the cardinality of keys in process-wide caches. Cache growth was not constrained by the number of legitimate locales, a configured maximum, or an eviction policy with a finite capacity.

This is a resource-lifetime bug: validation must consider not only whether one locale value can be parsed, but also how many distinct values an attacker can cause the process to retain over time.

Safe reproducer

Do not test heap exhaustion against a shared or production service. A safe validation can establish the security property without taking a JVM down:

  1. Build a disposable Struts application in an isolated network and configure a deliberately small JVM heap plus a hard test timeout.
  2. Add one route that performs a normal localized-text lookup. Do not add an artificial allocation loop.
  3. Record the Struts version, struts.locale setting, effective request locale, heap use, GC activity, and response status.
  4. Send a small, fixed corpus of synthetic locale variants at a low rate. Keep the corpus and total request count below an approved lab budget.
  5. On 6.11.0 or 7.3.0, confirm that memory remains bounded and the application stays responsive. If cache metrics are instrumented, confirm that entries do not exceed the configured maximum.
  6. Repeat with a fixed struts.locale; changing the request locale should no longer change the locale used for the lookup.

A non-destructive test harness can use this control logic:

for locale in FINITE_SYNTHETIC_LOCALE_CORPUS:
    send_one_request(locale)
    stop_if(heap_delta > LAB_SAFETY_BUDGET or service_unhealthy)
 
assert service_healthy
assert fixed_release_cache_is_bounded

Do not interpret a short run with no visible heap increase as proof that an old version is safe. Version and configuration inventory remain the authoritative exposure test. The finite corpus is for validating the fix and telemetry, not for reproducing service failure.

Fix assessment

Apache describes two complementary controls in the fixed releases:

  • localized-text caches are bounded, with maximum size configurable through struts.i18n.cacheMaxSize;
  • request-derived locales can be restricted to the runtime's available-locale set through struts.locale.validateRequestLocale.

Apache notes that the optional request-locale restriction is disabled by default for backward compatibility. The bounded caches are therefore the resource-safety control that must hold even when request-locale validation is not enabled. When a cache reaches its limit, entries are evicted transparently. The same fix is present in 7.3.0 and the 6.x maintenance release 6.11.0.

The documented workaround, setting struts.locale to a supported fixed value, removes the attacker-controlled locale from localized-text lookups. It is useful for immediate risk reduction but should not be treated as a substitute for upgrading unsupported 2.x branches.

A fix review should verify bounded behavior under concurrency, not only the presence of the new configuration keys. The public bulletin describes the behavioral change; this article does not claim an unpublished line-by-line patch history or private test result.

Regression tests

TestExpected result
Fixed struts.locale with varied request localesThe configured locale is used consistently
Validation disabled with many finite variantsCache remains within its configured bound
struts.locale.validateRequestLocale enabled with an unavailable localeSafe fallback; no unbounded new key
Available locale with validation enabledCorrect localized message is returned
Cache reaches its configured maximumEviction occurs and service remains healthy
Evicted locale is requested againCorrect content is recomputed without stale cross-locale data
Concurrent requests use many finite variantsHeap, latency, and GC remain within the lab budget
Type-conversion and validation errors trigger lookupsBoth paths use the bounded-cache behavior

Include correctness checks for fallback language and cache eviction. A memory fix that returns the wrong user's locale, retains stale messages, or creates a concurrency race is not a complete fix.

Detection

High-cardinality locale input

Capture the effective locale at the application or trusted edge. The following SPL assumes that value has been normalized into request_locale. The threshold of 20 distinct values in five minutes is only an initial hypothesis; tune it to the application's legitimate traffic and NAT behavior.

index=YOUR_WEB_INDEX request_locale=*
| bin _time span=5m
| stats count as requests dc(request_locale) as distinct_locales
        values(user_agent) as user_agents
  by _time src_ip
| where distinct_locales > 20
| sort - distinct_locales

Also calculate distinct locales across the whole service. An attacker can distribute requests across source addresses, and a reverse proxy can make many clients share one address. Useful pivots include session identifier, TLS client fingerprint, user agent, route, and edge request ID, where lawfully collected.

JVM correlation

Correlate a rise in locale cardinality with:

  • old-generation or total heap occupancy that does not return to baseline;
  • increased full-GC count or pause time;
  • allocation or promotion pressure;
  • OutOfMemoryError, health-check failure, container restart, or process exit;
  • latency increases on requests that trigger localized-text lookups.

Request volume by itself is a weak signal for this class. A low request rate can still be relevant when almost every request contributes a new retained key.

Incident Response

  1. Rate-limit or filter anomalous locale patterns at a trusted edge while preserving representative request evidence.
  2. Record the exact Struts version and struts.locale configuration on every instance; do not assume a homogeneous fleet.
  3. Preserve access logs, effective-locale telemetry, GC logs, JVM/container metrics, restart history, and deployment metadata.
  4. Upgrade to 6.11.0 or 7.3.0. If immediate upgrade is impossible, set a fixed supported struts.locale as Apache documents.
  5. Restart affected JVMs in a controlled manner after evidence collection to clear retained state, then confirm traffic is served by fixed or mitigated instances.
  6. Run the bounded-memory regression test and monitor heap after restoration.

A heap dump can contain credentials and user data and can itself add operational pressure. Collect one only under the organization's forensic and privacy policy, with adequate disk and memory headroom.

Disclosure Timeline

  • Researcher report date: not published in S2-074.
  • 2026-08-13: the public S2-074 page records this as its last-updated date and credits Kuniyoshi Noguchi (野口 晋義), @KuniNogu as reporter.
  • 2026-08-25: JVN published JVN#08517956 with CVSS v4.0 8.7 and the public reporter credit.
  • Fixed-release availability: S2-074 identifies 6.11.0 and 7.3.0 as the minimum fixed releases; this article does not infer their private patch or validation dates.

No private triage or coordination date is reconstructed from commit history.

Primary sources