XXE Injection

An XML external entity (XXE) is an entity whose value the parser fetches from a URI. If an attacker can submit XML to a parser that resolves such entities, the parser may read local files into the document or make network requests on the attacker's behalf (server-side request forgery). XXE turns up in SOAP endpoints, SAML handlers, document converters and feed importers.

The safest stance is to refuse DTDs altogether, since data feeds rarely need them. defusedxml (github.com/tiran/defusedxml (https://github.com/tiran/defusedxml 554 ), PSF licence; 0.7.1) wraps Python's standard parsers with that policy. Here it parses the catalog, then two harmless documents with a DTD:

defused.py: what defusedxml accepts and refusesPython
import defusedxml.ElementTree as DET
from defusedxml import DTDForbidden, EntitiesForbidden
docs = {
    "plain catalog": open("booknest-catalog.xml", "rb").read(),
    "internal entity": b'<!DOCTYPE b [<!ENTITY shop "BookNest">]><b>&shop;</b>',
    "doctype only": b'<!DOCTYPE b><b/>',
}
for name, data in docs.items():
    for kwargs in ({}, {"forbid_dtd": True}):
        try:
            root = DET.fromstring(data, **kwargs)
            result = f"parsed <{root.tag}>"
        except (EntitiesForbidden, DTDForbidden) as e:
            result = type(e).__name__
        print(f"{name:16} {str(kwargs):22} {result}")
Output
plain catalog    {}                     parsed <catalog>
plain catalog    {'forbid_dtd': True}   parsed <catalog>
internal entity  {}                     EntitiesForbidden
internal entity  {'forbid_dtd': True}   DTDForbidden
doctype only     {}                     parsed <b>
doctype only     {'forbid_dtd': True}   DTDForbidden

By default it allows a bare DOCTYPE but refuses any entity declaration; forbid_dtd=True refuses every DTD. lxml vs ElementTree showed current lxml 3,063 and ElementTree also declining to load external entities by default; other libraries differ, which is why Configuring Parsers Safely configures each one explicitly.