FlawOpen

flawopen.com/Внешние XML-сущности (XXE)/Безопасность XML-парсеров Python

Безопасен ли xml.etree.ElementTree в Python от XXE?

CWE-611: ссылка на внешнюю XML-сущностьСправочная страница
Краткий ответ

От классического XXE с чтением файлов и сетью встроенные парсеры Python на Expat по умолчанию не читают локальные файлы и не открывают сетевые соединения. Но не всякая обработка XML безопасна: Expat ниже 2.7.2 может быть уязвим к DoS через расширение сущностей и длинные токены. Настройки lxml также зависят от версии. Фиксируйте и явно настраивайте парсер либо используйте defusedxml для недоверенного XML.

РИСК — версия и параметры меняют поверхность атаки

XXE (CWE-611):
Парсер разрешает внешнюю сущность из XML под контролем атакующего; возможно чтение локальных файлов или запросы от сервера.
DoS через расширение сущностей:
Вложенные или повторные ссылки потребляют чрезмерные CPU или память при раскрытии.
Expat:
C-парсер встроенных модулей XML Python; активную версию проверяйте через pyexpat.EXPAT_VERSION.
DTD:
Объявление типа документа может задавать сущности и, в зависимости от настроек, запрашивать внешние ресурсы.
Шаг 1

Недоверенный XML попадает в парсер

Загрузка файла, SVG, сообщение SAML или интеграционные данные проходят границу доверия и передаются XML API.

Шаг 2

Реализация и настройки определяют поведение

Стандартная библиотека использует Expat, lxml — libxml2. Версия, внешнее разрешение, расширение сущностей и флаги определяют доступные действия.

Шаг 3

Разрешение или расширение вызывает последствия

При включённом внешнем разрешении сущность может запросить локальный или удалённый ресурс. Уязвимое расширение и длинные токены расходуют ресурсы.

Шаг 4

Права процесса ограничивают последствия

Права на файлы и сетевой egress ограничивают XXE; лимиты входа, обновлённый парсер, безопасные флаги и изоляция снижают риск.

Текущие рекомендации Python: Expat по умолчанию не читает локальные файлы и не устанавливает сетевые соединения. Однако версии ниже 2.7.2 могут быть уязвимы к billion laughs, quadratic blowup, длинным токенам и чрезмерному расходу памяти. Проверяйте pyexpat.EXPAT_VERSION: Python может использовать встроенный или системный Expat. Это риск истощения ресурсов для определённых версий, а не признак того, что актуальный ElementTree разрешает внешние сущности.

ЗАЩИТА — отклонять DTD и внешние ресурсы
# 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)
Три разных риска XML-парсеров
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.

Выбирайте безопасные параметры именно развернутого парсера

В defusedxml.ElementTree объявления сущностей и внешние ссылки запрещены по умолчанию; также задайте forbid_dtd=True, если формат не требует DTD. Для lxml явно установите resolve_entities=False, load_dtd=False, no_network=True и huge_tree=False. Текущие версии по умолчанию разрешают только внутренние сущности; до lxml 5.0 разрешение было включено. Не запускайте XInclude для недоверенного дерева без ограниченного загрузчика. Ограничьте размер до распаковки и анализа: флаги не ограничивают все затраты ресурсов.

Почему значения парсера нужно проверять с учётом версии

«Python XML» не означает единую конфигурацию. Стандартная библиотека использует Expat — встроенный или системный в зависимости от сборки. lxml оборачивает libxml2 и изменил стандарт разрешения сущностей в версии 5.0; обёртки и явные параметры тоже влияют. Записывайте версии Python, Expat, lxml и libxml2 и задавайте флаги у каждого места обработки недоверенного ввода.

Частые вопросы

Читает ли ElementTree локальные файлы через внешние сущности по умолчанию?

Нет. Python указывает, что Expat по умолчанию не читает локальные файлы и не открывает сеть. Проверьте версию и отличайте доступ через XXE от истощения ресурсов в старых сборках.

Опасен ли текущий lxml по умолчанию?

Не обобщайте для всех версий. Текущий XMLParser по умолчанию разрешает только внутренние сущности, не загружает DTD и не использует сеть. В lxml 5.0 параметр resolve_entities изменён с True на internal. Явно указывайте настройки и проверяйте обёртки.

Запрещает ли defusedxml любые DTD по умолчанию?

Нет. Объявления сущностей и внешние ссылки запрещены, но forbid_dtd=False. Установите True, если формат не требует DTD.

Устраняет ли безопасный парсер все XML-риски для ресурсов?

Нет. Ограничьте сжатый и распакованный ввод, обновляйте парсеры, не включайте huge_tree и не запускайте XInclude для недоверенных деревьев с неограниченным загрузчиком.

Первичные источники

См. также Внедрение внешних сущностей XML (XXE, CWE-611): как работает и как предотвратить — flawopen.com Защищён ли .NET XmlReader от XXE? XXE в Java: безопасна ли DocumentBuilderFactory?