Back to Blog
medium SEVERITY5 min read

How XML External Entity (XXE) Injection happens in Python and how to fix it

A high-severity XML External Entity (XXE) vulnerability was discovered in `utils/commands_extractors/find_java_repo_commands.py` where Python's native `xml.etree.ElementTree` library was used to parse potentially untrusted XML input. The fix replaces it with `defusedxml.ElementTree`, which disables external entity processing by default, preventing attackers from reading sensitive files or making unauthorized network requests.

O
By Orbis AppSec
Published August 28, 2026Reviewed August 28, 2026

Answer Summary

This is an XML External Entity (XXE) injection vulnerability (CWE-611) in Python caused by using the native `xml.etree.ElementTree` library to parse untrusted XML. The native library allows external entity expansion by default, enabling attackers to read local files or perform SSRF. The fix is to replace `import xml.etree.ElementTree as ET` with `import defusedxml.ElementTree as ET`, which disables DTD processing and external entity resolution.

Vulnerability at a Glance

cweCWE-611
fixReplace xml.etree.ElementTree with defusedxml.ElementTree
riskAttackers can read sensitive files (e.g., /etc/passwd), perform SSRF, or cause denial of service
languagePython
root causeUsing Python's native xml.etree.ElementTree which does not disable external entity processing
vulnerabilityXML External Entity (XXE) Injection

Introduction

In the production codebase of a web service, the file utils/commands_extractors/find_java_repo_commands.py was responsible for parsing XML content—likely Maven POM files or similar Java project configuration—to extract build commands. At line 7, it imported Python's native xml.etree.ElementTree library:

import xml.etree.ElementTree as ET

This single import created a high-severity attack surface. Because this code runs in a web service context where request handlers process external input, a remote attacker could craft malicious XML payloads to read sensitive files from the server, perform Server-Side Request Forgery (SSRF), or trigger denial-of-service conditions.

The Vulnerability Explained

Python's standard library XML parsers—including xml.etree.ElementTree, xml.dom.minidom, and xml.sax—do not disable Document Type Definition (DTD) processing by default. This means they will happily resolve external entities defined in a <!DOCTYPE> declaration.

Here's the vulnerable import in find_java_repo_commands.py:

import xml.etree.ElementTree as ET

When find_java_repo_commands() processes XML input using this library, an attacker can submit a payload like:

<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root><command>&xxe;</command></root>

When the parser encounters &xxe;, it resolves the entity by reading /etc/passwd from the server's filesystem. The contents of that file then appear in the parsed XML output—potentially returned to the attacker or logged where they can access it.

Attack Scenarios Specific to This Code

Since find_java_repo_commands() extracts commands from XML (likely parsing pom.xml or similar files from repositories), an attacker who can influence the XML content being parsed could:

  1. File Disclosure: Inject <!ENTITY xxe SYSTEM "file:///etc/shadow"> to exfiltrate credential hashes
  2. SSRF: Use <!ENTITY xxe SYSTEM "http://internal-server.local/admin"> to probe internal network services
  3. Denial of Service: Reference file:///dev/random or use recursive entity expansion (the "Billion Laughs" attack) to exhaust server memory

The PR description confirms this is a web service where "vulnerabilities in request handlers are directly exploitable by remote attackers," making this a critical exposure point.

The Fix

The fix is elegant in its simplicity—a single-line change that swaps the vulnerable native library for a hardened alternative:

Before (Vulnerable)

import xml.etree.ElementTree as ET

After (Fixed)

import defusedxml.ElementTree as ET

That's it. Because defusedxml provides a drop-in replacement API that mirrors xml.etree.ElementTree, no other code changes are needed. The find_java_repo_commands() function continues to work identically for valid XML inputs, but now:

  • DTD processing is disabled<!DOCTYPE> declarations are rejected
  • External entity resolution is blockedSYSTEM and PUBLIC entities cannot fetch files or URLs
  • Entity expansion is limited — recursive/nested entity attacks are prevented

The defusedxml library achieves this by wrapping the native parser with secure defaults, explicitly setting forbid_dtd=True, forbid_entities=True, and forbid_external=True at the parser level.

Verification

The PR includes a comprehensive regression test that validates the security invariant with multiple XXE payloads:

@pytest.mark.parametrize("payload", [
    # Valid input (boundary case)
    "<?xml version='1.0'?><root><command>mvn clean</command></root>",
    # XXE exploit payload - attempts to read sensitive file
    """<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root><command>&xxe;</command></root>""",
    # ... additional payloads
])
def test_xml_processing_resists_xxe_attacks(payload):
    # Ensures no sensitive data leaks through

This test confirms that after the fix, malicious payloads either raise safe exceptions or return empty/filtered results—never leaked file contents.

Prevention & Best Practices

1. Never Use Native Python XML Libraries for Untrusted Input

The Python documentation itself warns about this. Always use defusedxml when parsing XML from external sources:

pip install defusedxml

2. Apply the Principle of Least Privilege to Parsers

Even if you think your XML input is trusted, defense in depth means disabling features you don't need. You almost never need DTD processing or external entity resolution.

3. Use SAST Tools to Catch Unsafe Imports

Bandit rules B313-B410 and Semgrep's gitlab.bandit.B313...B410 rule specifically flag imports of native Python XML libraries. Integrate these into your CI/CD pipeline.

4. Audit All XML Parsing Across Your Codebase

If one file uses the native library, others likely do too. Run a grep for import xml. across your project and replace all instances.

5. Consider JSON or YAML Alternatives

If you control the data format, consider whether XML is necessary. JSON and YAML don't have entity expansion features, eliminating this attack class entirely.

Key Takeaways

  • xml.etree.ElementTree is unsafe for untrusted input — Python's native XML libraries are explicitly documented as vulnerable to XXE attacks
  • defusedxml is a drop-in replacement — changing one import line in find_java_repo_commands.py fixed the vulnerability without modifying any parsing logic
  • Web services parsing repository XML are high-value targets — since find_java_repo_commands() processes XML from repositories (like pom.xml), any attacker who can influence repository content can exploit XXE
  • Static analysis catches this pattern reliably — Bandit/Semgrep rules specifically target native XML library imports, making this a fully automatable detection
  • One vulnerable import can compromise an entire server — from a single import xml.etree.ElementTree, an attacker can read arbitrary files, scan internal networks, and cause denial of service

How Orbis AppSec Detected This

  • Source: XML content from external repositories processed by the find_java_repo_commands() function
  • Sink: xml.etree.ElementTree parser invocations in utils/commands_extractors/find_java_repo_commands.py:7
  • Missing control: No restriction on DTD processing or external entity resolution in the XML parser
  • CWE: CWE-611 (Improper Restriction of XML External Entity Reference)
  • Fix: Replaced import xml.etree.ElementTree as ET with import defusedxml.ElementTree as ET to disable external entity processing by default

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

XXE vulnerabilities in Python are uniquely dangerous because they hide behind an innocent-looking standard library import. The native xml.etree.ElementTree module provides no protection against malicious DTD declarations, and Python's own documentation recommends against using it for untrusted input. In a web service context like this one—where find_java_repo_commands.py parses XML from external repositories—the exposure is direct and exploitable.

The fix demonstrates that security improvements don't have to be complex. A single import change from xml.etree.ElementTree to defusedxml.ElementTree closes the vulnerability completely while maintaining full API compatibility. Make this substitution a standard practice in every Python project that handles XML.

References

Frequently Asked Questions

What is XML External Entity (XXE) injection?

XXE is a vulnerability where an attacker crafts malicious XML with external entity declarations that cause the XML parser to fetch local files, make network requests, or consume excessive resources during parsing.

How do you prevent XXE in Python?

Use the `defusedxml` library instead of Python's native XML libraries (xml.etree.ElementTree, xml.dom, xml.sax). defusedxml disables DTD processing and external entity resolution by default.

What CWE is XXE?

CWE-611: Improper Restriction of XML External Entity Reference. It falls under the broader injection vulnerability category.

Is input validation enough to prevent XXE?

No. While input validation can help, it's insufficient because DTD declarations can be obfuscated. The correct approach is to disable external entity processing at the parser level, which defusedxml does automatically.

Can static analysis detect XXE vulnerabilities?

Yes. Tools like Semgrep, Bandit, and other SAST scanners can detect imports of vulnerable native XML libraries and flag them. The Bandit rules B313-B410 specifically target unsafe XML parsing in Python.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #780

Related Articles

high

How Cache-Control Header Injection Happens in Node.js HTTP Libraries and How to Fix It

CVE-2026-13697 is a high-severity vulnerability in the undici HTTP client library where the cache interceptor mishandles malformed Cache-Control directives, potentially leading to information disclosure and denial of service attacks. Upgrading from undici 7.28.0 to 7.29.0 (or 8.9.0 for v8 users) patches this vulnerability by implementing stricter validation of Cache-Control headers. This fix is critical for any Node.js application that relies on undici for HTTP requests, especially those handlin

critical

How XML Multiple Root Element Injection happens in Node.js and how to fix it

The foam3 project contained a critical vulnerability in xmldom version 0.6.0 that allowed attackers to create malformed XML documents with multiple root elements, violating the XML specification and potentially bypassing security validations. The fix removed the vulnerable xmldom dependency entirely from package.json and package-lock.json, eliminating the attack surface.

critical

How Prototype Pollution happens in Node.js and how to fix it

A critical prototype pollution vulnerability was discovered in `worker/import-core.js`, where `request.json()` parsed untrusted HTTP request bodies without filtering dangerous keys like `__proto__` and `constructor`. An attacker could send a crafted JSON payload to corrupt the global `Object` prototype, potentially affecting every object in the application runtime. The fix replaces the unsafe parse with a JSON reviver function that strips these dangerous keys before any object is constructed.

critical

How Server-Side Template Injection happens in Node.js EJS and how to fix it

CVE-2022-29078 is a critical server-side template injection (SSTI) vulnerability in EJS versions prior to 3.1.7, where the `outputFunctionName` option is passed directly into generated code without sanitization, allowing attackers to execute arbitrary JavaScript on the server. The fix upgrades the EJS dependency from 2.7.4 to 3.1.7+ (resolved here as 6.0.1), eliminating the unsafe code generation path. Any Node.js application rendering EJS templates with user-influenced options is at risk of ful

high

How Prototype Pollution happens in JavaScript via defu and how to fix it

CVE-2026-35209 is a high-severity prototype pollution vulnerability in the `defu` JavaScript library (versions prior to 6.1.5), where a crafted `__proto__` key in the defaults argument can corrupt the global Object prototype. The fix upgrades `defu` from 6.1.4 to 6.1.5 in `pnpm-lock.yaml` and enforces the version via a workspace override, closing the attack surface in production code that depends on `defu` for deep object merging.

medium

How Path Traversal and Filename Injection Happens in Python File Handling and How to Fix It

A medium-severity path traversal vulnerability in `PainterNode/painter_node.py` allowed attackers to reference files outside the intended directory by exploiting a broken `isFileName()` validation function. The original logic used incorrect boolean operators, meaning the filename guard never actually blocked malicious inputs like `../../../etc/passwd` or paths containing backslashes. The fix rewrites the condition with proper logic and adds explicit checks for path separator characters and direc