Description
An XML parser that resolves external entities can access files or network resources chosen by an attacker. Unrestricted input size or entity expansion can also cause denial of service (DoS). The actual risk depends on the parser version and configuration.
Potential impact
- Information disclosure: Resolving external entities may expose sensitive file contents.
- Denial of service: Excessive input or entity expansion may exhaust server memory and CPU.
- Access to internal resources: If network references are permitted, an attacker may cause requests to resources reachable by the server.
Remediation
- Disable external-entity resolution and unnecessary DTD loading.
- Limit input size and processing resources, and handle parsing errors.
- Keep Python, the XML parser, and its underlying libraries updated with security fixes.
Examples
lxml
Before
python
# XML parsing with the default lxml parser
from lxml import etree
def unsafe_parse(xml_string):
root = etree.fromstring(xml_string)
return root
xml_data = """<?xml version="1.0"?>
<!DOCTYPE root [
<!ELEMENT root ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<root>&xxe;</root>"""
root = unsafe_parse(xml_data)
After
python
# XML parsing without entity expansion
from lxml import etree
def safe_parse(xml_string):
parser = etree.XMLParser(resolve_entities=False, load_dtd=False, no_network=True)
root = etree.fromstring(xml_string, parser=parser)
return root
xml_data = """<?xml version="1.0"?>
<!DOCTYPE root [
<!ELEMENT root ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<root>&xxe;</root>"""
root = safe_parse(xml_data)
Explanation:
- Before: This uses the default parser. Since lxml 5.0,
XMLParserdefaults toresolve_entities="internal", so this call does not necessarily read the external file. Check older versions or a changed default parser separately. - After: Entity expansion, external DTD loading, and network access are explicitly disabled. The example configures
lxmldirectly instead of using the unmaintaineddefusedxml.lxmlmodule.
ElementTree
Before
python
# XML parsing with standard ElementTree
import xml.etree.ElementTree as ET
def unsafe_parse(xml_string):
root = ET.fromstring(xml_string)
return root
xml_data = """<?xml version="1.0"?>
<!DOCTYPE root [
<!ELEMENT root ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<root>&xxe;</root>"""
root = unsafe_parse(xml_data)
After
python
# Safe XML parsing with defusedxml
from defusedxml.ElementTree import fromstring
def safe_parse(xml_string):
root = fromstring(xml_string)
return root
xml_data = """<?xml version="1.0"?>
<!DOCTYPE root [
<!ELEMENT root ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<root>&xxe;</root>"""
root = safe_parse(xml_data)
Explanation:
- Before: Standard ElementTree raises a parsing error rather than expanding this external entity. The default call does not itself demonstrate file disclosure.
- After:
defusedxml.ElementTreerejects entity declarations by default, so this input raisesEntitiesForbidden. Handle the error at the application boundary and apply input-size limits separately.