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

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

Главная цель такой инъекции — заставить сервер базы данных вернуть в ответе на запрос ту информацию, которую атакующий хочет получить: версию MySQL, имя текущей базы данных, имена таблиц, логины и пароли пользователей. Поскольку напрямую вывести эти данные в HTML-ответ часто невозможно, злоумышленники прибегают к трюкам с генерацией ошибок, которые затем отображаются на странице.

Разбор структуры вредоносного запроса

Давайте разберём строку по частям, чтобы понять логику атакующего.

  • " — закрывающая кавычка. Она нужна, чтобы «разорвать» исходный SQL-запрос и добавить свой код. В уязвимом приложении строка могла выглядеть так: SELECT * FROM products WHERE name = "$input". Подставив кавычку, атакующий закрывает строковый литерал и получает возможность дописать собственные инструкции.
  • AND — логический оператор. Он присоединяет условие к оригинальному запросу, чтобы тот оставался синтаксически корректным.
  • EXTRACTVALUE(3798, CONCAT(...)) — вызов функции MySQL. EXTRACTVALUE(XML_fragment, XPath_expression) извлекает значение из XML-фрагмента по заданному XPath-выражению. Если XPath-выражение некорректно, MySQL генерирует ошибку и включает в текст ошибки переданное значение. Именно это и эксплуатируется.
  • CONCAT(0x7e, ((SELECT (ELT(3798=3798,1)))), 0x7e) — конкатенация. 0x7e — это шестнадцатеричное представление символа ~ (тильда). Тильда используется как маркер, чтобы в сообщении об ошибке легко было найти начало и конец выводимых данных. Внутри — подзапрос SELECT (ELT(3798=3798,1)).
  • ELT(N, str1, str2, ...) — функция, возвращающая N-й элемент из списка строк. Выражение 3798=3798 всегда истинно (равно 1), поэтому ELT(1, 1) вернёт строку '1'. В реальной атаке вместо 1 подставляется подзапрос, извлекающий нужные данные, например: ELT(1, (SELECT password FROM users LIMIT 1)).
  • -- - — комментарий. Два дефиса с пробелом (или дефис-дефис-пробел) в MySQL означают начало комментария до конца строки. Это позволяет «отрезать» оставшуюся часть оригинального запроса, чтобы он не вызвал синтаксическую ошибку.

В результате MySQL пытается выполнить EXTRACTVALUE(3798, '~1~'). Поскольку '~1~' не является корректным XPath-выражением, СУБД возвращает ошибку вида: XPATH syntax error: '~1~'. Атакующий видит эту ошибку на странице и понимает, что инъекция работает. Далее он заменяет 1 на подзапросы, чтобы извлечь реальные данные.

Почему EXTRACTVALUE, а не UNION?

Метод UNION SELECT тоже популярен, но у него есть ограничения. Чтобы он сработал, нужно точно знать количество столбцов в оригинальном запросе и их типы. Кроме того, не всегда вывод UNION-запроса отображается на странице. Error-based инъекция через EXTRACTVALUE() не требует знания структуры SELECT и работает даже тогда, когда результаты обычного запроса не выводятся. Достаточно, чтобы приложение показывало сообщения об ошибках базы данных (что само по себе является плохой практикой).

Важно: начиная с MySQL 5.7 функция EXTRACTVALUE() была объявлена устаревшей, а в MySQL 8.0 — удалена. Однако многие сайты до сих пор работают на старых версиях, поэтому угроза остаётся актуальной.

Какие данные можно извлечь?

С помощью этой техники атакующий может получить практически любую информацию из базы:

  • версию MySQL (SELECT version());
  • имя текущей базы данных (SELECT database());
  • список таблиц (SELECT table_name FROM information_schema.tables);
  • имена столбцов;
  • учётные данные пользователей (логины, хеши паролей);
  • персональные данные клиентов.

Поскольку длина вывода в сообщении об ошибке ограничена (обычно около 32 символов), злоумышленники используют функции SUBSTRING() или MID(), чтобы извлекать данные по частям.

Как защититься от SQL-инъекций

Основной способ защиты — использовать параметризованные запросы (prepared statements). В этом случае данные пользователя передаются отдельно от SQL-кода и не могут изменить структуру запроса. Дополнительные меры:

  1. Никогда не выводить сообщения об ошибках базы данных конечному пользователю — только в лог-файлы.
  2. Использовать ORM и проверенные библиотеки для работы с БД.
  3. Ограничить права пользователя базы данных: приложению не нужны права на чтение information_schema или удаление таблиц.
  4. Регулярно обновлять СУБД и фреймворки.
  5. Применять WAF (Web Application Firewall) для блокировки подозрительных запросов.

Если вы встретили подобную строку в логах своего сайта — это сигнал о попытке взлома. Срочно проверьте код на наличие уязвимостей и закройте доступ к сообщениям об ошибках.

Источники