Feb 25, 2026·5 min read·116 visits
Critical Remote Code Execution (RCE) in Apache Struts 2 via the Jakarta Multipart parser. Attackers can execute arbitrary system commands by sending a malicious OGNL expression in the 'Content-Type' HTTP header. No authentication is required.
In the annals of cybersecurity history, few vulnerabilities have caused as much chaos, financial ruin, and executive sweating as CVE-2017-5638. It is the perfect storm: a widely used enterprise framework (Apache Struts 2), a trivial exploitation vector (an HTTP header), and a root cause rooted in over-helpful error handling. This vulnerability famously facilitated the 2017 Equifax data breach, exposing the personal information of 147 million people. It serves as a stark reminder that even the most robust fortresses can be toppled if the doorman decides to execute instructions scribbled on a suspicious package.
Imagine you're a bouncer at a club. Someone tries to hand you a fake ID. Instead of just saying "Access Denied," you read the name on the ID out loud over the club's PA system. Now imagine the name on the ID isn't a name, but a magic spell that causes the club to explode.
That is essentially CVE-2017-5638. Apache Struts 2, a heavy-lifting framework for Java web applications, uses the Jakarta Multipart parser to handle file uploads. When a user tries to upload a file, they send a multipart/form-data request. If the request is malformed—say, the Content-Type header is garbage—the parser throws an exception.
The framework, trying to be helpful, catches this exception and attempts to generate a localized error message to send back to the user. The problem? It includes the raw content of the invalid header in the error message processing pipeline. And in Struts 2, that pipeline processes OGNL (Object-Graph Navigation Language).
This means if an attacker puts an OGNL payload in the Content-Type header, Struts doesn't just reject it; it executes it while trying to tell you it's invalid.
Let's dig into the JakartaMultiPartRequest.java class. When an upload error occurs (like an invalid Content-Type), the code calls buildErrorMessage. This method eventually calls LocalizedTextUtil.findText.
Here is the logic flow of the failure:
The fatal flaw lies in how findText was called. The developers passed the exception message (which contains the attacker's string) as the default message key. The Struts localization system is designed to parse OGNL expressions in message keys to allow for dynamic text generation. By passing untrusted input into a function designed to evaluate expressions, Struts effectively handed the keys to the kingdom to anyone with curl.
The fix was painfully simple, which makes the exploit even more tragic. The vulnerability existed because the exception message e.getMessage() was passed as the 4th argument to findText.
Vulnerable Code (Before):
// The 'e.getMessage()' is passed as the default message key
// If the key isn't found, Struts evaluates this string for OGNL.
return LocalizedTextUtil.findText(
this.getClass(),
errorKey,
defaultLocale,
e.getMessage(), // <--- THE KILL ZONE
args
);Patched Code (After):
// First, check if the errorKey exists.
if (LocalizedTextUtil.findText(this.getClass(), errorKey, defaultLocale, null, new Object[0]) == null) {
// If not, use a safe default key and pass the exception message as an ARGUMENT
return LocalizedTextUtil.findText(
this.getClass(),
"struts.messages.error.uploading",
defaultLocale,
null,
new Object[] { e.getMessage() } // <--- Safe as an argument
);
} else {
return LocalizedTextUtil.findText(this.getClass(), errorKey, defaultLocale, null, args);
}In the patched version, the dangerous string e.getMessage() is wrapped in an Object[] array and passed as an argument. Arguments are treated as literal strings and are not recursively evaluated for OGNL expressions. It's the difference between eval(input) and print(input).
Struts 2 has security mechanisms to prevent OGNL from doing dangerous things, like accessing static methods or modifying system properties. However, OGNL is incredibly flexible. The exploit payload for CVE-2017-5638 is a masterclass in sandbox evasion.
The payload typically looks like this (formatted for readability):
%{
(#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).
(#process=@java.lang.Runtime@getRuntime().exec('cat /etc/passwd')).
(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).
(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).
(#ros.flush())
}Wait, it's not that simple. By Struts 2.3.x, strict OGNL security was in place. Attackers had to explicitly overwrite the _memberAccess object to regain access to private members and static methods.
The "Universal" Payload:
Attackers set Content-Type to a massive string starting with:
%{(#_='multipart/form-data')...
They use reflection to reset #_memberAccess to DEFAULT_MEMBER_ACCESS (which allows everything). Once they have that, they can instantiate java.lang.ProcessBuilder. Because this happens before the request is fully processed or logged by the application logic, standard application logs often show nothing until it's too late.
This is a System-Level RCE. It doesn't get worse than this. The application server (likely Tomcat or JBoss) is usually running with significant privileges.
The fact that this can be triggered by a single curl command against the login page (or any page using the multipart parser) makes it a "spray and pray" favorite for script kiddies and nation-states alike.
If you are running Struts 2.3.x < 2.3.32 or 2.5.x < 2.5.10.1, you are vulnerable. Stop reading and patch.
Remediation Steps:
Content-Type header contains %, #, or multipart/form-data followed by OGNL syntax.> [!WARNING]
> Blocking multipart/form-data entirely will break file uploads. You must specifically look for OGNL markers like %{ or ${ inside the header value.
cos or pell multipart parsers instead of jakarta in your struts.xml, though this may have functional side effects.CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Apache Struts 2 Apache | 2.3.5 - 2.3.31 | 2.3.32 |
Apache Struts 2 Apache | 2.5.0 - 2.5.10 | 2.5.10.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-917 |
| Attack Vector | Network (HTTP Header) |
| CVSS v3.1 | 9.8 (Critical) |
| Exploit Status | Active / Weaponized |
| KEV Listed | Yes |
| EPSS Score | 0.9427 (Very High) |
Improper Neutralization of Special Elements used in an Expression Language Statement ('Expression Language Injection')
An uncontrolled resource consumption vulnerability exists in the Scala-based http4s-blaze-server package of the http4s/blaze library. The vulnerability allows remote, unauthenticated attackers to cause an Out of Memory Error (OOM) and JVM crash by streaming a continuous sequence of small or empty WebSocket continuation frames with the FIN bit set to 0. This bypasses typical payload size checks because of the JVM's per-object allocation overhead, leading to rapid heap exhaustion with minimal network bandwidth.
A critical path traversal vulnerability has been identified in the OpenList Go-based backend package. The vulnerability exists within the batch rename handler because the application does not validate the source filename parameter before constructing filesystems paths. This omission allows authenticated users to escape their designated directory and rename files in sibling paths.
OpenList version 4.2.3 and prior is vulnerable to an authorization bypass and metadata leakage. When configured with the Bleve search engine backend, OpenList fails to perform separator-aware path matching when validating tenant containment. This allows authenticated users to access sibling directories sharing similar name prefixes. Furthermore, the search backend returns unfiltered global result counts, leaking existence verification data of unauthorized files via side-channel analysis.
An authorization bypass vulnerability in OpenList version 4.2.3 and below allows authenticated users to read arbitrary files outside of their designated base directories due to an insecure path prefix check using Go's standard strings.HasPrefix function.
A security policy bypass vulnerability exists in the AWS API MCP Server (awslabs-aws-api-mcp-server) from version 0.2.13 through 1.3.46. When the server fails to load the read-only operations index during startup (due to transient network failures, file permission issues, or other exceptions), it logs a warning but continues running in an insecure, degraded state. Under this condition, the security policy engine fails open, silently skipping all subsequent security checks and consent prompts for the lifetime of the process. This permits unauthorized mutating AWS CLI commands to execute via indirect prompt injection attacks.
An incomplete escaping vulnerability in the npm package 'shescape' allows unauthenticated users to trigger dynamic shell expansions, absolute path disclosure, and command block break-outs on Unix and Windows systems.