
На прошлогоднем HackTheBox CTF я убил сорок минут на ручной перебор символов в blind SQLi — посимвольно, через Burp Repeater. Каждый запрос менял один символ в SUBSTRING(), ждал ответа, записывал результат в блокнот. Как на бумажке в столбик считать, когда рядом лежит калькулятор. Потом перенастроил sqlmap с правильными флагами — --string, --technique=B, -r от сохранённого запроса — и получил полный дамп таблицы за три минуты. С тех пор правило простое: руками находишь и подтверждаешь инъекцию, рутину извлечения отдаёшь автоматике. Весь этот путь — от первой кавычки в поле ввода до готового дампа базы — разберу ниже.
SQL-инъекции в CTF прямо соответствуют третьей строке OWASP Top 10 (A03:2021 — Injection) и стабильно занимают первое место по частоте появления в web-категории соревнований. Но если три года назад хватало ' OR 1=1 --, то сейчас авторы тасков строят задания вокруг нестандартных точек инъекции и многоуровневых фильтров. Халява кончилась. Подробнее — в нашем статье о создание ctf заданий.
Sqlmap классифицирует технику инъекции по шести типам через флаг --technique (BEUSTQ). В CTF чаще всего встречаются четыре:
UNION-based — классика. Приложение выводит результат SQL-запроса на страницу, и ты «подклеиваешь» свой SELECT через UNION ALL SELECT. Данные читаются в открытом виде. Работает, когда вывод попадает в HTML-ответ напрямую — через цикл обработки строк или когда отображается только первая запись (partial UNION injection).
Error-based — приложение выбрасывает ошибки СУБД в ответ. Через специально сформированные выражения (ExtractValue() или UpdateXML() в MySQL) целевые данные попадают прямо в текст ошибки. Самый быстрый путь к данным после UNION.
Boolean-based blind — вывода нет, ошибок нет, но приложение по-разному реагирует на true/false условия. Страница показывает «Welcome» при истинном условии и пустоту при ложном. Извлечение данных — посимвольно, через бинарный поиск по ASCII-кодам. Арифметика простая: бинарный поиск по 95 печатным ASCII-символам — это ⌈log₂(95)⌉ ≈ 7 сравнений, то есть примерно семь HTTP-запросов на один символ.
Time-based blind SQL injection — самый медленный тип. Ни вывода, ни разницы в ответе — единственный канал это время. IF(condition, SLEEP(5), 0) — если условие истинно, сервер молчит пять секунд. Один символ — те же ~7 запросов бисекции, и каждый запрос «стоит» секунд задержки. Тут терпение нужно монашеское.
Ещё два типа — stacked queries (добавление нового запроса через ;) и inline queries (вложенные подзапросы) — попадаются реже, но именно они создают самые злые задачи в CTF категории hard.
Типичная ошибка — фокусироваться только на GET-параметрах в URL. В CTF инъекция прячется где угодно: в cookie, заголовках (X-Forwarded-For, Referer, User-Agent), POST-данных, JSON-полях API. На практике sqlmap с дефолтным --level=1 не тестирует cookie, поэтому blind SQLi в cookie-параметрах остаётся незамеченной — даже если уязвимость подтверждается вручную за минуту. Сколько раз видел это на CTF с нетипичными точками инъекции — не сосчитать.
Первые 60 секунд на любом web-таске: открываю страницу, Ctrl+U для исходного кода, DevTools для заголовков ответа. Заголовок X-Powered-By часто выдаёт стек (PHP, Flask, Express). Если дан исходный код — сразу ищу конкатенацию переменных в SQL-запросах: "SELECT ... WHERE id = '""'". Конкатенация вместо параметризованных запросов — корневая причина инъекций в базы данных, и в CTF авторы это обожают.
Параллельно проверяю скрытые эндпоинты: ffuf -u http://target/FUZZ -w common.txt с проверкой .git/HEAD, /backup, /admin. Утечка исходного кода на CTF — обычное дело, и она решает задачу быстрее любого фаззинга.
Первый шаг — вставить одинарную кавычку ' в подозрительный параметр и посмотреть, что произойдёт:
' AND SLEEP(5)-- для time-basedПосле подтверждения типа инъекции определяю количество колонок. Метод ORDER BY N — увеличиваю N от 1, пока не получу ошибку. Если ORDER BY 3 работает, а ORDER BY 4 ломает запрос — в исходном SELECT три колонки. Альтернатива — UNION SELECT NULL,NULL,... с подбором количества NULL (NULL совместим с большинством типов данных, в отличие от числовых литералов — меньше шансов нарваться на type mismatch).
UNION-based — самый благодарный тип для CTF. Результат виден сразу, не нужно ждать задержек или перебирать символы. Алгоритм, который у меня отработан до автоматизма:
Шаг 1: определение числа колонок. ' ORDER BY 1--, ' ORDER BY 2--, ..., пока не сломается. Допустим, 3 колонки.
Шаг 2: определение отображаемых позиций. ' UNION SELECT 1,2,3-- — смотрю, какая цифра появилась на странице. Если отображается «2» — вторая позиция и есть «окно» для вывода данных.
Шаг 3: разведка структуры БД. Вместо цифры 2 подставляю table_name из information_schema.tables. Конструкция ' UNION SELECT 1,table_name,3 FROM information_schema.tables-- покажет имена таблиц. Затем column_name из information_schema.columns с фильтром по нужной таблице.
Шаг 4: извлечение данных. Когда знаю таблицу и колонки — ' UNION SELECT 1,password,3 FROM users--. Флаг обычно лежит в отдельной таблице с «говорящим» именем (flag, secret, s3cr3t_t4bl3).
Типичная ошибка — пытаться сразу читать users. В CTF структура базы произвольная, и без information_schema угадываешь имена вслепую. Бывает и хитрее: в некоторых CTF-машинах стандартные SQL injection payloads не работают, потому что идентификаторы (имена таблиц/колонок) интерполируются через backtick-quoting, а пользовательские данные идут через prepared statements. Тут без анализа исходников не разобраться — надо понять, какие части запроса параметризованы, а какие конкатенируются вручную.
Отдельная история — SQLite. В CTF эта СУБД встречается непропорционально часто: Flask + SQLite — стандартный стек для простых web-тасков. У SQLite нет information_schema — вместо него SELECT name FROM sqlite_master WHERE type='table'. Sqlmap при --dbms=SQLite переключается автоматически, но при ручной эксплуатации новички регулярно ломаются на этом моменте. Каждый второй вопрос на форумах — «почему information_schema не работает?!».
Приложение не показывает данные из запроса, но по-разному реагирует на истинные и ложные условия. Классический индикатор: слово «Welcome» при true и его отсутствие при false.
Ручной алгоритм: формирую условие ' AND SUBSTRING(password,1,1)='a'-- и перебираю символы. Бинарный поиск ускоряет процесс: вместо перебора всех 95 печатных символов ASCII сравниваю с серединой диапазона (' AND ASCII(SUBSTRING(password,1,1))>64--), сужая область до нужного символа за 7 запросов.
Руками это мучительно медленно — один символ за 30-40 секунд, строка из 32 символов — почти 20 минут чистой работы. Но первые 2-3 символа стоит вытянуть вручную: это подтверждает формат данных и валидность payload'а перед запуском автоматики. Без этого шага потом полчаса будешь гадать, почему sqlmap выдаёт мусор.
Когда ни вывода, ни разницы в ответе — остаётся время. Payload ' AND IF(SUBSTRING(password,1,1)='a', SLEEP(5), 0)-- заставляет сервер задуматься на 5 секунд при угаданном символе. Самый медленный путь: один символ — 35-50 секунд с учётом задержек сети.
В CTF time-based встречается в двух сценариях: когда автор задания хочет усложнить жизнь (medium/hard таски) и когда приложение использует INSERT/UPDATE запросы — UNION тут невозможен по определению.
Флаг sqlmap --time-sec задаёт базовую задержку (по умолчанию 5 секунд). На нестабильных сетях с высоким джиттером увеличивайте --time-sec, чтобы снизить количество ложных срабатываний. На одном CTF с VPN через полмира я ставил --time-sec=10 — иначе sqlmap путал сетевые задержки с реальными SLEEP'ами.
Error-based — золотая середина между скоростью UNION и ограниченностью blind. Приложение не выводит результат запроса, но показывает ошибки СУБД. Суть: впихнуть целевые данные в текст ошибки.
Для MySQL классический payload — ' AND ExtractValue(1, CONCAT(0x7e, (SELECT version())))--. Сервер вернёт ошибку вида XPATH syntax error: '~5.7.34', где 5.7.34 — версия СУБД. Аналогично работает UpdateXML(). Для PostgreSQL — CAST() с заведомо невалидным типом.
В CTF error-based иногда маскируется: приложение ловит исключения и возвращает generic «Something went wrong», но при определённых типах ошибок текст слегка различается. Тут помогает sqlmap с опцией --string или --regexp для точного различения ответов. Глазами разницу можешь и не заметить, а sqlmap с правильным маркером — заметит.
Чёткий критерий: sqlmap подключается после того, как я вручную подтвердил три вещи — наличие инъекции, её тип и конкретную точку в запросе. Без этого sqlmap будет слепо перебирать сотни payload'ов, генерировать тонны трафика и, вероятно, ничего не найдёт — зато IP забанят.
Как отмечает автор SQLMap Cheat Sheet на highon.coffee: «I personally use SQLMap as an exploitation tool, due to the large amount of resources and traffic the tool uses I personally find that detection is better done manually or using other detection tools such as Burp Suite scanner». Полностью подтверждаю из собственного опыта на десятках CTF-задач.
Самый надёжный способ передать sqlmap контекст запроса — сохранить HTTP-запрос из Burp Suite в файл и запустить через -r. Это сохраняет все заголовки, cookie и POST-данные. Если точка инъекции нестандартная (cookie, заголовок), в файле помечаю её символом *:
GET /search?q=test HTTP/1.1
Host: ctf.example.com
Cookie: session=abc123; trackingId=xyz*
Команда: sqlmap -r request.txt --dbms=MySQL --technique=B --string="Welcome". Сразу передаю тип СУБД, технику и строку-маркер истинного ответа. Количество тестовых запросов сокращается на порядок. По документации sqlmap: «the more information you can give SQLMap the faster and less requests the tool will make». Логично — чем меньше гадать, тем быстрее результат.
Флаги --level (1-5) и --risk (1-3) определяют количество и агрессивность тестовых payload'ов. На --level=1 (по умолчанию) тестируются только GET и POST параметры. --level=2 добавляет cookie, --level=3 — User-Agent и Referer, --level=5 — все заголовки. Для CTF-тасков с инъекцией в cookie обязателен минимум --level=2, иначе sqlmap просто не проверит нужный параметр. Я на этом обжигался не раз.
--risk=1 использует безопасные payload'ы. --risk=3 добавляет OR-based конструкции и UPDATE-выражения — в реальном пентесте это может модифицировать данные, но на CTF допустимо.
Флаг --technique принимает комбинацию букв: B (boolean), E (error), U (union), S (stacked), T (time), Q (inline). По умолчанию включены все шесть (BEUSTQ), хотя фактический набор протестированных техник зависит ещё и от --level/--risk. Если тип инъекции уже известен — указываю конкретную букву: --technique=U для UNION. Убирает лишние проверки и экономит время.
Когда SQL-запрос на сервере обёрнут в нестандартную конструкцию, стандартные payload'ы sqlmap не формируют синтаксически валидный SQL. Флаги --prefix и --suffix задают строки перед и после каждого payload'а.
Для запроса вида SELECT ... WHERE name = ' + input + ' prefix — ' (кавычка и пробел), suffix — -- a (комментарий с символом после пробела — чтобы СУБД корректно распознала комментарий). В CTF эти флаги спасают при скобках, двойных кавычках и специфических функциях обёртки. Без них sqlmap может часами гонять невалидные payload'ы и выдавать «parameter does not seem to be injectable».
В задачах категории medium и выше авторы почти всегда добавляют фильтрацию ввода. Простейший случай — замена ключевых слов (SELECT, UNION). Более изощрённый — фильтрация пробелов или мультибайтовая обработка.
Sqlmap поставляется с набором tamper-скриптов через флаг --tamper=script_name. Самые полезные для CTF:
space2comment — заменяет пробелы на /**/, обходит фильтры пробеловbetween — заменяет > на NOT BETWEEN 0 AND, обходит фильтры операторов сравненияrandomcase — меняет регистр ключевых слов (SeLeCt вместо SELECT)charencode — URL-кодирует payloadequaltolike — заменяет = на LIKEСкрипты комбинируются: --tamper=space2comment,randomcase,between. Но tamper-скрипты значительно увеличивают количество запросов, и на CTF-серверах с жёстким rate limiting лучше использовать их точечно — сначала один, проверить, потом добавлять второй. Не надо вываливать весь арсенал сразу.
Отдельный случай — обход WAF мультибайтовыми кодировками. Классический пример (встречается, в частности, на root-me.org) — задача на обход экранирования в кодировке GBK, где addslashes() обходится через символы GBK. Механизм: функция экранирования находит байт одинарной кавычки (0x27) и вставляет перед ним бэкслеш (0x5c). Но если перед бэкслешем стоит ведущий байт GBK из диапазона 0x81–0xFE (например, 0xbf), то сочетание этого байта с 0x5c образует валидный двухбайтовый символ GBK — бэкслеш «поглощается», а кавычка 0x27 остаётся неэкранированной. Payload: admin%bf' OR 1=1 --. Красивый трюк. Защита — mysql_real_escape_string() вместо addslashes() и параметризованные запросы.
Sqlmap покрывает большинство стандартных сценариев, но в CTF регулярно встречаются ситуации, когда автоматика пасует: кастомная логика обработки ввода (шифрование cookie перед использованием в запросе), rate limiting на сервере, многоэтапная обработка payload'а (URL decode → base64 decode → SQL). Тут sqlmap тупо не знает, как подготовить payload, и приходится писать свой скрипт.
Для boolean-based blind базовая логика — перебор позиций символа с бинарным поиском по ASCII-коду:
import requests
url = "http://ctf.example.com/login"
flag = ""
for pos in range(1, 64):
low, high = 32, 126
while low < high:
mid = (low + high) // 2
payload = f"' AND ASCII(SUBSTR((SELECT flag FROM s3cret),{pos},1))>{mid}-- "
r = requests.post(url, data={"user": payload, "pass": "x"})
if "Welcome" in r.text:
low = mid + 1
else:
high = mid
flag += chr(low)
print(f"[+] {flag}")
Этот скрипт — шаблон, который адаптируется под каждый таск: меняется URL, метод отправки, маркер истинности и SQL-конструкция. Ядро — бинарный поиск по ASCII — остаётся неизменным. На одном CTF, где sqlmap не справился с cookie-based инъекцией через нестандартную обработку, подобный скрипт на 20 строк вытащил флаг за 4 минуты. Иногда 20 строк Python'а решают задачу лучше, чем тысячи строк sqlmap.
Порядок действий, который экономит время на соревнованиях:
X-Powered-By, исходный код, комментарии в HTML. Определить СУБД (MySQL, PostgreSQL, SQLite) до начала инъекцииORDER BY N с инкрементом, затем UNION SELECT NULL,... с подбором количестваsqlmap -r request.txt --dbms=X --technique=Y --string="Z", указывая всё, что узнал вручную--dbs, затем -D db_name --tables, затем -D db_name -T table_name --dumpВ терминах MITRE ATT&CK эксплуатация SQL-инъекций в веб-приложениях — Exploit Public-Facing Application (T1190, Initial Access). Извлечение данных через инъекцию иногда соотносят с Databases (T1213.006, Collection), хотя эта техника ATT&CK описывает постэксплуатационный доступ к БД через легитимные интерфейсы, а не через SQLi напрямую. ATT&CK не выделяет отдельной под-техники для SQLi-based извлечения, так что T1213.006 здесь применяется расширительно.
Со стороны защиты SigmaHQ содержит правило app_sqlinjection_errors.yml, детектирующее характерные ошибки SQL в логах приложений. Понимание паттернов обнаружения помогает в задачах на обход WAF: если знаешь, что именно ловит WAF — проще сформировать payload, который пройдёт мимо.
По статистике Verizon DBIR 2025, 26% всех подтверждённых нарушений связаны с веб-атаками, и инъекции — один из компонентов этой категории. Инъекции держатся в OWASP Top 10 (A03:2021). SQL-инъекции в CTF — это те же инъекции в продакшне, только с гарантией наличия уязвимости и отсутствием юридических последствий. Формула на бумаге понятна, но эксплуатация по-настоящему ощущается, когда сам прогоняешь payload через Burp. Готовый стенд есть на HackerLab.pro — категории web и forensics, нужна регистрация, после неё доступны таски всех уровней сложности.
Если говорить о моей практике за последние два года — подход «руками найти, автоматикой извлечь» не подвёл ни разу. Проблемы начинаются в двух крайностях. Первая: пропускаешь ручную фазу и сразу бросаешь sqlmap на таргет. Инструмент генерирует сотни запросов, CTF-сервер банит IP, а ты даже не уверен, что инъекция существует. Вторая: героически пишешь кастомный скрипт на каждый таск, тратя 40 минут вместо трёх. Большинство участников CTF живут в одной из этих крайностей и теряют на ровном месте.
Sqlmap — инструмент эксплуатации, а не обнаружения. Те, кто воспринимает его как серебряную пулю «навёл и стрельнул» — проигрывают тем, кто потратил три минуты на ручное подтверждение. Это не вопрос мастерства — это вопрос дисциплины.
И ещё одно наблюдение, которое мало кто озвучивает: навыки ручного SQLi деградируют быстрее всего. Стоит полгода не решать таски руками, полагаясь только на sqlmap — и на следующем CTF с кастомными фильтрами ты не соберёшь даже базовый UNION payload. Если хочешь не просто решать writeup'ы, а проходить полную цепочку от инъекции до дампа самостоятельно — на WAPT от Codeby эту прогрессию проходят с лабами и ментором, разбирая именно те случаи, где автоматика бессильна.
🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».
0 комментариев
Пожалуйста, войдите, чтобы оставить комментарий.
Загрузка комментариев...