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

SQL-инъекция — одна из самых опасных уязвимостей веб-приложений, входящая в топ OWASP. Злоумышленник внедряет вредоносный SQL-код в запрос, чтобы получить доступ к базе данных. Одним из методов является error-based SQL-инъекция, при которой атакующий заставляет сервер выдать сообщение об ошибке, содержащее конфиденциальные данные. Функция EXTRACTVALUE в MySQL часто используется для таких атак.

Рассматриваемый запрос: ` OR EXTRACTVALUE(8362,CONCAT(0x7e,((SELECT (ELT(8362=8362,1)))),0x7e))-- - — типичный пример error-based инъекции. В этой статье мы детально разберём, как он работает, какие данные позволяет извлечь и как защититься от подобных атак.

Как работает функция EXTRACTVALUE в MySQL

EXTRACTVALUE(XML_frag, XPath_expr) — функция MySQL, которая извлекает значение из XML-фрагмента по заданному XPath-выражению. Если XPath-выражение некорректно, MySQL генерирует ошибку с указанием проблемного фрагмента. Именно это свойство и эксплуатируют злоумышленники: они передают в качестве XPath строку, содержащую результат подзапроса, и сервер возвращает её в тексте ошибки.

Например, запрос SELECT EXTRACTVALUE(1, CONCAT(0x7e, (SELECT user()), 0x7e)); вернёт ошибку вида XPATH syntax error: '~root@localhost~'. Так злоумышленник узнаёт имя пользователя базы данных.

Разбор конкретного запроса

Запрос ` OR EXTRACTVALUE(8362,CONCAT(0x7e,((SELECT (ELT(8362=8362,1)))),0x7e))-- - состоит из нескольких частей:

  • ` — закрывающая кавычка, которая завершает строковый литерал в уязвимом параметре.
  • OR — логический оператор, делающий условие всегда истинным (если инъекция сработала).
  • EXTRACTVALUE(8362, ...) — вызов функции, где первый аргумент — произвольное число (8362), а второй — XPath-выражение, которое вызовет ошибку.
  • CONCAT(0x7e, ((SELECT (ELT(8362=8362,1)))), 0x7e) — конкатенация символа ~ (0x7e), результата подзапроса и ещё одного ~. Символы ~ используются как маркеры, чтобы легко найти данные в сообщении об ошибке.
  • SELECT (ELT(8362=8362,1)) — подзапрос, возвращающий 1. Здесь ELT(N, str1, str2, ...) возвращает N-ю строку из списка. Условие 8362=8362 всегда истинно, поэтому ELT(1,1) вернёт 1. В реальной атаке вместо этого подзапроса внедряют запрос к системным таблицам, например SELECT version() или SELECT table_name FROM information_schema.tables LIMIT 1.
  • -- - — комментарий, который игнорирует остаток оригинального SQL-запроса.

Таким образом, при выполнении уязвимого запроса СУБД попытается вычислить EXTRACTVALUE(8362, '~1~'), что приведёт к ошибке XPATH syntax error: '~1~'. Если вместо 1 подставить, например, (SELECT database()), в ошибке отобразится имя текущей базы данных.

Пример эксплуатации

Предположим, есть уязвимый параметр id в URL: http://example.com/product.php?id=1. Оригинальный запрос: SELECT * FROM products WHERE id = '$id'. Злоумышленник подставляет:

1' OR EXTRACTVALUE(1, CONCAT(0x7e, (SELECT version()), 0x7e))-- -

Сервер выполнит: SELECT * FROM products WHERE id = '1' OR EXTRACTVALUE(1, CONCAT(0x7e, (SELECT version()), 0x7e))-- -'. Возникнет ошибка, в тексте которой будет версия MySQL, например XPATH syntax error: '~8.0.23~'. Так атакующий получает информацию о СУБД.

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

1' OR EXTRACTVALUE(1, CONCAT(0x7e, (SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1), 0x7e))-- -

Почему EXTRACTVALUE уязвима?

Функция EXTRACTVALUE не является уязвимостью сама по себе — это штатная функция MySQL. Проблема в том, что при неправильной обработке пользовательского ввода злоумышленник может внедрить вызов этой функции в SQL-запрос. Если приложение не использует подготовленные выражения (prepared statements) и не экранирует спецсимволы, инъекция становится возможной.

Стоит отметить, что в MySQL 5.7 и более поздних версиях EXTRACTVALUE и UPDATEXML всё ещё доступны, но в некоторых сборках могут быть отключены. Однако error-based инъекции возможны и через другие функции, например GTID_SUBSET, JSON_KEYS и т.д.

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

Основные меры защиты:

  1. Используйте подготовленные выражения (prepared statements). Это самый надёжный способ: параметры запроса передаются отдельно от SQL-кода, и внедрение становится невозможным.
  2. Применяйте хранимые процедуры с параметрами, если это возможно.
  3. Экранируйте все пользовательские данные с помощью функций вроде mysqli_real_escape_string (для MySQL). Но помните, что экранирование — не панацея, особенно при неправильной кодировке.
  4. Ограничьте права пользователя БД. Веб-приложение должно работать под учётной записью с минимально необходимыми правами (только SELECT, INSERT, UPDATE, DELETE для нужных таблиц). Это не позволит атакующему читать системные таблицы.
  5. Отключите вывод ошибок СУБД на продакшене. Сообщения об ошибках не должны попадать в ответы сервера — это затруднит error-based инъекции.
  6. Используйте WAF (Web Application Firewall). Он может блокировать подозрительные запросы, содержащие EXTRACTVALUE и другие опасные конструкции.
  7. Регулярно обновляйте СУБД и фреймворки. Хотя EXTRACTVALUE не является багом, обновления закрывают другие уязвимости.

Заключение

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

Источники