Что такое SQL-инъекция через EXTRACTVALUE?

SQL-инъекция (SQLi) — один из самых распространённых способов атаки на веб-приложения, при котором злоумышленник внедряет вредоносный SQL-код в запросы к базе данных. Error-based SQL-инъекция — это техника, при которой атакующий заставляет сервер вернуть сообщение об ошибке, содержащее полезные данные (например, имена таблиц, столбцов или пароли). Функция EXTRACTVALUE в MySQL используется для извлечения значения из XML-строки по заданному XPath-выражению. Однако при неправильной обработке входных данных она может стать инструментом для извлечения информации из базы.

Рассматриваемый запрос: "))) AND EXTRACTVALUE(9798,CONCAT(0x7e,((SELECT (ELT(9798=9798,1)))),0x7e))-- - — это классическая полезная нагрузка для error-based SQL-инъекции. Давайте разберём её по частям.

Детальный разбор полезной нагрузки

Начнём с начала:

  • "))) — последовательность закрывающих кавычек и скобок. Она нужна, чтобы «закрыть» ранее открытые в уязвимом запросе конструкции (например, строковые литералы или вызовы функций) и сделать возможным внедрение собственного SQL-кода. Количество и тип символов зависят от контекста исходного запроса.
  • AND — логический оператор, присоединяющий наше условие к оригинальному запросу. Если исходный запрос был истинным, то добавление AND ... не изменит результат, но позволит выполнить внедрённую функцию.
  • EXTRACTVALUE(9798, CONCAT(0x7e, ((SELECT (ELT(9798=9798,1)))), 0x7e)) — это и есть ядро атаки. Функция EXTRACTVALUE принимает два аргумента: XML-документ (в нашем случае число 9798, которое не является валидным XML) и XPath-выражение. Второй аргумент формируется с помощью CONCAT, который объединяет тильду (0x7e — шестнадцатеричное представление символа ~), результат подзапроса и ещё одну тильду.
  • SELECT (ELT(9798=9798,1)) — это подзапрос, возвращающий значение. ELT — функция, возвращающая N-й элемент из списка. Здесь 9798=9798 — всегда истина (1), поэтому ELT(1,1) вернёт первый элемент из списка, то есть 1. Это просто демонстрация: в реальной атаке вместо 1 могло бы быть имя таблицы, столбца или хеш пароля.
  • -- - — комментарий, который «отрезает» оставшуюся часть оригинального SQL-запроса, чтобы не возникло синтаксических ошибок. Пробел после двух дефисов обязателен в MySQL.

Когда MySQL выполняет этот запрос, функция EXTRACTVALUE пытается интерпретировать число 9798 как XML. Это вызывает ошибку, и в сообщении об ошибке MySQL выводит вторую часть XPath-выражения, то есть строку, полученную в результате CONCAT. Таким образом, атакующий видит на экране (или в логах) значение, возвращённое подзапросом, — в нашем примере это ~1~. В реальной атаке вместо 1 подставляется полезная информация, например, версия СУБД, имя текущей базы данных или данные из таблиц.

Как это работает на практике

Предположим, уязвимый запрос выглядит так: SELECT * FROM users WHERE id = '"))) AND EXTRACTVALUE(...)-- -'. После подстановки полезной нагрузки запрос становится синтаксически корректным, но EXTRACTVALUE вызывает ошибку, и MySQL возвращает что-то вроде:

XPATH syntax error: '~1~'

Это сообщение содержит результат выполнения подзапроса. Меняя подзапрос, злоумышленник может извлекать произвольные данные. Например, чтобы получить имя текущей базы данных, можно использовать:

AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT database()), 0x7e))-- -

Тогда в ошибке появится имя базы. Аналогично можно получить версию MySQL (SELECT version()), список таблиц (SELECT table_name FROM information_schema.tables) и так далее.

Однако у EXTRACTVALUE есть ограничение: длина выводимой строки не должна превышать 32 символа. Поэтому для извлечения длинных данных используют посимвольное чтение или другие функции, например, UPDATEXML.

Зачем нужна конструкция ELT(9798=9798,1)?

В данном примере ELT(9798=9798,1) — это просто заглушка, демонстрирующая работу функции ELT. В реальных атаках вместо неё подставляют подзапрос, возвращающий нужные данные. Например, ELT(1, (SELECT user())) вернёт имя текущего пользователя MySQL. Но поскольку EXTRACTVALUE ожидает строку, а ELT возвращает значение, его приводят к строке или оборачивают в CONCAT.

Важно понимать, что вся конструкция CONCAT(0x7e, ((SELECT ...)), 0x7e) нужна для того, чтобы отделить выводимые данные от служебного текста ошибки. Тильды (~) служат маркерами начала и конца полезной нагрузки в сообщении об ошибке.

Методы защиты от error-based SQL-инъекций

Чтобы предотвратить подобные атаки, разработчикам следует придерживаться следующих правил:

  • Использовать подготовленные выражения (prepared statements) с параметризацией. Это самый надёжный способ, так как данные не попадают в SQL-код напрямую.
  • Экранировать специальные символы. Если по каким-то причинам параметризация недоступна, необходимо применять функции экранирования, например, mysqli_real_escape_string.
  • Ограничить права пользователя базы данных. Веб-приложение должно работать с минимально необходимыми привилегиями, чтобы даже при успешной инъекции злоумышленник не смог получить доступ к системным таблицам.
  • Отключить вывод подробных ошибок в production. Сообщения об ошибках не должны показываться пользователям, чтобы не раскрывать структуру базы данных.
  • Использовать WAF (Web Application Firewall). Современные WAF умеют распознавать типовые полезные нагрузки SQL-инъекций, включая EXTRACTVALUE.

Также рекомендуется проводить регулярные аудиты кода и пентесты для выявления уязвимостей.

Заключение

Полезная нагрузка "))) AND EXTRACTVALUE(9798,CONCAT(0x7e,((SELECT (ELT(9798=9798,1)))),0x7e))-- - — это пример error-based SQL-инъекции, эксплуатирующей функцию EXTRACTVALUE в MySQL. Она позволяет атакующему извлекать данные из базы через сообщения об ошибках. Понимание механики таких атак помогает разработчикам и специалистам по безопасности эффективно защищать веб-приложения.

Источники