Security Bulletins
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Apache Struts 7.4.0 (released 2026-10-02) running on Apache Tomcat 11.0.26 and Java 21. The
bulletin list of the Struts wiki, every bulletin cited here, the announcement pages announce-2026.html,
announce-2025.html, announce-2024.html, announce-2023.html, announce-2018.html and announce-2017.html, the
End-of-Life page and the security guide were fetched and read on 2026-10-10 (the bulletin list ends at S2-078, published
on 2026-10-05), and every CVE id shown was looked up on the CVE record service behind cve.org and, for the recent ones,
on NVD before it was written down. The page is the history of how Struts was attacked: the bulletins are the project’s
own advisories, one wiki page per vulnerability with the affected versions, the fixed versions, the reporter, the CVE id,
a description, a solution, a statement about backward compatibility and a workaround. Reading them as a series shows that
a handful of design decisions (OGNL evaluated on text that came from the request, parameters bound without a filter,
prefixes in parameter names that choose an action, a multipart parser that handles untrusted headers, plugins that
deserialize request bodies, and resources that grow with the input) produced almost every serious entry, and that each of
those decisions has a visible countermeasure in Struts 7.4.0. The running scenario is the Bookshelf: the page asks,
for a Bookshelf built on an old Struts release, which bulletins applied, and ends with an audit of the Bookshelf built on
7.4.0 that fails the build on a vulnerable version and probes the classic attack shapes against the running application.
The related pages of this section are Security Hardening, Security Headers, Debugging, Dev Mode and Logging, Testing, Deployment and Production and Struts 1 Classic and Migration.
How a Bulletin Reads
The wiki page Security Bulletins is an index; each entry is a child page named S2-nnn (S2 for Struts 2, a number in
the order of publication). Every bulletin has the same fields, and knowing them makes the pages quick to read.
| Field | What it tells you |
|---|---|
Summary |
A one-line statement of the weakness |
Who should read this |
Who is concerned (all Struts 2 developers, or only users of a plugin or a feature) |
Impact of vulnerability |
The class of damage: remote code execution, denial of service, disclosure of data, data integrity, bypass of a configured limit |
Maximum security rating |
Critical, Important, Moderate or Low (see the scale below) |
Recommendation |
The version to upgrade to, sometimes with a configuration alternative |
Affected Software |
The version ranges, including the branches that are end-of-life |
Reporter |
Who found it |
CVE Identifier |
The CVE id, empty for some old bulletins and for configuration advice such as S2-043 |
Problem, Solution, Backward compatibility, Workaround |
The mechanism, the fix, the compatibility cost of the fix and a mitigation when an upgrade is not possible |
The index page defines the four ratings, and the definition is about exploitability, not about the size of the damage.
| Rating | Meaning in the Struts scale |
|---|---|
Critical |
Could be exploited by a remote attacker to execute arbitrary code automatically, by worms or scanners, even when developers followed the security advice |
Important |
Could compromise data or availability; includes easy remote code execution that exists because the developer treated user input as trusted |
Moderate |
Significant mitigation exists: an unlikely configuration, a rarely used feature, or an authenticated attacker |
Low |
Believed extremely hard to exploit, or minimal consequences |
Three reading habits follow from the fields. First, read the affected ranges against the exact version in the
application, because several bulletins exclude the patched maintenance releases of an older branch (S2-029 and S2-032 list
"except 2.3.20.3 and 2.3.24.3"). Second, read the "Non-affected" and "Note" sentences: S2-067 says that applications not
using FileUploadInterceptor are safe, S2-075 says that applications using the default action mapper, the restful2
mapper or the REST plugin are not affected, and S2-072 says that releases which do not bundle the JSON plugin are not
affected. Third, read the workaround as a statement of what the weakness depends on: S2-068 offers a size-limited
temporary folder or disabling upload, which shows that the leak needs the upload feature to be on.
How the CVE Ids on This Page Were Checked
The wiki pages are written by the project, but the project’s own announcement pages carry ids that look unusual for the 2026 entries (for example CVE-2026-73631 or CVE-2026-104711), so the ids were not copied from the announcements. Each id was fetched from the CVE record service (the JSON API behind cve.org) and the state, the publication date and the title were compared with the bulletin; NVD was queried for a sample of the same ids and returned a record for each. The result is that every id in the tables below resolves to a PUBLISHED record whose title matches the bulletin. The records of the 2026 bulletins were published on 2026-08-14 and 2026-08-15 (S2-070 to S2-074) and on 2026-10-05 (S2-075 to S2-078); the record of S2-069, CVE-2025-68493, was published on 2026-01-11. Where a bulletin has no id (S2-001, S2-007, S2-043) the page says so instead of guessing, and where the bulletin says "Related to CVE-2017-7525" (S2-055) the id is cited as the related Jackson record, not as a Struts id.
Seven Families of Weakness
The seventy-eight bulletins are not seventy-eight different ideas. Grouped by mechanism they fall into a small number of families, and the family is what the Bookshelf author should remember, because the next bulletin will belong to one of them.
| Family | Bulletins (examples) | Countermeasure in Struts 7.4.0 and in your code |
|---|---|---|
OGNL evaluated on request data through parameter names |
S2-003, S2-005, S2-009, S2-020, S2-021 |
ParametersInterceptor with |
OGNL evaluated on request data through values (conversion errors, tags, localization) |
S2-007, S2-029, S2-036, S2-053, S2-059, S2-061, S2-062 |
|
Method and action selection by request syntax |
S2-016, S2-017, S2-019, S2-032, S2-033, S2-057 |
Dynamic Method Invocation off ( |
Multipart and upload handling |
S2-045, S2-046, S2-060, S2-063 to S2-068 |
The |
Plugins that parse or deserialize bodies |
S2-052, S2-054, S2-055, S2-056, S2-070 to S2-072, S2-077 |
Plugin limits ( |
Resources that grow with the input |
S2-011, S2-034, S2-041, S2-044, S2-047, S2-050, S2-063, S2-064, S2-065, S2-068, S2-073, S2-074, S2-076 |
Bounded reads, bounded caches, bounded decimal rendering; a fixed locale where the application is not multilingual (S2-074) |
Shared state and operational exposure |
S2-043, S2-069, S2-070, S2-071, S2-078 |
No Config Browser plugin in production, no shared formatter state, a modern XML parser setup (the XWork XXE of S2-069) |
Reading Order: What to Do When a New Bulletin Appears
A bulletin is useful only if it is compared with the application. The decision is small enough to draw: find the version, read the ranges and the conditions, and then either upgrade or apply the workaround.
The two boxes on the right are the reason the End-of-Life section further down matters: on an end-of-life branch the "fixed release" box is always empty.
Timeline of the Significant Bulletins
The tables list the bulletins that shaped the framework, in order of publication, with the rating, the affected and fixed versions as the bulletin states them, the CVE id (a link to the CVE record is in the References) and the lesson. Ratings are the "Maximum security rating" field of the bulletin.
2007 to 2013: parameter names and values become OGNL
| Bulletin | CVE | Rating | Affected | Fixed | Lesson |
|---|---|---|---|---|---|
S2-001 |
none listed |
Critical |
WebWork 2.1 with |
2.0.9 (or XWork 2.0.4) |
Remote code execution on a form validation error; the first sign that the expression language must not be fed with request text |
S2-003 |
CVE-2008-6504 |
Critical |
2.0.0 to 2.1.8.1 |
2.2.1 |
A parameter name could be an OGNL statement, so |
S2-005 |
CVE-2010-1870 |
Critical |
2.0.0 to 2.1.8.1 |
2.2.1 (or a configuration change) |
Same affected range as S2-003 and fixed in the same release: the parameter filter had to be fixed again, a sign that filtering names one pattern at a time is fragile |
S2-007 |
none listed |
Important |
2.0.0 to 2.2.3 |
2.2.3.1 |
User input evaluated as an OGNL expression when a conversion error occurred: error handling is also input handling |
S2-009 |
CVE-2011-3923 |
Critical |
2.0.0 to 2.3.1.1 |
2.3.1.2 |
Another |
S2-013 |
CVE-2013-1966 |
Critical |
2.0.0 to 2.3.14.1 |
2.3.14.2 |
A flaw in the |
S2-016 |
CVE-2013-2251 |
Critical |
2.0.0 to 2.3.15 |
2.3.15.1 |
The |
S2-019 |
CVE-2013-4316 |
Important |
2.0.0 to 2.3.15.1 |
2.3.15.2 |
Dynamic Method Invocation became disabled by default, the first time a default was changed to close a family |
S2-020 |
CVE-2014-0050 and CVE-2014-0094 |
Important |
2.0.0 to 2.3.16.1 |
2.3.16.2 |
Upgrade of Commons FileUpload against a denial of service, and |
The names of the bulletins S2-002 to S2-004, S2-006, S2-008, S2-010 to S2-012, S2-014, S2-015, S2-017, S2-018 and S2-021 to S2-028 are on the index page; most are cross-site scripting issues, token weaknesses (S2-010, S2-023), denial of service through long parameter names (S2-011) and more bypasses of the excluded parameters (S2-021, S2-022, S2-024). The long list from 2012 to 2014 is the reason the old books, written in that period, teach a security model (blocklists in the interceptor) that the framework replaced.
2016 to 2018: method selection, the REST plugin, uploads, namespaces
| Bulletin | CVE | Rating | Affected | Fixed | Lesson |
|---|---|---|---|---|---|
S2-029 |
CVE-2016-0785 |
Important |
2.0.0 to 2.3.24.1 (except 2.3.20.3) |
2.3.20.3, 2.3.24.3, 2.3.28 |
The first forced double evaluation: |
S2-032 |
CVE-2016-3081 |
Important |
2.3.20 to 2.3.28 (except 2.3.20.3 and 2.3.24.3) |
2.3.20.3, 2.3.24.3, 2.3.28.1; disable DMI |
The |
S2-033 |
CVE-2016-3087 |
Important |
2.3.20 to 2.3.28 (same exceptions) |
same as S2-032 |
The |
S2-037 |
CVE-2016-4438 |
Important |
2.3.20 to 2.3.28.1 |
2.3.29 |
Remote code execution through the REST plugin: a plugin inherits the weaknesses of the mapper it replaces |
S2-045 |
CVE-2017-5638 |
Critical |
2.3.5 to 2.3.31 and 2.5 to 2.5.10 |
2.3.32 or 2.5.10.1 |
Incorrect exception handling and error-message generation in the Jakarta Multipart parser let a crafted |
S2-046 |
CVE-2017-5638 |
Critical |
2.3.5 to 2.3.31 and 2.5 to 2.5.10 |
2.3.32 or 2.5.10.1 |
The same CVE; the record lists |
S2-052 |
CVE-2017-9805 |
Critical |
2.1.6 to 2.3.33 and 2.5 to 2.5.12 |
2.3.34 or 2.5.13 |
The REST plugin XStream handler deserialized XML without type filtering; deserialization of request bodies is code execution unless the types are restricted |
S2-053 |
CVE-2017-12611 |
Moderate |
2.0.0 to 2.3.33 and 2.5 to 2.5.10.1 |
2.3.34 or 2.5.12 |
An unintended expression in a FreeMarker tag instead of a string literal; templates are code too |
S2-055 |
related to CVE-2017-7525 (Jackson) |
Important |
2.5 to 2.5.14 |
2.5.14.1 |
A deserialization flaw in the Jackson library; a Struts upgrade was the way to ship the library fix |
S2-057 |
CVE-2018-11776 |
Critical |
2.3 to 2.3.34 and 2.5 to 2.5.16 |
2.3.35 or 2.5.17 |
With |
S2-057 is the bulletin that shows how configuration and code combine: no line of the application was wrong in isolation.
The bulletin’s own advice (and the verified 7.4.0 defaults in this page’s audit) is that every package declares its
namespace and that the defaults for method invocation stay off. The 7.x line changed the default of
struts.mapper.alwaysSelectFullNamespace to true as part of the 6 to 7 migration, which makes the namespace discipline
more important, not less.
2019 to 2022: forced evaluation and the excluded packages
| Bulletin | CVE | Rating | Affected | Fixed | Lesson |
|---|---|---|---|---|---|
S2-059 |
CVE-2019-0230 |
Important |
2.0.0 to 2.5.20 |
2.5.22 |
Forced double evaluation of |
S2-060 |
CVE-2019-0233 |
Moderate |
2.0.0 to 2.5.20 |
2.5.22 (or add |
An access permission override during upload caused a denial of service: package exclusion lists are part of the API surface |
S2-061 |
CVE-2020-17530 |
Important |
2.0.0 to 2.5.25 |
2.5.26 |
The S2-059 fix was incomplete; forced evaluation of raw input in tag attributes still ran code |
S2-062 |
CVE-2021-31805 |
Important |
2.0.0 to 2.5.29 |
2.5.30 |
The S2-061 fix was incomplete again, so some attributes still evaluated twice; the CVE record says the fix for CVE-2020-17530 was incomplete |
Four bulletins in four years teach one rule, repeated in the security guide: do not put incoming, untrusted user input into a forced expression evaluation. The Bookshelf probe at the end of this page tests that rule on 7.4.0.
2023 to 2026: uploads, resources and the 7.x line
| Bulletin | CVE | Date | Rating | Affected | Fixed | Lesson |
|---|---|---|---|---|---|---|
S2-063 |
CVE-2023-34149 |
2023-06 |
Important |
2.0.0 to 6.1.2 |
2.5.31 or 6.1.2.1 |
Memory exhaustion through unchecked list bounds in parameters |
S2-064 |
CVE-2023-34396 |
2023-06 |
Important |
2.0.0 to 6.1.2 |
2.5.31 or 6.1.2.1 |
Memory exhaustion because normal form fields in a multipart request had no limit |
S2-065 |
CVE-2023-41835 |
2023-12 |
Moderate |
2.5.31 and 6.1.2.1 to 6.3.0 |
2.5.32, 6.1.2.2 or 6.3.0.1 |
Uploaded files stayed in the save directory when a field exceeded |
S2-066 |
CVE-2023-50164 |
2023-12-07 |
Critical |
2.0.0 to 2.3.37 (EOL), 2.5.0 to 2.5.32, 6.0.0 to 6.3.0 |
2.5.33 or 6.3.0.2 |
Upload parameters could be manipulated to enable path traversal and, in some circumstances, upload a malicious file that gives code execution |
S2-067 |
CVE-2024-53677 |
2024-12-10 |
Critical |
2.0.0 to 2.3.37 (EOL), 2.5.0 to 2.5.33 (EOL), 6.0.0 to 6.3.0.2 |
6.4.0 and a migration to the Action File Upload interceptor |
The same weakness through setters for uploaded files mixed with setters for normal fields; the fix is not backward compatible and the old mechanism stays vulnerable |
S2-068 |
CVE-2025-64775 |
2025-12-01 |
Important |
2.0.0 to 2.3.37 (EOL), 2.5.0 to 2.5.33 (EOL), 6.0.0 to 6.7.4, 7.0.0 to 7.0.3 |
6.8.0 or 7.1.1 |
File leak in multipart processing exhausts the disk; workaround: a size-limited temporary folder on its own volume, or no upload |
S2-069 |
CVE-2025-68493 |
2026-01-11 |
Important |
2.0.0 to 2.3.37 (EOL), 2.5.0 to 2.5.33 (EOL), 6.0.0 to 6.1.0 |
6.1.1 |
An XML external entity weakness in the XWork component: disclosure of data, denial of service and server-side request forgery |
The upload bulletins, S2-066 and S2-067, deserve their own paragraph because they changed the framework. S2-066 was
fixed by patch releases (2.5.33 and 6.3.0.2), but for S2-067 the bulletin’s only solution is a new mechanism, because it
concerns the mixing of setters for uploaded files with setters for normal fields, and it says the change is not backward
compatible. The fix was a new mechanism in 6.4
(actionFileUpload, with UploadedFilesAware and UploadedFile) and the removal of the old interceptor in 7.0, so an
application that upgrades the version number only finds that its upload code compiles and silently receives nothing. The
upload page of this section shows the new mechanism with verified examples.
The 2026 Bulletins, S2-070 to S2-078
Nine bulletins in two batches (14 August 2026 and 5 October 2026), on top of S2-069 in January, make ten for 2026. They are mostly Moderate and mostly about bounds, and the second batch follows by three days the 7.4.0 and 6.12.0 releases of 2 October 2026.
| Bulletin | CVE | Rating | Affected | Fixed | What it is |
|---|---|---|---|---|---|
S2-070 |
CVE-2026-73631 |
Moderate |
7.2.1 only |
7.3.0 |
Per-request parsing state in the JSON plugin shared across concurrent requests; data of one request observable in another and a configured limit not enforced |
S2-071 |
CVE-2026-73632 |
Low |
7.2.1 only |
7.3.0 |
The same kind of shared state on the serialization side of the JSON plugin (SMD and JSON-RPC support) |
S2-072 |
CVE-2026-73633 |
Moderate |
2.1.8 to 2.3.37, 2.5.0 to 2.5.33 (EOL), 6.0.0 to 6.10.0, 7.0.0 to 7.2.1 |
7.3.0 or 6.11.0 |
Unbounded read of a JSON request body |
S2-073 |
CVE-2026-73634 |
Moderate |
6.0.0 to 6.10.0, 7.0.0 to 7.2.1 |
7.3.0 or 6.11.0 |
Unbounded read of a Content Security Policy violation report |
S2-074 |
CVE-2026-73635 |
Moderate |
2.0.0 to 2.3.37, 2.5.0 to 2.5.33 (EOL), 6.0.0 to 6.10.0, 7.0.0 to 7.2.1 |
7.3.0 or 6.11.0 |
The locale of the request chooses the localized-text caches, so a client that varies it grows the caches until the heap is full; applications with a fixed locale are not affected |
S2-075 |
CVE-2026-104711 |
Moderate |
2.0.0 to 2.3.37 (EOL), 2.5.0 to 2.5.33 (EOL), 6.0.0 to 6.11.0, 7.0.0 to 7.3.0 only when the OGNL allowlist is disabled |
7.4.0 or 6.12.0, or migrate off the legacy RESTful mapper |
OGNL injection in the legacy RESTful action mapper; not affected: default mapper, |
S2-076 |
CVE-2026-104712 |
Moderate |
2.5.14 to 2.5.33, 6.0.0 to 6.11.0, 7.0.0 to 7.3.0 |
7.4.0 or 6.12.0 |
A request parameter bound to a |
S2-077 |
CVE-2026-104713 |
Important |
2.1.8 to 2.3.37, 2.5.0 to 2.5.33 (EOL), 6.0.0 to 6.11.0, 7.0.0 to 7.3.0 |
7.4.0 or 6.12.0 |
Unbounded read of a REST request body |
S2-078 |
CVE-2026-104714 |
Moderate |
2.0.0 to 2.3.37, 2.5.0 to 2.5.33 (EOL), 6.0.0 to 6.11.0, 7.0.0 to 7.3.0 |
7.4.0 or 6.12.0 |
A date or time formatter retained for a localized message is used by concurrent requests without isolation, so one user’s value can appear in another user’s response |
Five observations come out of these nine entries.
-
A regression can hide in one release. S2-070 and S2-071 affect Struts 7.2.1 and nothing else; the bulletin’s recommendation is 7.3.0. A release number is therefore part of the audit, not only "7.x".
-
Bounded reads are the new baseline. S2-072, S2-073 and S2-077 are the same defect in three places (the JSON plugin, the CSP report endpoint, the REST plugin). Both plugin pages of this section (JSON Plugin and Ajax and REST Plugin) describe the limit constants (
struts.json.maxLengthandstruts.rest.content.maxLength) and the fixed 7.4.0 behaviour. -
Input that selects a cache key is an input. S2-074 is the localization version of the same idea: the locale of the request chooses the key of a cache, so an attacker chooses the number of entries. The localization page of this section covers the fixed locale. See Localization and i18n.
-
The allowlist did its job. S2-075 is a remote code execution class bug that the bulletin rates Moderate, because a Struts 7 application with the default OGNL allowlist is not affected. The rating is a statement about the mitigation, and the mitigation is a default that the Bookshelf must not switch off.
-
Type conversion renders too. S2-076 shows that the combination of a numeric conversion and a rendered value can amplify a request; see Type Conversion for the conversion side.
Why Struts 2.5 and Older Must Be Upgraded
The End-of-Life page states what end-of-life means: the team no longer provides security patches, bug fixes or updates for that branch, and the project supports only the user mailing list and the issue tracker for supported versions, so an EOL version receives no support from the project at all.
| Branch | End-of-life date | What the bulletins show |
|---|---|---|
Struts 1.x |
5 April 2013 |
No patches by definition of end-of-life; CVE-2014-0114 (BeanUtils |
Struts 2.3.x |
12 September 2019 |
The last 2.3 release named as a fix in the bulletins read for this page is 2.3.35 (S2-057); later bulletins that list the branch name "2.3.37 (EOL)" as affected offer no 2.3 fix |
Struts 2.5.x |
30 October 2023 |
The last fix on the branch is S2-066 in 2.5.33 (December 2023); S2-067 (2024), S2-068 (2025), S2-069 (2026) and all of S2-072 to S2-078 list 2.5.0 through 2.5.33 (EOL) as affected and offer no 2.5 fix |
The consequence is arithmetic, and it can be read directly from the pages cited above: an application that stays on
2.5.33 is affected by S2-067, S2-068, S2-069, S2-072, S2-074, S2-076, S2-077 and S2-078 with no patched release to move
to, and every future bulletin will be added to that list. The End-of-Life page mentions that third-party vendors sell
extended security coverage for EOL versions and says the Apache Software Foundation does not endorse any commercial
offer; it is a bridge for a migration that is already planned, not a place to stay. The migration is a package rename
(javax to jakarta), the removal of the old upload interceptor and the opt-in parameter annotation, which the Struts
7.4.0 pages of this section describe one by one.
Worked Example: Which Bulletins Hit an Old Bookshelf?
The Bookshelf of this documentation was, in the style of the older books, once a Struts 2.5.30 application with the
fileUpload interceptor for covers, the REST plugin for a catalogue endpoint and <s:property value="%{...}"/> in a
few pages. Reading the affected ranges of the bulletins above against 2.5.30 gives a short audit.
| Bulletin | Applies to 2.5.30? | Reason from the bulletin text |
|---|---|---|
S2-062 |
No |
Affected 2.0.0 to 2.5.29; 2.5.30 is the fixed release (the audit starts one release earlier, at 2.5.29, for that bulletin) |
S2-063, S2-064 |
Yes |
Affected 2.0.0 to 6.1.2; fixed in 2.5.31 |
S2-065 |
No |
Affected only 2.5.31 and 6.1.2.1 to 6.3.0 |
S2-066 |
Yes |
Affected 2.5.0 to 2.5.32; fixed in 2.5.33 |
S2-067 |
Yes, with no fix on 2.5 |
Affected 2.5.0 to 2.5.33 (EOL); the fix is 6.4.0 with the new upload mechanism |
S2-068, S2-069 |
Yes, with no fix on 2.5 |
Both list 2.5.0 through 2.5.33 (EOL) |
S2-072, S2-074, S2-077, S2-078 |
Yes (when the plugin or feature is used), no fix on 2.5 |
Each lists 2.5.0 through 2.5.33 (EOL) |
S2-073 |
No |
Affected range starts at 6.0.0; the bulletin says earlier releases do not contain the feature |
The table is derived from the published ranges only; it does not claim that each weakness is exploitable in that application, because most bulletins have conditions (the plugin is in use, the feature is on). It is the starting point of the triage in the diagram above, and the next section turns the same idea into a build step.
Worked Example: A Build That Refuses a Vulnerable Struts
The cheapest control is a Maven Enforcer rule that fails the build when any Struts artifact on the classpath is older than
the release that closed the latest bulletins. The Bookshelf pom pins one version with the struts2-bom (so every plugin
follows it) and adds the bannedDependencies rule below. The rule uses the version range syntax (,7.4.0), which means
"anything below 7.4.0".
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<id>no-vulnerable-struts</id>
<phase>validate</phase>
<goals><goal>enforce</goal></goals>
<configuration>
<rules>
<bannedDependencies>
<excludes>
<exclude>org.apache.struts:*:(,7.4.0)</exclude>
</excludes>
<message>Struts below 7.4.0 has published security bulletins (S2-075 to S2-078); upgrade the struts2-bom.</message>
</bannedDependencies>
</rules>
</configuration>
</execution>
</executions>
</plugin>
With the property struts.version set to 7.3.0 (the Bookshelf pom reads the version of the BOM from it), the build stops
in the validate phase, before any code is compiled, and the output names the artifact and the rule.
$ mvn -B -q validate -Dstruts.version=7.3.0 [ERROR] Rule 0: org.apache.maven.enforcer.rules.dependency.BannedDependencies failed with message: [ERROR] Struts below 7.4.0 has published security bulletins (S2-075 to S2-078); upgrade the struts2-bom. [ERROR] org.example:bookshelf-struts:war:1.0.0-SNAPSHOT [ERROR] org.apache.struts:struts2-core:jar:7.3.0 <--- banned via the exclude/include list
With the default 7.4.0 the same command prints Rule 0: ...BannedDependencies passed and BUILD SUCCESS; both runs were
made on 2026-10-10 with Maven and JDK 21. The rule is a floor, not a scanner: it knows nothing about bulletins that are
published after the date it was written, so the floor has to be raised by a person when a new bulletin names a higher
fixed version. A dependency checker in the continuous integration pipeline covers the transitive libraries that the
bulletins do not name (the Jackson case of S2-055 is the example).
Worked Example: Probing the Classic Attack Shapes on 7.4.0
The second half of the audit asks whether the shapes of the historical attacks still do anything on 7.4.0. The Bookshelf
action books has one parameter, filter, declared with @StrutsParameter, and the JSP forced.jsp echoes it in four
ways. All probes use harmless arithmetic (7*7 would print 49 if it were evaluated) against the scratch application.
public class BookAction extends ActionSupport {
private String filter;
public String getFilter() { return filter; }
@StrutsParameter
public void setFilter(String filter) { this.filter = filter; }
public List<String> getTitles() { return List.of("Dune", "Neuromancer"); }
}
<%@ taglib prefix="s" uri="/struts-tags" %>
<p id="a">[<s:property value="filter"/>]</p>
<p id="b">[<s:property value="%{filter}"/>]</p>
<s:textfield name="q" id="%{filter}" value="x"/>
The probes and what 7.4.0 answered (HTTP status in parentheses; session ids that appear in rewritten links were removed from the output):
| Probe (the shape of the bulletin) | Request | Result on 7.4.0 |
|---|---|---|
Expression in a parameter value (S2-007, S2-029 family) |
|
The page prints |
Forced evaluation in the attribute of a tag (S2-029, S2-059, S2-061, S2-062) |
|
The generated input has |
ClassLoader manipulation through a parameter name (S2-020, S2-021, CVE-2014-0114 shape) |
|
The request is answered normally (200), |
OGNL in the parameter name (S2-003, S2-005, S2-009 shape) |
|
Answered normally (200); nothing is evaluated |
Dynamic Method Invocation, |
|
Answered by the normal |
Dynamic Method Invocation with |
|
404; DMI is disabled by default |
|
|
No redirect (no |
|
|
200, the response does not contain 49; the application’s page is rendered |
Malformed multipart |
|
200, the page is rendered with an empty |
Source: Struts Security Guide — Do not use incoming, untrusted user input in forced expression evaluation
The conclusion is limited on purpose. A clean probe proves that one shape no longer works in one configuration; it does not prove that the application is secure, and the bulletins S2-075 to S2-078 show that the dangerous settings are the ones that were switched off, such as the OGNL allowlist (S2-075 says Struts 7 is affected only when the allowlist is disabled). The hardening page of this section lists the settings that must stay at their defaults.
Following the Announcements
The project publishes security news through four channels, and a team should subscribe to at least the first.
| Channel | What it carries |
|---|---|
|
The announcements mailing list named on the project’s mailing list page, with the subscribe address |
The Announcements pages ( |
One entry per release and per bulletin, with an anchor of the form |
The Security Bulletins index on the wiki |
The index of the S2 pages, the authoritative list of bulletins |
|
The private address for reporting a vulnerability (never for ordinary bugs, which go to the issue tracker); the project asks reporters to read the security guideline first |
Source: Apache Struts — Mailing Lists
Two practical rules. A bulletin appears on the wiki and in the announcements together, so a delay of days between an announcement and the Bookshelf’s upgrade is a window in which the bulletin is public and the application is not patched; the reporting page says that the project takes an active stance against security problems and denial of service attacks and that it cannot promise workarounds for generic problems such as a client streaming a lot of data; its stated philosophy is to avoid attacks that make the server consume resources in a non-linear relationship to the size of the input, which is the kind of fix the 2026 bulletins describe. Second, a release announcement without a bulletin can still be a security release: the announcement of 7.4.0 and 6.12.0 came three days before the bulletins S2-075 to S2-078 were published, and an application that waited for the bulletin gained nothing.
Beyond the Struts Bulletins
Struts runs inside a container, and the container publishes its own security pages. The Tomcat CVEs that touch JSP and the default servlet (CVE-2024-50379 and CVE-2024-56337 for uploads treated as JSP on case-insensitive file systems, CVE-2024-52318 for the XSS in generated JSPs from unreleased pooled tags, CVE-2025-24813 for the partial PUT, CVE-2017-12617 for a JSP upload by PUT and CVE-2020-1938 "Ghostcat" for AJP) are described with their fixed versions in Security, which also lists the verified table of the container side. The Struts 1 record CVE-2014-0114 belongs to the Struts 1 page of this section. The Jackson record CVE-2017-7525 is the one S2-055 points at, and the general rule it teaches is that a Struts bulletin may be a library bulletin with a Struts version number on it.
Pitfalls
| Pitfall | Why it hurts | What to do |
|---|---|---|
Reading only the "Fixed" column |
Several bulletins list branches as affected that have no fix (EOL), and a patch release may exist for another branch only |
Read the Affected Software ranges against the exact version in the WAR, then the Recommendation |
Treating the rating as the severity of the damage |
The scale is about exploitability: S2-075 is a code execution class bug rated Moderate because the default allowlist blocks it |
Read the Problem and the "not affected" notes, and keep the defaults that provide the mitigation |
Upgrading to "latest 7.x" without the release number |
S2-070 and S2-071 affect 7.2.1 only |
Pin the |
Trusting an old fix to be complete |
S2-005 after S2-003, S2-061 after S2-059 and S2-062 after S2-061 show that the first fix is sometimes incomplete |
Follow the chain of "similar to" bulletins and take the last fixed version |
Keeping the old upload mechanism after upgrading |
S2-067 says that keeping the old mechanism keeps the application vulnerable; in 7.x the interceptor is removed and the old code silently receives nothing |
Migrate to |
Copying a CVE id from an announcement without opening the record |
Ids differ from habits (the 2026 ids are long) and transcription errors create wrong claims |
Look up each id on the CVE record service or NVD and compare the title with the bulletin |
Staying on 2.5.x with a commercial extension and no plan |
The extension is a bridge for a migration, and the project does not endorse it |
Schedule the 6.x or 7.x migration and treat the extension as temporary |
Switching off the OGNL allowlist to make an old expression work |
S2-075 depends on the allowlist being disabled for Struts 7 |
Add the package to |
Leaving Config Browser or devMode in production |
S2-043 (Low) says that using the Config Browser plugin in production is itself the problem; the security guide says to restrict it and to disable devMode |
Remove the plugin from the production WAR and keep |
Never raising the Enforcer floor |
A floor written in 2026 knows nothing about the next bulletin |
Update the rule whenever a bulletin names a higher fixed version, and keep a dated note |
What Changed Recently
-
2023: S2-063 and S2-064 (June) and S2-065 and S2-066 (December) close denial of service and path traversal in upload handling; Struts 2.5.x reaches end-of-life on 30 October 2023.
-
2024: S2-067 (December) forces the migration to the Action File Upload mechanism of 6.4.
-
2025: S2-068 (December) names 6.8.0 and 7.1.1 together as the fixed versions, the pattern of the 6.x and 7.x lines patched in parallel that continues in 2026.
-
2026: S2-069 (January) is an XXE in the XWork component fixed in 6.1.1; S2-070 to S2-074 (August) fix limits and shared state in the 7.2.1 line and are fixed in 7.3.0 and 6.11.0; S2-075 to S2-078 (October) are fixed in 7.4.0 and 6.12.0, released on 2 October 2026. As of 2026-10-10 no bulletin lists 7.4.0 as an affected version.
Where the Books and Old Tutorials Are Outdated
Struts 2 in Action (2008, Struts 2.0) and the Struts 1 book Struts in Action (2002) predate almost every bulletin on this page, so the model they teach is the one the bulletins replaced. The statements below describe what that model was and what the bulletins and the 7.4.0 defaults say today.
-
Parameters are bound to every setter that the request names. The 2.0 model relies on the interceptor to filter dangerous names; the bulletins S2-003, S2-005, S2-009, S2-020 and S2-021 are that filter being bypassed or extended. Struts 6.4 and 7 made binding opt-in with the
@StrutsParameterannotation (see the parameter injection page of this section). -
Dynamic Method Invocation and the
!,method:,action:andredirect:syntax are normal. S2-016, S2-019, S2-032 and S2-033 turned them into attack surface; DMI is off by default (S2-019) and the 7.4.0 probe above shows the prefixes ignored. -
%{...}can be used freely in any tag attribute. S2-029, S2-059, S2-061 and S2-062 are the history of why forced evaluation of request data is dangerous; the security guide’s rule is the up-to-date version. -
The
fileUploadinterceptor withFile,xxxContentTypeandxxxFileNamesetters. S2-066 and S2-067 ended it; the 7.x equivalent is theactionFileUploadinterceptor. -
Struts 1 is a supported choice. It has been end-of-life since 5 April 2013, and CVE-2014-0114 names Struts 1.x through 1.3.10; the Struts 1 book is for reading legacy code, not for starting projects.
-
The version of the example is the version to use. The Struts 2.0 and 2.1 versions of that era are inside the affected ranges of S2-003 (2.0.0 to 2.1.8.1) and of every later bulletin that begins at 2.0.0.
-
Security is a chapter, not a build step. A build that fails on a vulnerable dependency, like the Enforcer rule above, is a modern habit that the books do not show.