CVE-2025-68493

Struts S2-069 (CVE-2025-68493): The Undying Ghost of XML Parsing Past

Alon Barad
Alon Barad
Software Engineer

Jan 13, 2026·6 min read

Executive Summary (TL;DR)

A critical XXE flaw in Apache Struts versions 6.0.0 through 6.1.0 (and older EOL versions) allows attackers to weaponize XML configuration parsing. By injecting malicious DTDs, attackers can exfiltrate sensitive files (/etc/passwd) or pivot into internal networks via SSRF. The fix involves upgrading to 6.1.1, which explicitly disables unsafe XML features in the SAXParserFactory.

Apache Struts has returned to the spotlight with S2-069, a classic XML External Entity (XXE) vulnerability residing in the XWork component. Despite being 2026, the framework's core configuration parser failed to disable external entity resolution, allowing attackers with control over configuration inputs to read local files, trigger SSRF, or cause Denial of Service.

The Hook: Struts, The Cockroach of the Internet

If you've been in security for more than five minutes, you know Apache Struts. It is the unkillable beast of the Java ecosystem, the framework that gave us the Equifax breach, and the reason many SysAdmins wake up screaming in the middle of the night. Just when we thought we had moved on to newer, shinier frameworks with their own problems (looking at you, Next.js), Struts pulls us back in.

This time, we aren't looking at OGNL injection (thank the gods), but an old friend: XML External Entity (XXE) Injection. The vulnerability, designated CVE-2025-68493 (S2-069), hits the XWork component. XWork is the engine room of Struts 2; it handles the command pattern framework, interceptors, and—crucially for us—configuration parsing.

In the modern era of 2026, you might ask: "Who is still writing code that parses XML without disabling external entities?" The answer, apparently, is the Apache Software Foundation. This flaw is particularly nasty because it affects the fundamental way Struts loads its own instructions. If you can influence the XML that XWork digests, you own the server's eyes and ears.

The Flaw: Failure to Communicate (Securely)

The root cause of CVE-2025-68493 is a tale as old as XML itself. The Java specification for SAXParserFactory is, by default, permissive. It assumes that if you are parsing XML, you probably want to resolve every single entity defined in it, including those that point to the filesystem or the internet.

When XWork initializes its internal parsers to read configuration files (like struts.xml or plugin configurations), it spins up a SAXParser. In the affected versions (6.0.0 through 6.1.0, plus the graveyard of EOL versions), the developers neglected to toggle the "paranoia switches."

Specifically, they failed to disable:

  • http://xml.org/sax/features/external-general-entities
  • http://xml.org/sax/features/external-parameter-entities
  • http://apache.org/xml/features/nonvalidating/load-external-dtd

This omission means the parser blindly follows instructions. If an XML node says "Go fetch the contents of /etc/shadow and paste it right here," the parser dutifully complies. It’s like locking your front door (authentication) but leaving the window wide open with a sign that says "Free Wi-Fi and File Access."

The Code: Anatomy of a Patch

Let's look at the smoking gun. The vulnerability lies in the helper classes responsible for creating the SAX parser instance. Below is a reconstruction of the vulnerable logic versus the hardened logic introduced in version 6.1.1.

The Vulnerable Implementation:

// Creating a parser the 1999 way
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
factory.setValidating(true);
// ... missing security features ...
SAXParser parser = factory.newSAXParser();
parser.parse(inputStream, handler);

The Secure Implementation (Patch):

In the fix, the developers explicitly tell the factory to stop being so trusting. They effectively neuter the parser's ability to talk to the outside world.

SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
 
// The Fix: Explicitly disabling DTDs and External Entities
try {
    factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
    factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
    factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
    factory.setXIncludeAware(false);
} catch (ParserConfigurationException e) {
    // Log warning
}
 
SAXParser parser = factory.newSAXParser();

The difference is subtle in lines of code but massive in security posture. The patched version throws an exception or simply ignores the malicious !DOCTYPE declarations, rendering the XXE payload inert.

The Exploit: Reading Files and Surfing Intranets

How do we weaponize this? The attack vector relies on the application parsing untrusted XML. In Struts, this could happen if an attacker can upload a plugin, modify a configuration file via a separate vulnerability, or if the application exposes an endpoint that accepts XML which is then processed by XWork's XmlConfigurationProvider.

Here is the classic attack flow:

  1. Recon: Attacker identifies an endpoint or upload vector processed by the XWork XML parser.
  2. Payload Construction: We create an XML document with a malicious Document Type Definition (DTD).

The Payload (File Read):

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
  <!ELEMENT foo ANY >
  <!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<foo>&xxe;</foo>

When the vulnerable Struts application parses this, it sees &xxe; and replaces it with the contents of /etc/passwd. If the application reflects this parsed data back in an error message or a UI element (common in dashboards), the attacker gets the file content.

The Payload (SSRF):

Even if we can't see the output (Blind XXE), we can force the server to make HTTP requests.

<!DOCTYPE foo [
  <!ELEMENT foo ANY >
  <!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/" >]>
<foo>&xxe;</foo>

This requests the AWS metadata service. If the Struts server is an EC2 instance, the parser fetches the temporary credentials, which the attacker can then use to compromise the cloud environment.

The Impact: Why You Should Care

Why is an 8.1 CVSS score scary? Because XXE is a gateway drug to full system compromise. It isn't just about reading /etc/hosts to prove a point.

1. Confidentiality Loss: Source code, database configurations (db.properties), and SSH keys are often readable by the user running the web server. One file read can lead to a database dump.

2. Server-Side Request Forgery (SSRF): In modern cloud-native deployments (Kubernetes, AWS), internal metadata services are the keys to the kingdom. An XXE vulnerability essentially turns the web server into a proxy for the attacker to explore the internal network, hitting Redis instances, internal admin panels, or cloud metadata APIs.

3. Denial of Service (DoS): The "Billion Laughs" attack. By defining recursive entities (Entity A refers to ten Entity Bs, which refer to ten Entity Cs...), an attacker can craft a 1KB XML file that expands into gigabytes of memory usage, crashing the JVM and taking the application offline.

The Mitigation: Patching the Unpatchable

The remediation path is straightforward but critical. You need to upgrade your Struts libraries. The magic number is 6.1.1.

If you are stuck on Struts 2.5.x or (heaven forbid) 2.3.x, you are running End-of-Life software and have bigger problems than just this CVE. However, for those in the 6.0.0 branch, upgrading is mandatory.

Immediate Actions:

  1. Update: Bump struts2-core to version 6.1.1 or higher.
  2. Verify: Ensure no transitive dependencies are pulling in older XWork libraries.

Emergency Workarounds (The Nuclear Option):

If you cannot redeploy immediately, you can attempt to restrict XML parsing globally via JVM arguments. This is risky as it might break legitimate functionality that relies on external DTDs, but it stops the bleeding:

java -Djavax.xml.accessExternalDTD="" \
     -Djavax.xml.accessExternalSchema="" \
     -Djavax.xml.accessExternalStylesheet="" \
     -jar your-application.jar

This tells the underlying Java XML processor: "Do not allow access to external protocols, period." It's a blunt instrument, but effective against standard XXE.

Technical Appendix

CVSS Score
8.1/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H
EPSS Probability
0.04%
Top 89% most exploited

Affected Systems

Apache Struts 2.0.0 - 2.3.37 (EOL)Apache Struts 2.5.0 - 2.5.33 (EOL)Apache Struts 6.0.0 - 6.1.0

Affected Versions Detail

Product
Affected Versions
Fixed Version
Apache Struts
Apache
>= 2.0.0, <= 2.3.37N/A (EOL)
Apache Struts
Apache
>= 2.5.0, <= 2.5.33N/A (EOL)
Apache Struts
Apache
>= 6.0.0, <= 6.1.06.1.1
AttributeDetail
Attack VectorNetwork (XML Configuration Injection)
CVSS v3.18.1 (High)
CWE IDCWE-611 (XXE)
ImpactInformation Disclosure, SSRF, DoS
EPSS Score0.00037 (Low Probability)
Exploit StatusProof of Concept (PoC) Available
CWE-611
Improper Restriction of XML External Entity Reference

The software processes an XML document that can contain XML entities with URIs that resolve to documents outside of the intended sphere of control, causing the product to embed incorrect documents into its output.

Vulnerability Timeline

Internal Advisory Created by Apache Team
2025-12-19
CVE-2025-68493 Published
2026-01-11
Technical Deep Dive Published by Penligent
2026-01-12

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.