flawopen.com/Внешние XML-сущности (XXE)/Безопасность XML-парсеров Python
Безопасен ли xml.etree.ElementTree в Python от XXE?
От классического 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:
- Объявление типа документа может задавать сущности и, в зависимости от настроек, запрашивать внешние ресурсы.
Недоверенный XML попадает в парсер
Загрузка файла, SVG, сообщение SAML или интеграционные данные проходят границу доверия и передаются XML API.
Реализация и настройки определяют поведение
Стандартная библиотека использует Expat, lxml — libxml2. Версия, внешнее разрешение, расширение сущностей и флаги определяют доступные действия.
Разрешение или расширение вызывает последствия
При включённом внешнем разрешении сущность может запросить локальный или удалённый ресурс. Уязвимое расширение и длинные токены расходуют ресурсы.
Права процесса ограничивают последствия
Права на файлы и сетевой egress ограничивают XXE; лимиты входа, обновлённый парсер, безопасные флаги и изоляция снижают риск.
Текущие рекомендации Python: Expat по умолчанию не читает локальные файлы и не устанавливает сетевые соединения. Однако версии ниже 2.7.2 могут быть уязвимы к billion laughs, quadratic blowup, длинным токенам и чрезмерному расходу памяти. Проверяйте pyexpat.EXPAT_VERSION: Python может использовать встроенный или системный Expat. Это риск истощения ресурсов для определённых версий, а не признак того, что актуальный ElementTree разрешает внешние сущности.
# 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.
Выбирайте безопасные параметры именно развернутого парсера
В defusedxml.ElementTree объявления сущностей и внешние ссылки запрещены по умолчанию; также задайте forbid_dtd=True, если формат не требует DTD. Для lxml явно установите resolve_entities=False, load_dtd=False, no_network=True и huge_tree=False. Текущие версии по умолчанию разрешают только внутренние сущности; до lxml 5.0 разрешение было включено. Не запускайте XInclude для недоверенного дерева без ограниченного загрузчика. Ограничьте размер до распаковки и анализа: флаги не ограничивают все затраты ресурсов.
- ✓Инвентаризируйте XML-точки входа и парсеры: ElementTree/Expat, lxml/libxml2, SAX/DOM, SVG, SAML, импорт архивов и последующие шаги XInclude.
- ✓Используйте поддерживаемый Python с Expat 2.7.2 или новее, если доступно; проверяйте активную версию через
pyexpat.EXPAT_VERSION. - ✓Для недоверенного XML используйте
defusedxml; запрещайте DTD черезforbid_dtd=True, если схема их не требует. - ✓При использовании lxml зафиксируйте поддерживаемую версию и явно отключите сущности, DTD, сеть и huge-tree; не полагайтесь на меняющиеся значения по умолчанию.
- ✓Ограничьте сжатый и распакованный размер, при необходимости изолируйте парсер и проверяйте безвредные DTD/entity-тесты в CI без внешних запросов.
Почему значения парсера нужно проверять с учётом версии
«Python XML» не означает единую конфигурацию. Стандартная библиотека использует Expat — встроенный или системный в зависимости от сборки. lxml оборачивает libxml2 и изменил стандарт разрешения сущностей в версии 5.0; обёртки и явные параметры тоже влияют. Записывайте версии Python, Expat, lxml и libxml2 и задавайте флаги у каждого места обработки недоверенного ввода.
Частые вопросы
Нет. Python указывает, что Expat по умолчанию не читает локальные файлы и не открывает сеть. Проверьте версию и отличайте доступ через XXE от истощения ресурсов в старых сборках.
Не обобщайте для всех версий. Текущий XMLParser по умолчанию разрешает только внутренние сущности, не загружает DTD и не использует сеть. В lxml 5.0 параметр resolve_entities изменён с True на internal. Явно указывайте настройки и проверяйте обёртки.
Нет. Объявления сущностей и внешние ссылки запрещены, но forbid_dtd=False. Установите True, если формат не требует DTD.
Нет. Ограничьте сжатый и распакованный ввод, обновляйте парсеры, не включайте huge_tree и не запускайте XInclude для недоверенных деревьев с неограниченным загрузчиком.