Как я могу использовать различные XML-библиотеки PHP, чтобы получить DOM-подобную функциональность и избежать DoS-уязвимостей, таких как Billion Laughs или Quadratic Blowup?
Я пишу веб-приложение с XML API на PHP и беспокоюсь о трех конкретных уязвимостях, связанных с определением встроенного DOCTYPE: включение локального файла, разложение квадратичного объекта и разложение экспоненциального объекта. Мне бы хотелось использовать встроенные в PHP библиотеки (5.3), но я хочу убедиться, что я не подвержен им.
Я обнаружил, что могу устранить LFI с помощью libxml_disable_entity_loader, но это не помогает с встроенными объявлениями ENTITY, включая сущности, которые ссылаются на другие сущности.
Библиотека SimpleXML (SimpleXMLElement, simplexml_load_string и т. Д.) Великолепна, потому что это анализатор DOM и все мои входные данные довольно малы; это позволяет мне использовать xpath и довольно легко манипулировать DOM. Я не могу понять, как остановить объявления ENTITY. (Я был бы рад отключить все встроенные определения DOCTYPE, если это возможно.)
Библиотека XML Parser (xml_parser_create, xml_set_element_handler и т. Д.) Позволяет мне установить обработчик по умолчанию, который включает в себя сущности, с помощью xml_set_default_handler. Я могу взломать его, чтобы для нераспознанных объектов он просто возвращал исходную строку (т. Е. "& Ent;"). Хотя эта библиотека разочаровывает: поскольку это SAX-парсер, я должен написать несколько обработчиков (целых 9..).
Так можно ли использовать встроенные библиотеки, извлекать DOM-подобные объекты и защищать себя от этих различных DoS-уязвимостей? Спасибо
Эта страница описывает три уязвимости и предоставляет решение... если бы я только использовал.NET: http://msdn.microsoft.com/en-us/magazine/ee335713.aspx
ОБНОВИТЬ:
<?php
$s = <<<EOF
<?xml version="1.0?>
<!DOCTYPE data [
<!ENTITY en "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa....">
]>
<data>&en;&en;&en;&en;&en;&en;&en;&en;&en;&en;&en;&en;.....</data>
EOF;
$doc = new DOMDocument();
$doc->loadXML($s);
var_dump($d->lastChild->nodeValue);
?>
Я старался loadXML($s, LIBXML_NOENT);
также. В обоих случаях я получаю более 300 МБ. Есть ли что-то, что я все еще скучаю?
2 ответа
Примечание. Если вы создадите тестовые примеры с файлами, которые содержат фрагменты XML, в следующем порядке, ожидайте, что редакторы также могут быть подвержены этим атакам и могут зависать / падать.
Миллиард смеха
<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>
При загрузке:
ФАТАЛЬНО: #89: Обнаружен эталонный цикл объекта 1: 7
... (плюс шесть раз то же самое = в семь раз больше с выше)
ФАТАЛЬНО: #89: Обнаружен цикл ссылки на сущность 14:13
Результат:
<?xml version="1.0"?>
Использование памяти невелико, пик не затронут DOMDocument
, Поскольку этот пример показывает 7 фатальных ошибок, можно сделать вывод, и это действительно так, что это загружает без ошибок:
<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
]>
<lolz>&lol2;</lolz>
Поскольку замена сущностей не действует и эта работа, давайте попробуем
Квадратичный взрыв
Вот этот здесь, сокращенный для вашего удовольствия от просмотра (мои варианты около 27/11kb):
<?xml version="1.0"?>
<!DOCTYPE kaboom [
<!ENTITY a "aaaaaaaaaaaaaaaaaa...">
]>
<kaboom>&a;&a;&a;&a;&a;&a;&a;&a;&a;...</kaboom>
Если вы используете $doc->loadXML($src, LIBXML_NOENT);
это работает как атака, пока я пишу это, скрипт все еще загружается.... Так что на самом деле это занимает некоторое время для загрузки и потребляет память. То, что вы можете играть со своим собственным. W/ о LIBXML_NOENT
работает без нареканий и быстро.
Но есть оговорка, если вы получите nodeValue
тега, например, вы расширите сущности, даже если вы не используете этот флаг загрузки.
Обойти эту проблему можно, удалив узел DocumentType из документа. Обратите внимание на следующий код:
$doc = new DOMDocument();
$doc->loadXML($s); // where $s is a Quadratic attack xml string above.
// now remove the doctype node
foreach ($doc->childNodes as $child) {
if ($child->nodeType===XML_DOCUMENT_TYPE_NODE) {
$doc->removeChild($child);
break;
}
}
// Now the following is true:
assert($doc->doctype===NULL);
assert($doc->lastChild->nodeValue==='...');
// Note that entities remain unexpanded in the output XML
// This is not so good since this makes the XML invalid.
// Better is a manual walk through all nodes looking for XML_ENTITY_NODE
assert($doc->saveXML()==="<?xml version="1.0"?>\n<kaboom>&a;&a;&a;&a;&a;&a;&a;&a;&a;...</kaboom>\n");
// however, canonicalization will produce warnings because it must resolve entities
assert($doc->C14N()===False);
// Warning will be like:
// PHP Warning: DOMNode::C14N(): Node XML_ENTITY_REF_NODE is invalid here
Таким образом, хотя этот обходной путь не позволяет XML-документу потреблять ресурсы в DoS, он позволяет легко генерировать недопустимый XML.
Некоторые цифры (я уменьшил размер файла, иначе это занимает слишком много времени) ( код):
LIBXML_NOENT disabled LIBXML_NOENT enabled
Mem: 356 184 (Peak: 435 464) Mem: 356 280 (Peak: 435 464)
Loaded file quadratic-blowup-2.xml into string. Loaded file quadratic-blowup-2.xml into string.
Mem: 368 400 (Peak: 435 464) Mem: 368 496 (Peak: 435 464)
DOMDocument loaded XML 11 881 bytes in 0.001368 secs. DOMDocument loaded XML 11 881 bytes in 15.993627 secs.
Mem: 369 088 (Peak: 435 464) Mem: 369 184 (Peak: 435 464)
Removed load string. Removed load string.
Mem: 357 112 (Peak: 435 464) Mem: 357 208 (Peak: 435 464)
Got XML (saveXML()), length: 11 880 Got XML (saveXML()), length: 11 165 132
Got Text (nodeValue), length: 11 160 314; 11.060893 secs. Got Text (nodeValue), length: 11 160 314; 0.025360 secs.
Mem: 11 517 776 (Peak: 11 532 016) Mem: 11 517 872 (Peak: 22 685 360)
Я до сих пор не решил о стратегиях защиты, но теперь знаю, что загрузка миллиарда смеха в PHPStorm, например, заморозит его, и я прекратил тестировать позже, так как не хотел останавливать его, когда писал это.
Вы должны на самом деле протестировать ваше приложение с образцами документов и посмотреть, если оно уязвимо.
Основной библиотекой для php-библиотек xml является libxml2. Его поведение контролируется из php в основном через необязательные константы, которые большинство библиотек будут принимать в качестве аргумента при загрузке xml.
Вы можете определить версию вашего php libxml2 с помощью echo LIBXML_DOTTED_VERSION;
В более поздних версиях (после 2.6) libxml2 содержит ограничения на замену сущностей, разработанные для предотвращения как экспоненциальных, так и квадратичных атак. Они могут быть отменены с помощью LIBXML_PARSEHUGE
вариант.
По умолчанию libxml2 не загружает dtd, не добавляет атрибуты по умолчанию и не выполняет подстановку сущностей. Поэтому поведение по умолчанию - игнорировать dtds.
Вы можете включить части этого следующим образом:
LIBXML_DTDLOAD
загрузит dtds.LIBXML_NONET
отключит сетевую загрузку dtds. Вы должны всегда иметь это и использовать каталог dtd libxml для загрузки dtds.LIBXML_DTDVALID
выполнит проверку dtd при разборе.LIBXML_NOENT
будет выполнять замену объекта.LIBXML_DTDATTR
добавит атрибуты по умолчанию.
Поэтому, используя настройки по умолчанию, PHP/libxml2, вероятно, не уязвимы ни к одной из этих проблем, но единственный способ узнать наверняка - это проверить.