Back to Blog
medium SEVERITY6 min read

How XML External Entity (XXE) Injection Happens in Python and How to Fix It

A critical XML External Entity (XXE) vulnerability was discovered in `scripts/screenshots/ui.py` where the native Python `xml.etree.ElementTree` library was used without XXE protections. The fix replaces the vulnerable import with `defusedxml.ElementTree`, which disables external entity processing by default and prevents attackers from exploiting XML parsing to access sensitive files or execute denial-of-service attacks.

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

Answer Summary

XML External Entity (XXE) injection is a vulnerability in Python's native `xml.etree.ElementTree` library (CWE-611) that allows attackers to read arbitrary files, perform SSRF attacks, or cause denial-of-service through malicious XML payloads containing external entity definitions. The fix is to replace the native XML library with `defusedxml.ElementTree`, which disables external entity processing by default and safely rejects XXE payloads.

Vulnerability at a Glance

cweCWE-611 (Improper Restriction of XML External Entity Reference)
fixReplace `xml.etree.ElementTree` with `defusedxml.ElementTree` to disable external entity processing
riskAttackers can read arbitrary files, perform SSRF attacks, or cause denial-of-service through malicious XML payloads
languagePython
root causeNative Python XML libraries enable external entity processing by default, allowing entity expansion attacks
vulnerabilityXML External Entity (XXE) Injection

How XML External Entity (XXE) Injection Happens in Python and How to Fix It

Introduction

In the scripts/screenshots/ui.py file, a critical XML External Entity (XXE) vulnerability was discovered at line 9 where the code imported Python's native xml.etree.ElementTree library:

import xml.etree.ElementTree as ET

This seemingly innocent import created a significant security risk. The native Python XML libraries enable external entity processing by default, which means any XML parsed by this code could be weaponized by an attacker to read sensitive files, perform server-side request forgery (SSRF) attacks, or trigger denial-of-service conditions through XML bombs.

The vulnerability matters because the UI screenshot automation tool likely processes XML configuration files or responses from external services. If an attacker could inject malicious XML into these inputs, they could potentially read the system's SSH keys, database credentials, or other sensitive files stored on the same server where the screenshot tool runs.

The Vulnerability Explained

XML External Entity (XXE) injection is a server-side vulnerability that exploits how XML parsers handle external entity definitions. Here's how it works:

The Vulnerable Code Pattern:

When Python's xml.etree.ElementTree parses XML without explicit security configuration, it honors external entity declarations like this:

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

If the screenshot tool parsed this malicious XML, the parser would attempt to read /etc/passwd and include its contents in the parsed document. An attacker could then exfiltrate that data through error messages or response content.

The Specific Risk in ui.py:

The scripts/screenshots/ui.py file uses the vulnerable import at line 9:

import xml.etree.ElementTree as ET

Any subsequent calls to ET.parse() or ET.fromstring() in this file would be vulnerable. For example:

tree = ET.parse('config.xml')  # Vulnerable to XXE
root = tree.getroot()

Attack Scenarios:

  1. File Disclosure: An attacker could craft a malicious XML configuration file that, when parsed, reads /etc/passwd or application secrets from environment variables written to XML files.

  2. SSRF (Server-Side Request Forgery): Using XXE to make the server connect to internal services:
    xml <!ENTITY xxe SYSTEM "http://localhost:8080/admin">

  3. Denial-of-Service (XML Bomb): The "billion laughs" attack uses entity expansion to consume server memory:
    ```xml

    <!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
    <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
    ]>
    &lol3;
    ```

Why This Matters for This Code:

The screenshot automation tool likely processes XML from configuration files or external APIs. If any of these sources are attacker-controlled or can be manipulated through a supply chain attack, the XXE vulnerability becomes exploitable. The tool runs with the permissions of the user or service account that executes it, so any files readable by that account are at risk.

The Fix

The fix is simple but critical: replace Python's native XML library with defusedxml, which is hardened against XXE attacks.

Before (Vulnerable):

import xml.etree.ElementTree as ET

After (Secure):

import defusedxml.ElementTree as ET

This single-line change completely eliminates the XXE vulnerability because defusedxml disables external entity processing, DTD processing, and other dangerous XML features by default.

How This Solves the Problem:

The defusedxml library is a drop-in replacement for Python's native XML libraries that:

  1. Disables external entity processing – The parser no longer honors <!ENTITY> declarations that reference external files or URLs
  2. Prevents billion laughs attacks – Entity expansion is limited or disabled
  3. Maintains API compatibility – Code using ET.parse(), ET.fromstring(), etc. works identically without modification
  4. Provides sensible defaults – Security is enabled automatically; developers don't need to remember to configure parser options

When defusedxml.ElementTree encounters the malicious XXE payload shown earlier, it silently ignores the external entity definition and parses only the safe XML structure. The attacker's attempt to read /etc/passwd fails silently, preventing file disclosure.

Prevention & Best Practices

For Python Developers:

  1. Always use defusedxml instead of native XML libraries – Make it a project standard:
    ```python
    # Good
    import defusedxml.ElementTree as ET

# Bad
import xml.etree.ElementTree as ET
```

  1. Add defusedxml to your project dependencies:
    bash pip install defusedxml

  2. Audit existing code for vulnerable XML imports – Search your codebase for:
    - import xml.etree.ElementTree
    - import xml.dom.minidom
    - import xml.sax
    - import xmlrpc

All of these should be replaced with their defusedxml equivalents.

  1. Use static analysis tools – Configure Semgrep, Bandit, or your SAST scanner to flag native XML library usage and recommend defusedxml.

  2. Never disable XXE protections – Even if you think you need to process external entities, use defusedxml with explicit configuration rather than reverting to unsafe libraries.

Security Standards:

  • CWE-611: Improper Restriction of XML External Entity Reference
  • OWASP A05:2021: Security Misconfiguration (XXE is often categorized here)
  • OWASP A03:2021: Injection (XXE is a form of injection attack)

Key Takeaways

  • Native Python XML libraries are unsafe by defaultxml.etree.ElementTree, xml.dom.minidom, and xml.sax all enable external entity processing, creating XXE vulnerabilities.

  • defusedxml is a drop-in replacement – You can change a single import statement and gain complete XXE protection without rewriting any other code.

  • XXE affects more than file disclosure – This vulnerability can be chained with SSRF attacks to access internal services, or weaponized as a denial-of-service vector through XML bombs.

  • Static analysis caught this proactively – Semgrep flagged the vulnerable import in scripts/screenshots/ui.py:9 before it could be exploited, demonstrating the value of automated security scanning.

  • Defense in depth matters – Even though the screenshot tool may not directly expose XML parsing to untrusted users, replacing the native library removes an exploit primitive that could be chained with other vulnerabilities.

How Orbis AppSec Detected This

Source: XML configuration files or API responses parsed by the screenshot automation tool in scripts/screenshots/ui.py

Sink: The xml.etree.ElementTree import at line 9, which is used for all subsequent XML parsing operations in the file via ET.parse() and ET.fromstring() calls

Missing control: No protection against external entity processing; the native Python XML library was used without disabling XXE-vulnerable features

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

XML External Entity (XXE) injection is a powerful attack vector that Python developers often overlook because the vulnerability is hidden in library defaults rather than in application code. The fix is straightforward: replace native XML libraries with defusedxml.

This vulnerability in scripts/screenshots/ui.py demonstrates why proactive security hardening matters. Even if the screenshot tool isn't directly exposed to untrusted XML input today, removing this exploit primitive raises the bar against increasingly sophisticated automated attack tools. By switching to defusedxml with a single-line change, the development team eliminated an entire class of vulnerabilities without sacrificing functionality or performance.

Make defusedxml a standard practice in your Python projects. It's a small change with massive security benefits.


References

Frequently Asked Questions

What is XML External Entity (XXE) injection?

XXE is a vulnerability where an attacker injects malicious XML entity definitions into XML input, allowing them to read arbitrary files, perform SSRF attacks, or cause denial-of-service through billion laughs attacks (XML bombs).

How do you prevent XXE injection in Python?

Use the `defusedxml` library instead of native Python XML libraries. The defusedxml library disables external entity processing, DTD processing, and other dangerous XML features by default.

What CWE is XXE injection?

CWE-611: Improper Restriction of XML External Entity Reference. This is one of the OWASP Top 10 risks and affects multiple XML parsing libraries across many programming languages.

Is input validation enough to prevent XXE?

No. Blacklist-based validation is unreliable because XXE payloads can be encoded in many ways. The proper fix is to disable external entity processing at the parser level using defusedxml.

Can static analysis detect XXE vulnerabilities?

Yes. Tools like Semgrep, Bandit, and commercial SAST scanners can detect unsafe use of native XML libraries and recommend switching to defusedxml.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #18

Related Articles

medium

How Hardcoded AWS Credentials Happen in Node.js Configuration Files and How to Fix It

A critical security issue was discovered in the S3 Express deployment configuration file where an AWS Secret Access Key was hardcoded as a placeholder example. This vulnerability could allow attackers to gain unauthorized access to AWS resources if the example file was accidentally deployed to production or committed to version control without proper sanitization.

medium

How Hardcoded AWS Secret Access Keys Happen in Configuration Files and How to Fix Them

A hardcoded AWS Secret Access Key pattern was detected in the `settings.example` configuration file of an nginx AWS credentials module. While the value itself was a placeholder string, its format matched a real AWS secret key pattern, making it a dangerous template that could mislead developers into committing real credentials. The fix replaces the lookalike secret value with an unambiguous placeholder that cannot be mistaken for or used as a real credential.

medium

How Denial of Service via ZIP Bomb happens in Node.js adm-zip and how to fix it

The cc-viewer application was vulnerable to Denial of Service attacks through the adm-zip library (version 0.5.17), which could be exploited using specially crafted ZIP files that trigger excessive memory allocation. Upgrading to adm-zip 0.6.0 resolves CVE-2026-39244 by implementing proper safeguards against ZIP bomb attacks and malicious archive structures.

medium

How GitHub Actions Mutable Action Tags Enable Supply-Chain Attacks and How to Fix Them

A GitHub Actions workflow was using `actions/checkout@v1`, a mutable tag reference that could be silently repointed by the action owner to inject malicious code. This supply-chain vulnerability was fixed by pinning the action to a specific commit SHA (`11bd71901bbe5b1630ceea73d27597364c9af683`), ensuring the workflow always executes verified, immutable code.

medium

How Uninitialized Memory Vulnerabilities Happen in Rust and How to Fix Them

The fuser crate (versions prior to 0.16.0) contained a critical vulnerability that allowed uninitialized memory to be read and leaked through FUSE operations. This security issue was fixed by upgrading fuser from 0.15.1 to 0.16.0, which tightens memory handling and prevents potential information disclosure in applications that interact with the filesystem via FUSE.