Главная / Блог / SQL-инъекции в CTF: UNION, blind SQLi и sqlmap

12 мин.00

SQL-инъекции в CTF: UNION, blind SQLi и sqlmap

SQL-инъекции в CTF: UNION, blind SQLi и sqlmap

На прошлой неделе разбирал с командой CTF-таск средней сложности: форма поиска, MySQL за ней, никаких ошибок в ответе, WAF режет слово UNION. Новичок потратил три часа на перебор пейлоадов из гугла — безрезультатно. Я закрыл задачу за 20 минут, и дело не в таланте. Определил СУБД по реакции на %23, классифицировал тип инъекции как boolean-based blind, обошёл фильтр через inline-комментарии, передал в sqlmap с нужным tamper-скриптом. Алгоритм, а не гадание. Здесь — тот самый алгоритм для системной эксплуатации SQL-инъекций в CTF.

Место 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: пользовательский ввод попадает в SQL-запрос без валидации и санитизации. Подробнее — в нашем обзоре создание ctf заданий.

Зачем атакующему всё это? Монетизация. Утечка пользовательских данных (email, пароли, платёжка), захват сервера через чтение системных файлов, выполнение команд ОС. Утечка Exploit.In (2016 год, дата публикации в HIBP: 2016-10-13) — 593 427 119 email-адресов с паролями, по данным Have I Been Pwned. Согласно Verizon DBIR 2025, доля веб-атак среди всех подтверждённых нарушений — 26%.

В CTF цепочка короче, но логика та же: найти точку входа, определить СУБД, выбрать технику, вытащить флаг. Каждый шаг зависит от результата предыдущего — поэтому SQL-инъекции в CTF требуют методологии, а не рандомного копипаста пейлоадов.

Дерево решений: какую технику SQL-инъекции в CTF выбрать

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

Шаг 1 — реакция на спецсимвол. Вставляем одинарную кавычку ' в параметр. Три варианта. Ошибка синтаксиса SQL (текст вроде You have an error in your SQL syntax) — вероятен error-based. Контент страницы изменился, но без ошибки — вероятен boolean-based blind SQLi. Никакой видимой реакции — пробуем ' AND SLEEP(5)-- для MySQL или '; SELECT pg_sleep(5)-- для PostgreSQL: задержка ответа на 5 секунд подтвердит time-based blind SQL инъекцию.

Шаг 2 — определение СУБД. По данным SQL Injection Cheat Sheet от Invicti, разница в синтаксисе СУБД — главная причина, почему универсальные пейлоады не работают. Быстрые проверки: %23 работает как комментарий (URL-encoded символ #) — MySQL/MariaDB. ' UNION SELECT sqlite_version()-- возвращает результат — SQLite. ' AND @@version>0-- проходит без ошибки — вероятно MySQL или MSSQL (проверка ненадёжна: в MySQL 5.7+/8.0 с default strict mode сравнение строки с числом может выбросить ошибку приведения типа — результат стоит проверять эмпирически; для MySQL предпочтительнее %23 или version()). Конкатенация через || работает — PostgreSQL или SQLite.

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

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

Параметр MySQL PostgreSQL SQLite MSSQL
Комментарий строки -- или # -- -- --
Конкатенация строк CONCAT() двойная вертикальная черта двойная вертикальная черта +
Текущая версия @@version version() sqlite_version() @@version
Метаданные таблиц information_schema information_schema sqlite_master information_schema
Задержка SLEEP(N) pg_sleep(N) Нет нативной WAITFOR DELAY
Чтение файлов LOAD_FILE() pg_read_file() Нет OPENROWSET

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

Error-based SQL-инъекция работает, когда приложение отдаёт тексты ошибок базы данных в HTTP-ответе. На CTF это самый быстрый тип — от обнаружения до флага можно дойти за 3-5 запросов.

Техники для MySQL: extractvalue и updatexml

Две рабочие функции — extractvalue() и updatexml(). Обе ожидают корректное XPath-выражение и выбрасывают ошибку, если получают мусор. Пейлоад ' AND extractvalue(1, concat(0x7e, (SELECT version())))-- заставляет MySQL вернуть ошибку вида XPATH syntax error: '~5.7.33' (конкретное значение зависит от версии сервера; техника работает в MySQL 5.x и 8.x) — версия СУБД прямо в тексте ошибки. Символ 0x7e (тильда) нужен, чтобы строка гарантированно не была валидным XPath.

Цепочка эксплуатации SQL-инъекций вручную: сначала подзапрос SELECT table_name FROM information_schema.tables LIMIT 1 — получаем имя первой таблицы в ошибке. Затем SELECT column_name FROM information_schema.columns WHERE table_name='flag' LIMIT 1 — имена столбцов. Наконец, SELECT flag FROM flag — забираем флаг.

А вот тут подвох, на котором регулярно горят новички. extractvalue() и updatexml() возвращают не более ~32 символов (характерно для MySQL 5.x/8.0; точное значение зависит от версии — проверяйте эмпирически), включая служебные символы вроде 0x7e, так что полезных данных помещается ещё меньше. Флаг длиннее 30 символов? Режем через SUBSTRING(): конструкция с substring((SELECT flag FROM flag), 33, 32) внутри extractvalue вытаскивает вторую порцию. Типичная ситуация на CTF — флаг обрезается посередине, выглядит невалидным, и человек начинает искать другую уязвимость. А надо было просто дочитать.

Альтернатива — техника через FLOOR(RAND(0)*2) и GROUP BY, описанная в руководстве pentest-tools.com. Ошибка Duplicate entry '10.1.36-MariaDB#0' содержит данные. Тонкость: RAND(0) с фиксированным seed даёт детерминированную последовательность — без seed=0 техника нестабильна и может сработать через раз.

Техники для PostgreSQL: CAST

Стандартный приём — через CAST(). Как описывает PortSwigger в исследовании blind SQL injection, попытка привести строку к integer заставляет СУБД вернуть содержимое строки в тексте ошибки. Пейлоад ' AND 1=CAST((SELECT table_name FROM information_schema.tables LIMIT 1) AS int)-- вернёт ERROR: invalid input syntax for type integer: "users". Этот же приём выручает, когда ограничение на длину ввода не позволяет использовать conditional responses.

Техника мертва, если приложение перехватывает исключения и показывает generic-ответ (HTTP 500 без деталей). Нет ошибок — переключаемся на UNION или blind.

UNION-based SQL инъекция: полная цепочка эксплуатации

UNION-based — самый распространённый тип SQL-инъекций в CTF. Оператор 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) запросов — при 10 столбцах всего 4 запроса.

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

Извлечение данных: три шага до флага

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

Полная цепочка для MySQL (три столбца, второй отображается):

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

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

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

Boolean-based blind SQLi на практике

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

Определение индикатора TRUE/FALSE

Первым делом находим, чем отличается ответ при TRUE от FALSE. Классический пример из PortSwigger Web Academy — cookie TrackingId: пейлоад xyz' AND '1'='1 — страница содержит «Welcome back» (TRUE); xyz' AND '1'='2 — текст отсутствует (FALSE). Индикатором может быть что угодно: длина ответа, HTTP-статус, наличие конкретного HTML-элемента. Я обычно начинаю с diff длины ответа в байтах — самый надёжный маркер.

Бинарный поиск по символам

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

Вручную это терпимо для 8-символьного пароля (около 50 запросов), но для длинного флага — неразумная трата времени. На CTF делаю 2-3 запроса руками для подтверждения инъекции и понимания формата ответа, дальше передаю в автоматизацию.

Conditional errors через CASE WHEN

Более мощная техника boolean-based blind SQLi — для случаев, когда контент страницы не меняется вообще, но приложение по-разному обрабатывает ошибки базы данных. Как описывает PortSwigger, пейлоад xyz' AND (SELECT CASE WHEN (1=1) THEN 1/0 ELSE 'a' END)='a вызывает деление на ноль при TRUE (HTTP 500), при FALSE — нормальный ответ.

Применяем к данным: подставляем в CASE WHEN условие вида Username='Administrator' AND SUBSTRING(Password,1,1)>'m' — и используем тот же бинарный поиск, но индикатор уже не контент, а HTTP-статус.

Time-based blind SQL инъекция: когда нет вообще никакой обратной связи

Time-based blind — крайний случай. Приложение возвращает одинаковый ответ независимо от запроса. Единственный канал утечки — время ответа сервера. Медленно, больно, но работает.

Для MySQL: ' IF(SUBSTRING((SELECT flag FROM flag),1,1)='s', SLEEP(5), null)--. Если первый символ флага равен 's' — сервер ответит через 5 секунд, иначе мгновенно. Для PostgreSQL: '; SELECT CASE WHEN (SUBSTRING(password,1,1)='a') THEN pg_sleep(5) ELSE pg_sleep(0) END FROM users--. Для MSSQL: '; IF (SUBSTRING((SELECT TOP 1 flag FROM flag),1,1)='s') WAITFOR DELAY '0:0:5'--.

По данным OWASP, альтернатива SLEEP() в MySQL — функция BENCHMARK(): BENCHMARK(5000000, ENCODE('MSG','by 5 seconds')) выполняет операцию 5 миллионов раз, создавая задержку, зависящую от CPU сервера. Число итераций нужно калибровать под конкретную цель — в отличие от детерминированного SLEEP(N). Полезно, когда SLEEP() заблокирован WAF-ом.

Ручной перебор time-based blind SQL инъекции непрактичен для любого объёма данных. На CTF разумная тактика: подтвердить инъекцию парой ручных запросов, зафиксировать точный синтаксис и точку инъекции, затем передать работу sqlmap.

sqlmap для CTF: от ручного POC к автоматизации SQLi

Переход от ручной эксплуатации к автоматизации SQLi sqlmap — навык, без которого на CTF далеко не уедешь. Ручной POC подтверждает точку инъекции и определяет СУБД. Дальше sqlmap делает рутину быстрее и надёжнее.

Пошаговый workflow

Сохраняем HTTP-запрос из Burp Suite в файл (ПКМ → Copy to file). Если точка инъекции неочевидна — помечаем звёздочкой в файле. Как описывает Vaadata, sqlmap при пентесте запускают после ручной идентификации инъекции, чтобы не генерировать лишний трафик. На CTF это ещё важнее: rate-limiting может заблокировать IP после шквала автоматических запросов.

# Определить тип инъекции и СУБД
sqlmap -r req.txt --batch --level=3 --risk=2
# Получить список баз данных
sqlmap -r req.txt --dbs
# Таблицы целевой БД
sqlmap -r req.txt -D target_db --tables
# Дамп флага
sqlmap -r req.txt -D target_db -T flag --dump

Опция --batch автоматически отвечает на вопросы дефолтами — экономит время. --level=3 расширяет проверку на cookie, User-Agent, Referer. --risk=2 включает OR-based пейлоады — на проде опасны, на CTF допустимы. Для задач с JSON body или нестандартными cookie удобнее сохранять запрос через Burp и использовать -r, чем собирать URL руками с --cookie и --data.

Ключевые флаги для CTF

Опция -p parameter указывает конкретный параметр — пропускает сканирование остальных и ускоряет процесс. --technique=U ограничивает sqlmap для CTF только UNION-based техникой (аналогично B для boolean, T для time-based) — сокращает время в разы, если тип инъекции уже известен из ручного POC. --string="Welcome" задаёт текстовый маркер TRUE-ответа для blind-сценариев. --prefix и --suffix задают обвязку пейлоада для нестандартных SQL-конструкций, когда инъекция сидит внутри вложенного запроса или функции.

Когда sqlmap не справляется

sqlmap — не серебряная пуля. На CTF встречаются задачи с WebSocket-протоколом, кастомным форматом ответа (JSON с вложенной логикой), rate-limiting с капчей или WAF, который банит по сигнатурам sqlmap (User-Agent sqlmap/1.x). В таких случаях — пишем свой скрипт. Для boolean-based blind: отправляем запрос с условием SUBSTRING(...)>'X', парсим ответ на маркер, двигаемся бинарным поиском. Для time-based — замеряем время ответа. Порог задержки лучше определять динамически: сначала замеряем baseline без инъекции, потом ищем отклонение на 4+ секунды. На нестабильных CTF-серверах фиксированный порог часто даёт ложные срабатывания.

Обход WAF SQL инъекция: техники для CTF-задач

На продвинутых CTF-задачах сырые пейлоады не пройдут — стоит фильтрация ключевых слов или символов. Вот что работает.

Смена регистра. Фильтр блокирует SELECT — пробуем SeLeCt. Базы данных нечувствительны к регистру ключевых слов, а простые regex-фильтры проверяют точное совпадение. По данным Invicti SQL Injection Cheat Sheet, это один из базовых способов обхода фильтрации.

Inline-комментарии. UN/**/ION SE/**/LECT — парсер SQL игнорирует комментарии, regex-фильтр не видит целого слова. В MySQL специальный синтаксис /*!50000 UNION*/ выполняет конструкцию только на MySQL 5.0+ — одновременно обход фильтра и fingerprinting СУБД. Красивый трюк.

Hex-кодирование строк. Если фильтруются кавычки — передаём значения в hex: WHERE table_name=0x666C6167 вместо WHERE table_name='flag'. Работает в MySQL. Для PostgreSQL и MSSQL — аналог через CHR(): CHR(102)||CHR(108)||CHR(97)||CHR(103).

Замена пробелов. При блокировке пробелов — /**/, %09 (tab), %0a (newline) или скобки: UNION(SELECT(flag)FROM(flag)).

Double URL encoding. Если WAF декодирует URL однократно, а приложение дважды — %2527 после первого декодирования станет %27, после второго — одинарная кавычка.

Tamper-скрипты sqlmap для обхода фильтров

Автоматизация обхода WAF SQL инъекции через sqlmap: sqlmap -r req.txt --tamper=space2comment,randomcase. Популярные tamper-скрипты: space2comment — пробелы в /**/, randomcase — случайный регистр, between — замена > на BETWEEN, charencode — URL-кодирование пейлоада, equaltolike — замена = на LIKE. Комбинация нескольких скриптов через запятую часто решает задачи, где один приём не проходит. Если стандартных скриптов не хватает — sqlmap поддерживает кастомные: Python-файл с функцией tamper(payload), модифицирующей каждый пейлоад перед отправкой.

Вижу одну и ту же картину у начинающих CTF-игроков: учат SQL-инъекции по чит-шитам, копируют пейлоады, запускают sqlmap на всё подряд. Когда стандартный пейлоад не срабатывает — зависают. Проблема в том, что sqlmap создаёт ложное чувство компетенции. Человек «решает» задачу, но не понимает, почему extractvalue возвращает только 32 символа, почему ORDER BY 4 падает, а ORDER BY 3 нет, почему boolean-based blind требует бинарного поиска, а не линейного перебора.

Blind SQLi на практике — это не про SQL. Это про умение программировать: написать скрипт, который измеряет время ответа или парсит разницу в контенте, отладить его на граничных случаях, обработать нестабильность сети. Кто не может написать цикл с условием и HTTP-запросом — застрянет на blind-задачах навсегда. Никакой чит-шит эту проблему не решит. Каждую задачу стоит сначала решать вручную, пока не поймёте механику, и только потом ускорять рутину через sqlmap. На WAPT эту цепочку проходят в течение двух модулей с лабами — от ручного построения пейлоада до автоматизации через sqlmap с кастомными tamper-скриптами.

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

Поделиться

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

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

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

Читайте также

pwndbg для начинающих: отладка и pwn в CTF

14 мин.

5

pwndbg для начинающих: отладка и pwn в CTF

Пошаговый гайд по pwndbg: установка, команды checksec/vmmap/cyclic, эксплуатация buffer overflow и связка с pwntools для решения pwn-тасков CTF.

20 ОКТЯБРЬ, 2026

Insecure Deserialization в CTF: эксплуатация

13 мин.

5

Insecure Deserialization в CTF: эксплуатация

Разбираем PHP unserialize и Python pickle по байтам — POP chain, PHAR, jsonpickle, CVE-2020-22083. Пошаговая методология эксплуатации десериализации для CTF.

20 ОКТЯБРЬ, 2026

Декодирование кодировок CTF: алгоритм и инструменты

13 мин.

8

Декодирование кодировок CTF: алгоритм и инструменты

Пошаговый алгоритм распознавания Base64, Hex, Base32 и других кодировок в CTF-тасках. Таблица визуальных признаков, CyberChef-рецепты и Python-скрипт автоматизации.

19 ОКТЯБРЬ, 2026