Securing an Apache Struts application comes down to three customer-side habits: keep internet-facing actions to a minimum, run a supported framework version with security updates applied, and lock down how request data reaches the OGNL expression language. Long-lived Struts applications need the same patching discipline as new ones. This guide covers how authentication and authorisation actually work around a Struts application, the configuration constants that close off OGNL injection, how to treat the REST, JSON and Config Browser plugins, what to log and detect, and how to plan an exit from end-of-life branches including Struts 1. It is written for Java developers, application security engineers and the operations teams who own legacy Struts estates. For general Java guidance see the Java and Spring Security Guide; this page stays on Struts.
This is an independent guide from NHI Mgmt Group, based on the Apache Struts project's public documentation as of November 2026. It is not sponsored or reviewed by the Apache Struts project. Product features change, so confirm settings against the Apache Struts project's current documentation.
Key takeaways
- Version is the first control. As of November 2026 the supported lines are Struts 7.x (current 7.4.0, Java 17 and Jakarta EE) and Struts 6.x (current 6.12.0, Java 8); 2.5.x, 2.3.x and 1.x are end of life and get no fixes.
- OGNL is the recurring attack surface. Most critical Struts bulletins are expression injection or parameter binding flaws, so turn on the allowlist, require
@StrutsParameterannotations and keep the excluded class lists intact. - Struts does not authenticate users. The servlet container, a filter or a separate framework does, usually by URL pattern, which is why mixing access levels in one namespace and exposing JSPs directly break authorisation.
- File upload code needs a rewrite, not just an upgrade. Current releases use the Action File Upload mechanism, and moving to it is not backward compatible.
- Patch speed and detection both failed at Equifax. GAO found an out-of-date notification list, a scan that missed the vulnerable portal and an expired certificate that blinded traffic inspection for about ten months.
How identity and access work in Apache Struts
Struts is an MVC framework, not an identity product. It maps HTTP requests to action classes through an interceptor stack and renders results, usually JSP or FreeMarker templates. Who the user is and what they may reach is decided elsewhere, and the gaps between those layers are where access control fails.
| Layer | What it decides | Typical mechanism | Struts-specific risk |
|---|---|---|---|
| Servlet container | Authentication, coarse authorisation by URL | web.xml <security-constraint>, container realms, SSO agents | URL patterns protect a namespace, so an admin action in a public namespace is public |
| Servlet filter or security framework | Authentication, session, CSRF, role checks | Spring Security, Apache Shiro, a corporate SSO filter in front of the Struts filter | Filter order: the security filter must run before StrutsPrepareAndExecuteFilter |
| Interceptor stack | Per-action checks | Roles interceptor, custom authorisation interceptors | Custom interceptors left out of one package's stack silently skip checks |
| Action and parameter binding | Which request values reach Java objects | Parameters interceptor, @StrutsParameter, accepted and excluded patterns | Unannotated setters and getters let attackers set fields you never meant to expose |
| Views | What is rendered | JSP, FreeMarker, Struts tags | JSPs reachable directly bypass the action and its checks |
The Struts security guideline gives two rules that follow from this design:
- Group actions by security level: because access is commonly enforced by URL pattern (for example
/secure/*requiring roleadmin), never mix actions with different access levels in the same namespace. - Never expose JSP files directly: keep JSPs under
WEB-INFand add a*.jspsecurity constraint mapped to a role that no user holds. The project recommends both. Since Struts 7.2.0 the framework logs a security warning when JSP tags are used outside an action scope, which helps find exposed pages. - Disable empty namespace fallback: set
struts.actionConfig.fallbackToEmptyNamespace=falseso actions in the default namespace cannot be reached through other paths.
Admin roles and privileged access
Privileged access to a Struts application comes in three forms: framework features that expose internals, administrative actions inside the application, and the accounts that deploy and run it.
- devMode: development mode relaxes behaviour and exposes debugging. The guideline is explicit that production
struts.xmlmust set<constant name="struts.devMode" value="false" />. Treat any production build with devMode true as a release blocker. - Config Browser Plugin: it exposes the internal configuration and is meant for development only. Remove the jar from production builds; if a team insists, put it behind authentication as the project advises.
- Dynamic Method Invocation: set
struts.enable.DynamicMethodInvocation=falseand use Strict Method Invocation so only declared methods can be called on an action. - Administrative actions: place them in their own namespace with their own security constraint and interceptor stack, require step-up authentication for destructive operations, and review them separately in penetration tests.
- Deployment and runtime accounts: the application server should run as a dedicated non-root account with write access only to log and temp directories. A Struts remote code execution flaw runs code with exactly that account's rights, so the account boundary is your blast radius. Manager consoles of the container (Tomcat Manager, WebLogic console) should never be reachable from the internet.
Patching and version support
Struts security is dominated by framework vulnerabilities, so version management deserves its own section. The project publishes security bulletins (S2-nnn) on its wiki and announcements on struts.apache.org, and reports go privately to [email protected].
| Branch | Status (checked 8 Nov 2026) | Platform | What it means for you |
|---|---|---|---|
| 7.x | Supported, 7.4.0 released 2 October 2026 | Java 17, Jakarta EE | Target line; parameter annotations required and OGNL allowlist enabled by default |
| 6.x | Supported, 6.12.0 released 2 October 2026 | Java 8, Servlet API 3.1 | Stepping stone; enable annotations and allowlist yourself |
| 2.5.x | End of life (announced 30 October 2023) | javax | No fixes; migrate |
| 2.3.x | End of life since 12 September 2019 | javax | No fixes; migrate |
| 1.x | End of life since 5 April 2013; last release 1.3.10 in December 2008 | javax | No fixes and no drop-in replacement; a rewrite project |
Apply Struts security updates promptly and follow the Apache Struts security bulletins. If you do not accept XML in the REST plugin, remove the XStream handler. See the Emergency Out-of-Band Patching Playbook.
Integrations, API tokens and service accounts
A Struts application is rarely alone. It holds credentials for databases, directories and downstream APIs, and its plugins open extra request formats. An attacker who achieves code execution inherits all of them, which is what turned the Equifax portal into a route to other databases.
- Datasource and API credentials: keep them out of
struts.properties,struts.xmland WAR files. Use container-managed JNDI datasources or a secrets manager, scope each database account to the tables the application needs, and rotate after any suspected compromise. See the Secrets Management Guide. - REST plugin: limit content type handlers to what you need; S2-052 showed the risk of an XML handler nobody used. Apply authentication and rate limits as you would for any API.
- JSON plugin: the 2026 bulletins on unbounded body reads and shared parsing state mean the plugin must be current. Put request size limits in front of it at the proxy.
- Legacy RESTful action mapper: if configured, upgrade for S2-075 and confirm the allowlist is on.
- Outbound calls: restrict egress from application servers to known destinations so a compromised action cannot freely reach the internet or cloud metadata endpoints.
Hardening baseline
These constants come from the Struts security guideline. Set them in struts.xml or struts.properties, and extend lists rather than shrinking them: the project warns against reducing the default excluded classes.
| Setting | Recommended value | Why |
|---|---|---|
struts.devMode | false | No debug behaviour in production |
struts.parameters.requireAnnotations | true (available from 6.4, default from 7.0) | Only fields marked @StrutsParameter accept request values; use depth for nested objects |
struts.parameters.requireAnnotations.transitionMode | true only while migrating | Requires annotations for nested parameters while leaving public setters exposed |
struts.allowlist.enable | true (with struts.allowlist.classes and struts.allowlist.packageNames as needed) | Classes used in OGNL must be allowlisted as well as not excluded |
struts.excludedClasses, struts.excludedPackageNames, struts.excludedPackageNamePatterns | Defaults, extended | Block reflection and runtime classes from expressions |
struts.ognl.valueStackFallbackToContext | false | Stops expressions reaching the ActionContext |
struts.ognl.excludedNodeTypes | Configure (OGNL Guard, off by default) | Forbids chosen syntax nodes, mitigating string concatenation tricks |
struts.ognl.expressionMaxLength | Default 256 or lower | Long injected expressions are refused |
struts.ognl.allowStaticFieldAccess | false | Non-primitive static fields can reach dangerous objects |
struts.disallowProxyObjectAccess, struts.disallowDefaultPackageAccess, struts.ognl.disallowCustomOgnlMap | true | Close known bypass routes |
struts.enable.DynamicMethodInvocation | false | Only declared methods are callable |
struts.actionConfig.fallbackToEmptyNamespace | false | No cross-namespace action resolution |
Code-level rules from the same guideline: do not define setters that are not needed; never feed incoming values into localisation logic or forced OGNL evaluation (%{...}); prefer Struts tags to raw EL; define custom error pages so stack traces do not leak; and use the FetchMetadata, COOP and COEP interceptors for browser-side isolation. Validate input with the validation framework, but remember validation does not fix an injectable expression.
Rolling out hardening and upgrades
- Inventory: find every Struts application, including vendor products that embed it. Look for
struts2-coreandstruts-core(Struts 1) jars in WAR files and SBOMs, and record version, internet exposure and owner. See the SBOM Guide. - Triage by exposure: internet-facing applications on 2.x or 1.x come first; put them behind authentication or a WAF virtual patch while you work, accepting that a WAF is a delay, not a fix.
- Upgrade within supported lines: move 6.x to the latest 6.x release, then plan 7.x (Java 17 and the
jakarta.*namespace are the real effort). - Enable annotations in transition mode: on 6.4 or later turn on
requireAnnotationswithtransitionMode, add@StrutsParameterto legitimate fields, then switch transition mode off. - Enable the allowlist in a test environment: run the regression suite and watch logs for blocked class warnings, adding only what the application genuinely needs.
- Rewrite file upload actions to the Action File Upload mechanism and test traversal cases explicitly.
- Roll back safely: deploy as a new versioned artefact behind a load balancer so you can revert the artefact, but do not roll back to a version missing security updates; revert configuration constants one at a time instead.
- Struts 1 and 2.3/2.5: treat as a migration project with a dated plan and an interim compensating control set (isolation, WAF, authentication in front, aggressive monitoring). Extended commercial support exists for EOL branches; the Apache Software Foundation does not endorse any offering.
Logging and detection
Exploitation of Struts usually looks like T1190 Exploit Public-Facing Application followed by command execution or a web shell (T1505.003). Detection should cover the request, the JVM and the host.
| Signal | Where | What to look for |
|---|---|---|
| OGNL syntax in requests | Reverse proxy, WAF, access logs | %{, ${, #context, @java.lang in headers (especially Content-Type), parameter names or filenames |
| Framework warnings | Application log | Excluded or non-allowlisted class warnings, parameter rejection messages, direct JSP access warnings (7.2.0+) |
| Child processes | EDR or process auditing on app servers | The Java process spawning shells, curl, wget or scripting interpreters |
| New files in web roots | File integrity monitoring | JSP or class files appearing outside deployments |
| Unusual egress | Firewall, proxy, DNS | App servers contacting new hosts; large outbound transfers from database tiers |
Equifax's own detection failed because an expired certificate stopped encrypted traffic inspection for about ten months, according to GAO. Monitor the health of your monitoring: certificate expiry on inspection devices, log source silence and agent coverage. See the Web Shell Response Playbook.
Metrics that show it is working:
- Percentage of Struts applications on a supported branch and its latest release.
- Median days from a Struts bulletin to production deployment, tracked separately for Critical bulletins.
- Number of applications with
requireAnnotationsandallowlist.enableon. - Count of Struts 1, 2.3 and 2.5 applications still running, trending to zero against a dated plan.
- Coverage of app servers by process monitoring and file integrity monitoring.
Common misconfigurations
- Notification that reaches nobody: GAO found that Equifax circulated the March 2017 US-CERT notice using an out-of-date recipient list, so the people who would have patched never received it. Map every Struts application to a current owner.
- Scanning that misses the asset: a scan about a week later did not detect the vulnerable portal. Pair network scanning with software composition analysis of the deployed artefacts.
- Upgrading but keeping the deprecated upload interceptor: S2-067 stays exploitable on a patched version if actions still use
FileUploadInterceptor. - Shrinking the excluded class lists to make a feature work, reopening the classes that OGNL exploits rely on.
- Admin actions in a public namespace or JSPs reachable directly, bypassing URL-based security constraints.
- devMode or the Config Browser Plugin shipped to production because the build profile was never split.
Framework mapping
| Control area | NIST SP 800-53 Rev 5 | NIST CSF 2.0 | Other |
|---|---|---|---|
| Patch Struts and dependencies | SI-2 Flaw Remediation, RA-5 | ID.RA-01, PR.PS-02 | CIS Control 7; ISO/IEC 27001:2022 A.8.8 |
| Retire EOL branches | SA-22 Unsupported System Components | PR.PS-02 | CIS Control 2 |
| Hardening constants and plugin removal | CM-6, CM-7 | PR.PS-01 | ISO/IEC 27001:2022 A.8.9 |
| Parameter binding and input handling | SI-10 | PR.PS-01 | CIS Control 16; ISO/IEC 27001:2022 A.8.28 |
| Namespace and JSP access control | AC-3, AC-6 | PR.AA-05 | OWASP ASVS 5.0 |
| Exploit and web shell detection | AU-6 | DE.CM-09 | ATT&CK T1190, T1505.003 |
Practitioner checklist
- Inventory every Struts application and embedded copy with version, owner and exposure.
- Move all applications to the latest 7.x or 6.x release; set a dated plan for every 2.x and 1.x application.
- Set
struts.devMode=falseand remove the Config Browser Plugin from production builds. - Enable
struts.parameters.requireAnnotationsandstruts.allowlist.enable, and keep excluded class lists at defaults or larger. - Disable Dynamic Method Invocation, empty namespace fallback and static field access.
- Rewrite file uploads to the Action File Upload mechanism and remove unused REST handlers.
- Keep JSPs under
WEB-INF, add a*.jspdeny constraint and group actions by access level. - Move datasource and API credentials into a secrets manager with least-privilege database accounts.
- Alert on Java processes spawning shells, new JSP files in web roots and OGNL syntax in requests.
- Subscribe owners to Struts announcements and track bulletin-to-deploy time, prioritising Critical bulletins.
Standards and references
- Apache Struts: Security guideline
- Apache Struts: End-of-Life Versions
- Apache Struts: Struts 1 EOL Announcement
- Apache Struts: Security Bulletin S2-067
- Apache Struts: Announcements 2026
- MITRE ATT&CK: Exploit Public-Facing Application
Related NHI Mgmt Group resources: Java and Spring Security Guide · Patch Management Guide · Web Application Firewall Guide · Secure Software Development Lifecycle (SSDLC) Guide