Apache Struts2 has a long, uncomfortable history of turning its own template engine against the applications built on it, and CVE-2019-0230 is one more entry in that ledger. Tracked internally by the Struts team as S2-059, the flaw is a forced "double OGNL evaluation" bug: when a tag attribute is evaluated twice against Object-Graph Navigation Language (OGNL), an attacker who can influence the first-pass value can smuggle a second OGNL expression through and have it executed on the server. Under the right conditions — an application that mirrors raw, user-modifiable input back into a Struts tag attribute using %{...} or ${...} syntax — that translates directly into remote code execution. Apache and third-party scanners rate it CVSS 9.8, Critical, and it sits alongside CVE-2017-5638 (the Equifax bug) and CVE-2018-11776 (S2-057) as part of a recognizable pattern: Struts2's OGNL evaluator repeatedly becomes the pivot point for full server compromise.
What's actually broken
OGNL is the expression language Struts2 uses internally to bind form data to Java objects and to render dynamic values inside JSP tags. By design, certain Struts tag attributes support "forced" evaluation, meaning their string value is run through the OGNL interpreter a second time after the first evaluation resolves. That's a deliberate feature for a narrow set of use cases. The problem is that if a developer passes unvalidated, attacker-controlled input into one of those attributes (any attribute other than value), the first evaluation can produce a string that is itself a valid OGNL expression — which the framework then dutifully evaluates a second time. Because OGNL in this context can reach static method calls and runtime classes, that second evaluation can be turned into arbitrary command execution on the underlying host.
Unlike CVE-2017-5638, which was exploitable purely through a crafted Content-Type header against any endpoint using the Jakarta Multipart parser, CVE-2019-0230 requires a specific coding pattern: the application has to be echoing user-supplied data into a Struts tag attribute via forced OGNL evaluation syntax. That makes it narrower in default exploitability but no less severe where the pattern exists, and it's a pattern that shows up frequently in Struts2 codebases that build dynamic UI components, error messages, or redirect targets from request parameters.
Apache disclosed a companion issue, CVE-2019-0233, in the same advisory — a denial-of-service condition in the file upload interceptor. It's unrelated in mechanism but worth patching in the same pass since it ships in the same fixed release.
Affected versions and components
- Affected: Apache Struts 2.0.0 through 2.5.20 (the full 2.x line up to that point)
- Fixed: Apache Struts 2.5.22 and later
- Component: The core OGNL expression evaluation used by Struts tag attribute rendering — not a specific plugin or interceptor, which is why the exposure is broad across any app relying on the affected version range
- Reported by: Matthias Kaiser, Apple Information Security
Notably, Struts 2.5.22 (released November 29, 2019) had already shipped the underlying fixes — including optional, off-by-default hardening — before Apache formally published the S2-059 advisory and CVE-2019-0230 details in August 2020. Struts 2.5.26 went further, strengthening the "Proactive OGNL Expression Injection Protection" so that expression evaluation likely to trigger a second, unintended evaluation is blocked outright rather than left to developer discipline. If you're running anything in the 2.5.20-and-earlier range, both the November 2019 fix release and the later hardening in 2.5.26 matter.
CVSS, EPSS, and KEV context
- CVSS v3.1 Base Score: 9.8 (Critical) — vector
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H, reflecting a network-reachable, low-complexity attack with no privileges or user interaction required and full impact on confidentiality, integrity, and availability - CVSS v2 Base Score: 7.5 (High), as originally scored under the older metric
- EPSS: Exploit Prediction Scoring System data has consistently placed CVE-2019-0230 at a very high probability of exploitation activity (in the range of ~0.9, i.e., roughly a 90%+ modeled chance of observed exploitation attempts in a 30-day window). EPSS values drift over time as new exploitation telemetry comes in, but the consistently elevated score reflects how heavily Struts2 endpoints get swept by both opportunistic scanners and targeted red teams looking for OGNL injection points.
- CISA KEV: As of this writing, CVE-2019-0230 is not listed in CISA's Known Exploited Vulnerabilities catalog, unlike its more famous sibling CVE-2017-5638. That doesn't mean it's benign — it means federal remediation mandates don't currently apply to it — but the CVSS and EPSS numbers alone justify treating it as a priority patch for any internet-facing Struts2 deployment.
The gap between the "important" severity Apache initially assigned in its own advisory language and the 9.8 Critical score assigned by NVD and most vendor trackers is itself worth noting: Apache's per-issue severity labels in Struts bulletins have historically undersold how dangerous OGNL injection primitives turn out to be once public proof-of-concept exploits circulate.
Timeline
- November 29, 2019 — Apache Struts 2.5.22 is released, containing the code changes and new (disabled-by-default) protections that address the forced double OGNL evaluation issue, bundled alongside unrelated bug fixes.
- August 13, 2020 — Apache formally publishes the S2-059 security bulletin, disclosing CVE-2019-0230 (forced double OGNL evaluation RCE) and CVE-2019-0233 (file upload DoS) together, with remediation guidance.
- September 14, 2020 — NVD publishes its CVE-2019-0230 entry, assigning the CVSS v3.1 base score of 9.8.
- Ongoing — Struts 2.5.26 later strengthens Proactive OGNL Expression Injection Protection, reducing reliance on developers manually avoiding the vulnerable coding pattern.
The roughly nine-month gap between the fix shipping and the advisory being published is consistent with how Apache has handled several Struts OGNL disclosures: patches land in a release before the security bulletin catches up, which means version-string fingerprinting alone can undercount exposure — teams that "already upgraded" months before the CVE existed may not realize which release actually carried the fix.
Remediation steps
- Upgrade to Struts 2.5.22 or later, and prefer 2.5.26+ where feasible. The later release enables stronger built-in protection against forced double evaluation rather than relying solely on secure coding discipline.
- Audit tag attributes for forced OGNL evaluation on user input. Search your JSP/Struts tag usage for
%{...}and${...}patterns applied to any attribute other thanvalue, and trace whether the interpolated value can be influenced by request parameters, headers, or stored user data. - Never pass unvalidated request data into a tag attribute that undergoes forced evaluation. If the attribute's value must vary based on user input, treat that input as untrusted and avoid double-evaluation-eligible syntax entirely, or strictly allowlist expected values.
- Enable Proactive OGNL Expression Injection Protection where your Struts version supports it — this is off by default in earlier 2.5.x releases and needs to be explicitly turned on.
- Inventory every Struts2 instance in your environment, including vendor-bundled or OEM'd applications that embed Struts under the hood — a large share of real-world Struts exposure comes from third-party software rather than in-house code.
- Add WAF/virtual-patching rules targeting known OGNL injection payload patterns as a compensating control while upgrades roll out, but don't treat it as a substitute for patching — OGNL payload obfuscation techniques have repeatedly bypassed signature-based filters for prior Struts CVEs.
- Re-test after upgrading. Because the vulnerable pattern is a coding practice, not just a library version, confirm that application-level fixes (removing risky forced-evaluation usage) accompany the framework upgrade rather than assuming the version bump alone closes every instance.
How Safeguard Helps
Struts2's OGNL history is exactly the kind of risk that slips through point-in-time reviews: a component gets pinned to a version years ago, a CVE gets published long after the fix already shipped, and nobody revisits the dependency until it shows up in a pen test or an incident. Safeguard is built to close that gap across the software supply chain.
Safeguard continuously inventories the open-source and third-party components running across your codebases and containers — including transitive and vendor-bundled dependencies — so a Struts2 instance sitting inside an OEM'd application gets flagged with the same rigor as one your own team pulled into a pom.xml. When a CVE like CVE-2019-0230 is published or reassigned a higher severity, Safeguard correlates it against your actual deployed versions in real time, rather than waiting for the next scheduled scan, and surfaces the specific services and repositories affected.
Because CVSS alone doesn't tell you what to fix first, Safeguard layers in exploitability signal — EPSS trends, KEV status, and reachability of the vulnerable code path — so a Critical-rated but hard-to-reach flaw doesn't crowd out something actively being exploited in the wild. For issues like this one, where the vulnerable pattern is a specific coding practice rather than a blanket library defect, Safeguard's code-level analysis helps distinguish instances where forced OGNL evaluation actually touches attacker-controlled input from instances where the vulnerable API exists but isn't reachable — cutting remediation effort down to the deployments that genuinely need attention.
Finally, Safeguard tracks remediation end-to-end: once a fix is identified — upgrade to 2.5.26, remove a risky tag pattern, enable proactive OGNL protection — it verifies the change lands in the affected services and stays closed, so a patched component doesn't quietly regress the next time someone forks an old build or a vendor ships a stale OEM image. For supply chain risk that ages the way Struts2's OGNL bugs have, that continuous verification is the difference between a one-time scan result and an actually managed vulnerability.