CVE-2017-5638 is a remote code execution vulnerability in the Apache Struts 2 web framework, rated CVSS v3.1 10.0 (Critical). An unauthenticated attacker could execute arbitrary operating-system commands simply by sending an HTTP request with a malicious Content-Type header. No valid upload, no credentials, no application-specific knowledge required. It is best remembered as the vulnerability that, left unpatched, enabled the 2017 Equifax breach affecting roughly 147 million people.
Timeline and impact
Apache published the advisory and a fix on March 6, 2017. Working exploits circulated almost immediately, and mass scanning began within days — this was a point-and-shoot RCE against internet-facing Java applications, so the window between disclosure and exploitation was effectively zero. Equifax was breached that same year; the subsequent investigations concluded a vulnerable, unpatched Struts installation was the initial entry point, and that the organization had failed to apply the fix or detect the vulnerable component in its inventory. The intruders then dwelled undetected for months, moving laterally and exfiltrating personal data on roughly 147 million people. The incident became the textbook example of why "we shipped a patch" upstream means nothing until every downstream deployment actually applies it.
CVE-2017-5638 also did not appear in isolation. Struts 2's history is punctuated by OGNL-injection RCEs — CVE-2017-9805 in the REST plugin, CVE-2018-11776 via namespace evaluation, CVE-2019-0230, and CVE-2020-17530 among them — because the framework repeatedly evaluated attacker-influenced strings as OGNL in different code paths. That recurring pattern is the real story for defenders: a single component that keeps producing the same class of critical flaw is one you must be able to locate instantly across your estate every time a new advisory lands, rather than rediscovering where it runs during each fire drill.
Technical root cause
Struts 2 historically evaluated OGNL (Object-Graph Navigation Language) expressions in many places, which repeatedly turned into an RCE liability. In CVE-2017-5638 the culprit is the Jakarta Multipart parser, the default handler for multipart/form-data file uploads.
When the parser encountered a Content-Type value it could not parse, it built an error message to log and surface — and it ran that message through OGNL evaluation. Because the attacker controls the Content-Type header, they control the string that reaches the OGNL engine. A malicious header looks roughly like this:
POST /upload.action HTTP/1.1
Host: victim.example
Content-Type: %{(#_='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).
(#cmd='id').(#p=new java.lang.ProcessBuilder(#cmd)).(#p.start())}
The %{...} wrapper tells Struts to evaluate the contents as OGNL. The payload disables the member-access restrictions that normally sandbox OGNL, then constructs a ProcessBuilder and launches an operating-system command. Because the multipart parser triggers evaluation before any real upload processing, the request does not even need a valid body — the poisoned header alone is enough. The command runs with the privileges of the servlet container, which on many deployments was more than enough to establish a foothold.
How to detect if you are affected
- Identify the framework and version. Vulnerable ranges are Struts 2.3.5 through 2.3.31 and 2.5 through 2.5.10. Check the bundled
struts2-coreJAR version in your WAR files and on your classpath. - Look for the transitive case. Struts often arrives inside a larger application archive rather than as a dependency a team consciously manages, so a search of
pom.xml/build.gradlealone can miss a shaded or vendored copy. - Review access logs on internet-facing apps for
Content-Typevalues containing%{orognl,ProcessBuilder, orgetRuntime— a strong indicator of attempted exploitation.
Manual JAR hunting does not scale. Safeguard's software composition analysis identifies struts2-core and its version everywhere it appears in your build outputs, including inside packaged artifacts.
Remediation and patched versions
- Upgrade Struts to 2.3.32 or 2.5.10.1 or later, which replace the vulnerable exception-handling path so attacker-controlled
Content-Typevalues are no longer evaluated as OGNL. - Rebuild and redeploy every artifact that bundles Struts. A patched library in your repository does nothing until the WAR that ships it is rebuilt. Safeguard's container image scanning catches a vulnerable Struts JAR baked into an application image.
- Add WAF rules as a stopgap only, blocking
%{/ OGNL patterns inContent-Type. Filters were repeatedly bypassed for this bug, so treat them as a delay, not a fix. - Assume compromise on exposed, unpatched hosts and hunt for web shells, unexpected outbound connections, and new processes spawned by the servlet container.
How Safeguard surfaces and auto-fixes CVE-2017-5638
The Equifax story is not really about one bug — it is about a component that nobody had a reliable inventory of, so nobody knew to patch it. Safeguard closes that gap by continuously resolving your dependency graph and matching it against vulnerability intelligence, so a vulnerable struts2-core cannot hide inside a fat JAR or a legacy service that no one is actively maintaining. Findings carry CISA KEV and EPSS context, so an internet-exploitable, actively weaponized RCE like this one is elevated to the top of the queue rather than sorted purely by score.
When a safe upgrade path exists, automated fix pull requests raise the version bump against your build files and re-run your checks, cutting the disclosure-to-deployment window that Equifax so painfully demonstrated. Griffin AI reasons over reachability and exploit maturity to tell you which Struts instances are truly exposed versus dormant. If you want to see how that prioritization compares to legacy scanners, our tool comparison walks through the differences on Java RCE findings specifically.
A single unapplied patch turned into one of the largest data breaches on record. The defense is not heroic — it is continuous inventory plus fast, automated patching.
Begin at app.safeguard.sh/register, and find integration guides at docs.safeguard.sh.