Что такое ExtractValue и при чём тут SQL-инъекция?

Функция ExtractValue в MySQL предназначена для извлечения значения из XML-документа по заданному XPath-выражению. Синтаксис прост: ExtractValue(xml_frag, xpath_expr). Однако при некорректном использовании она может стать мощным инструментом для проведения SQL-инъекций, основанных на ошибках (error-based SQL injection). Злоумышленники эксплуатируют сообщения об ошибках, которые генерирует СУБД, чтобы получить фрагменты данных из базы.

В запросе, который вы ищете, используется конструкция: %")) AND EXTRACTVALUE(9347,CONCAT(0x7e,((SELECT (ELT(9347=9347,1)))),0x7e))-- -. Давайте разберём её по частям.

Анатомия вредоносного запроса

1. Начало: %"))

Символы %")) — это попытка закрыть ранее открытые кавычки, скобки и, возможно, часть исходного SQL-запроса. Например, если уязвимый код формирует запрос вида SELECT * FROM users WHERE name = '%...', то вставка %")) может нарушить синтаксис и позволить внедрить собственные конструкции.

2. Ключевое слово AND

Оператор AND используется для добавления условия. Если исходный запрос возвращает true, то добавление AND с функцией, вызывающей ошибку, заставит СУБД выдать сообщение об ошибке, которое будет содержать результат подзапроса.

3. Функция EXTRACTVALUE(9347, CONCAT(...))

Первый аргумент — 9347 — это не XML, а просто число, которое вызовет ошибку, так как ExtractValue ожидает корректный XML-фрагмент. Второй аргумент — CONCAT(0x7e, ((SELECT (ELT(9347=9347,1)))), 0x7e) — формирует XPath-выражение, которое также будет некорректным. В результате MySQL сгенерирует ошибку вида: XPATH syntax error: '~1~'. В этом сообщении и будет содержаться результат подзапроса.

4. Подзапрос (SELECT (ELT(9347=9347,1)))

Функция ELT(N, str1, str2, ...) возвращает N-ю строку из списка. Здесь 9347=9347 — это условие, которое всегда истинно (1), поэтому ELT(1, 1) вернёт 1. В реальной атаке вместо этого подзапроса злоумышленник может вставить извлечение данных, например, (SELECT version()) или (SELECT password FROM users LIMIT 1).

5. Завершение: -- -

Два дефиса и пробел (или -- -) — это комментарий в SQL, который отсекает оставшуюся часть исходного запроса, чтобы не нарушать синтаксис.

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

Предположим, уязвимый запрос выглядит так:

SELECT * FROM articles WHERE id = '%")) AND EXTRACTVALUE(9347,CONCAT(0x7e,((SELECT (ELT(9347=9347,1)))),0x7e))-- -'

После подстановки пользовательского ввода запрос станет:

SELECT * FROM articles WHERE id = '%")) AND EXTRACTVALUE(9347,CONCAT(0x7e,((SELECT (ELT(9347=9347,1)))),0x7e))-- -'

MySQL попытается выполнить EXTRACTVALUE с некорректными аргументами и выдаст ошибку, в тексте которой будет значение, возвращённое подзапросом. Например:

ERROR 1105 (HY000): XPATH syntax error: '~1~'

Если вместо ELT(9347=9347,1) подставить (SELECT database()), то в ошибке появится имя текущей базы данных. Так злоумышленник может последовательно извлекать любые данные: имена таблиц, столбцов, логины, пароли и т.д.

Почему это опасно?

SQL-инъекции через ExtractValue относятся к классу error-based SQLi. Их особенность в том, что они не требуют слепого перебора (blind SQLi) и позволяют быстро получать информацию, просто анализируя сообщения об ошибках. Если приложение выводит ошибки СУБД пользователю (что часто случается на этапе разработки или при неверной конфигурации), злоумышленник может автоматизировать процесс с помощью инструментов вроде sqlmap.

По данным OWASP, SQL-инъекции остаются в топ-10 самых критичных уязвимостей веб-приложений. ExtractValue — лишь один из многих векторов, но он особенно удобен для извлечения данных из MySQL версий 5.1 и выше.

Примеры реальных атак

Вот несколько вариантов полезной нагрузки, которые могут использоваться после успешного определения точки инъекции:

  • AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT version()), 0x7e)) — получение версии MySQL.
  • AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT user()), 0x7e)) — получение имени текущего пользователя БД.
  • AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT table_name FROM information_schema.tables LIMIT 1), 0x7e)) — извлечение имени первой таблицы.

Во всех случаях в ошибке будет фигурировать результат подзапроса, обрамлённый тильдами (~), которые добавляются через 0x7e для удобства парсинга.

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

Основной метод защиты — параметризованные запросы (prepared statements). Они исключают возможность внедрения SQL-кода, так как данные передаются отдельно от структуры запроса. Если вы используете PHP, применяйте PDO или mysqli с подготовленными выражениями. В Python — psycopg2 или SQLAlchemy. В Java — PreparedStatement.

Дополнительные меры:

  1. Отключите вывод ошибок СУБД в продакшене. Сообщения об ошибках не должны попадать в ответы API или на веб-страницы.
  2. Используйте ORM (например, Hibernate, Entity Framework), которые по умолчанию экранируют опасные символы.
  3. Проводите регулярный аудит кода и сканирование уязвимостей с помощью инструментов (sqlmap, OWASP ZAP).
  4. Ограничьте права пользователя БД: не давайте приложению доступ к information_schema без необходимости.
  5. Включите WAF (Web Application Firewall), который может блокировать подозрительные конструкции вроде EXTRACTVALUE.

Заключение

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

Источники