flawopen.com/Entidades externas XML (XXE)/Seguridad de parsers XML en Python
¿Es seguro contra XXE xml.etree.ElementTree de Python?
Para el XXE clásico de archivos y red, los parsers integrados de Python basados en Expat no acceden a archivos locales ni abren conexiones de red de forma predeterminada. Eso no hace seguro todo uso de XML: Expat anterior a 2.7.2 puede sufrir DoS por expansión de entidades y tokens largos. Los valores predeterminados de lxml varían según la versión. Fija y configura explícitamente los parsers o usa defusedxml con XML no confiable.
RIESGOSO — la versión y las opciones del parser cambian la exposición
- XXE (CWE-611):
- El parser resuelve una entidad externa de XML controlado por el atacante y puede leer archivos locales o hacer solicitudes desde el servidor.
- DoS por expansión de entidades:
- Referencias anidadas o repetidas consumen CPU o memoria desproporcionadas al expandirse.
- Expat:
- Parser C de los módulos XML integrados de Python; comprueba la versión activa con
pyexpat.EXPAT_VERSION. - DTD:
- Una declaración de tipo de documento puede definir entidades y, según la configuración, solicitar recursos externos.
XML no confiable llega al parser
Una carga, SVG, mensaje SAML o dato de integración cruza el límite de confianza y llega a una API XML.
La implementación y las opciones determinan el comportamiento
La biblioteca estándar usa Expat; lxml usa libxml2. Versión, resolución externa, expansión y flags definen lo que puede ocurrir.
La resolución o expansión produce un efecto
Si se habilita la resolución externa, una entidad puede pedir contenido local o remoto. La expansión vulnerable o los tokens largos pueden agotar recursos.
Los permisos del proceso limitan el impacto
Permisos de archivos y salida de red acotan el XXE; límites de entrada, parsers actualizados, flags seguras y aislamiento reducen la exposición.
La guía actual de Python indica que Expat no accede a archivos locales ni crea conexiones de red de forma predeterminada. También advierte que versiones anteriores a 2.7.2 pueden ser vulnerables a billion laughs, quadratic blowup, tokens largos o consumo desproporcionado de memoria; comprueba pyexpat.EXPAT_VERSION, pues Python puede usar Expat incluido o del sistema. Es un riesgo de agotamiento según la versión, no una prueba de que ElementTree actual resuelva entidades externas.
# Standard-library external access is off by default, # but old Expat builds have resource-exhaustion risks. import xml.etree.ElementTree as ET from xml.parsers import expat print(expat.EXPAT_VERSION) # inspect the runtime parser root = ET.fromstring(untrusted_xml) # lxml before 5.0 defaulted resolve_entities=True. # Explicit unsafe settings illustrate the risky boundary. from lxml import etree parser = etree.XMLParser(resolve_entities=True, load_dtd=True) root = etree.fromstring(untrusted_xml, parser)
from defusedxml.ElementTree import fromstring
# Reject DTD declarations when the format does not require them.
root = fromstring(untrusted_xml, forbid_dtd=True)
# If lxml is required, set its security options explicitly.
from lxml import etree
parser = etree.XMLParser(
resolve_entities=False,
no_network=True,
load_dtd=False,
huge_tree=False,
)
root = etree.fromstring(untrusted_xml, parser)
# Do not run XInclude on untrusted trees without a restricted loader.
Elige opciones seguras para el parser desplegado
En defusedxml.ElementTree, las declaraciones de entidades y referencias externas se prohíben de forma predeterminada; añade forbid_dtd=True si el formato aceptado no necesita DTD. En lxml, define explícitamente resolve_entities=False, load_dtd=False, no_network=True y huge_tree=False. Las versiones actuales solo resuelven entidades internas por defecto; antes de lxml 5.0 se resolvían. No ejecutes XInclude en árboles no confiables sin un loader restringido. Limita el tamaño antes de descomprimir y analizar: las flags no acotan todos los costes.
- ✓Inventaría todos los puntos de entrada y parsers XML: ElementTree/Expat, lxml/libxml2, SAX/DOM, SVG, SAML, importación de archivos y cualquier paso XInclude posterior.
- ✓Usa una versión mantenida de Python con Expat 2.7.2 o posterior cuando sea posible; verifica la versión activa con
pyexpat.EXPAT_VERSION, sin asumir qué copia se usa. - ✓Para XML no confiable, usa
defusedxml; rechaza DTD conforbid_dtd=Truesi el esquema no las necesita. - ✓Si usas lxml, fija una versión mantenida y desactiva explícitamente entidades, carga DTD, red y huge-tree; no dependas de valores predeterminados cambiantes.
- ✓Limita el tamaño comprimido y descomprimido, aísla el análisis cuando corresponda y prueba fixtures benignas DTD/entidad en CI sin hacer solicitudes externas.
Por qué revisar los valores del parser según la versión
“XML de Python” no es una configuración única. La biblioteca estándar usa Expat, incluido o del sistema según la compilación del intérprete. lxml envuelve libxml2 y cambió el valor predeterminado de resolución de entidades en 5.0; wrappers y opciones pueden cambiarlo otra vez. Registra Python, Expat, lxml y libxml2, y declara las flags en cada punto de entrada no confiable.
Preguntas frecuentes
No. Python documenta que Expat no accede a archivos locales ni abre conexiones de red por defecto. Comprueba la versión y diferencia la lectura de entidades externas de los problemas de agotamiento en parsers antiguos.
No generalices entre versiones. El XMLParser actual de lxml resuelve solo entidades internas, no carga DTD y no usa la red de forma predeterminada. resolve_entities cambió de True a internal en lxml 5.0. Define las opciones explícitamente y revisa los wrappers.
No. Sus valores predeterminados prohíben entidades y referencias externas, pero forbid_dtd es False. Pásalo a True si el formato no requiere DTD.
No. Limita datos comprimidos y descomprimidos, actualiza los parsers, evita huge_tree y no procesa XInclude no confiable con un loader sin restricciones.