sqlmap обход WAF: ручная настройка tamper-цепочки и слепая инъекция на практике

На пентесте API коммерческого сервиса за Cloudflare сканер Burp Suite находит потенциальную boolean-based SQL-инъекцию в GET-параметре. Запускаю sqlmap -u "https://target/api?id=42" -p id — через полторы минуты получаю does not seem to be injectable. При этом ручной тест через curl подтверждает: запрос с id=42 OR 1=1 возвращает данные, а id=42 OR 1=2 — пустой ответ. Инъекция есть, но WAF (Web Application Firewall — межсетевой экран уровня приложений, который фильтрует HTTP-трафик по сигнатурам) режет стандартные payload’ы sqlmap до того, как они доходят до базы данных.
Знакомая ситуация? Тут sqlmap нужно перевести из автопилота в «режим хирурга»: отключить перебор всего подряд, вручную задать тип инъекции, собрать tamper-цепочку под конкретный фильтр и указать prefix/suffix для точки входа.
Место sqlmap в цепочке атаки
SQL-инъекция — типичный вектор первого проникновения через публично доступное приложение. В терминах MITRE ATT&CK (открытая база тактик и техник атак — каждая техника имеет свой идентификатор, T-код) это T1190, Exploit Public-Facing Application, тактика Initial Access. Полная цепочка при пентесте веб-приложения выглядит так: разведка endpoint’ов → обнаружение инъектабельного параметра → подтверждение уязвимости → эксплуатация через sqlmap → извлечение данных из БД (T1213.006, Databases) → анализ дампа и движение к дальнейшим целям.
Tamper-скрипты, модифицирующие payload’ы для обхода WAF, — это по сути Command Obfuscation (T1027.010): маскировка SQL-кода так, чтобы фильтр не распознал вредоносный паттерн, а СУБД корректно обработала запрос.
[Применимо: внешний пентест, black box, веб-приложение за WAF (Cloudflare, ModSecurity, AWS WAF, Imperva). При внутреннем пентесте WAF между атакующим и приложением обычно отсутствует — стандартный режим sqlmap работает без tamper.]
sqlmap (github.com/sqlmapproject/sqlmap, коммиты идут ежедневно, 33 000+ звёзд на GitHub) остаётся центральным инструментом на этапе эксплуатации SQL-инъекций. До него — работа в Burp Suite или через curl для ручной верификации. После — анализ извлечённых данных и пост-эксплуатация. Именно на этапе слепой инъекции за WAF sqlmap экономит часы ручного посимвольного перебора — при условии, что правильно настроен.
Требования к окружению
Перед практической частью убедитесь, что всё на месте:
- ОС: Kali Linux 2024.x+ или Parrot OS (подойдёт любой Linux с Python)
- Python: 3.8+ (sqlmap работает и на 2.7, но для написания кастомных tamper’ов берите 3.x)
- sqlmap: актуальная версия из Git —
git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git - Burp Suite Community или Pro: для анализа трафика через прокси (по умолчанию слушает на порту 8080)
- curl: для ручной верификации (предустановлен в Kali)
- RAM: 2 ГБ достаточно — sqlmap нетребователен к ресурсам
- Сеть: прямой доступ к целевому приложению; если WAF блокирует по IP — потребуются резидентные прокси через ProxyChains
Все действия — только на легально авторизованных целях: по договору на пентест, в рамках программы bug bounty или на собственном стенде. Несанкционированное тестирование — уголовное преступление (ст. 272, 273 УК РФ).
Почему sqlmap по умолчанию молчит за WAF
При стандартном запуске без дополнительных флагов sqlmap делает две вещи, которые WAF ловит моментально.
User-Agent. По умолчанию sqlmap отправляет строку sqlmap/1.x.x#stable (https://sqlmap.org). Любой WAF с минимальным набором сигнатур блокирует запросы с таким заголовком ещё на входе. Из write-up’а пентестерской компании Vaadata: одна только замена User-Agent иногда переводит ответ сервера из пустого 200 OK с Content-Length: 0 (WAF «молча» дропает запрос) в нормальный ответ с данными. Строка «sqlmap» — буквально первое, что WAF проверяет.
Эвристические тесты. sqlmap начинает с серии «разведочных» запросов: вставляет кавычки, скобки, конструкции типа AND 1=1, UNION SELECT NULL — в открытом виде, без какой-либо обфускации. Каждый из них содержит ключевые слова (UNION, SELECT, SLEEP, BENCHMARK), которые WAF мониторит по сигнатурам. По данным OWASP (SQL Injection Bypassing WAF), WAF обнаруживает вредоносные запросы по трём признакам: ключевые слова SQL, известные вредоносные цепочки символов, частота запросов с одного IP.
Результат: sqlmap получает одинаковые пустые ответы на все свои тесты, не может отличить true от false при boolean-based проверке и рапортует all tested parameters do not appear to be injectable. Это не ошибка инструмента — это sqlmap, которому WAF просто не дал работать. Инъекция при этом может прекрасно существовать.
Ручная верификация слепой SQL инъекции до sqlmap
Доверять выводам автоматики за WAF нельзя — ни положительным (false positive от эвристики), ни отрицательным (WAF порезал все тесты). Ручная проверка занимает 5 минут и даёт три вещи: подтверждение инъекции, определение её типа (boolean-based или time-based), первичный отпечаток WAF-фильтра.
Шаг 1 — зафиксируйте базовое поведение. Отправьте легитимный запрос через curl -s "https://target/api?id=42" -o valid.html. Затем заведомо невалидный: curl -s "https://target/api?id=0" -o invalid.html. Сравните файлы: diff valid.html invalid.html или по размеру wc -c valid.html invalid.html. Ожидаемый результат: в valid.html есть конкретное значение (имя, ID записи), в invalid.html — пустое поле или отсутствие данных. Если ответы отличаются — у вас есть база для boolean-теста.
Шаг 2 — проверьте SQL-логику. Отправьте запрос с условием, которое всегда истинно: curl -s "https://target/api?id=42+OR+1=1" -o true.html. И ложное: curl -s "https://target/api?id=42+OR+1=2" -o false.html. Если true.html совпадает с valid.html, а false.html — с invalid.html, boolean-based blind инъекция подтверждена.
Тут стоит остановиться на секунду. Слепая инъекция (blind SQL injection) — ситуация, когда приложение не выводит данные из SQL-запроса напрямую. Атакующий «угадывает» ответ базы по косвенным признакам: для boolean-based — по изменению контента страницы (true vs false), для time-based — по задержке ответа (SLEEP/PG_SLEEP). Если оба запроса вернули одинаковый пустой ответ или страницу блокировки — WAF срезал payload на уровне ключевого слова OR.
Шаг 3 — определите триггер WAF. Попробуйте обфусцировать пробелы через SQL-комментарии: curl -s "https://target/api?id=42/**/OR/**/1=1" -o bypass.html. Если ответ стал нормальным — WAF фильтрует паттерн «пробел + ключевое слово SQL». Если нет — WAF ловит само слово OR независимо от форматирования, и нужна другая стратегия: hex-кодирование, замена OR на ||, или переход к time-based технике через SLEEP/PG_SLEEP. На выходе вы определите минимум один конкретный триггер WAF, от которого будете отталкиваться при сборке tamper-цепочки.
Режим «хирурга»: sqlmap без эвристики с точечной настройкой
Когда инъекция подтверждена вручную и её тип известен, не нужно просить sqlmap «проверить всё». Каждый лишний тестовый запрос может привести к блокировке по rate limit или добавлению IP в чёрный список WAF. Вот набор флагов, превращающих sqlmap из широкого сканера в точечный инструмент:
--technique=B (или T для time-based) — указывает sqlmap тестировать только один тип инъекции. По умолчанию перебираются все шесть техник: Boolean, Error, Union, Stacked, Time, Out-of-band (сокращённо BEUSTQ). Вы уже определили тип вручную — зачем проверять остальные пять?
--dbms=mysql (или postgresql, mssql) — фиксирует тип СУБД. Без этого sqlmap генерирует payload’ы для 40+ СУБД, большинство которых не имеют отношения к цели.
--random-agent — заменяет User-Agent sqlmap/1.x на случайный браузерный. Минимальная мера, которая отсекает самый грубый слой фильтрации.
--skip-waf — пропускает встроенный тест на наличие WAF. Парадокс: этот тест сам генерирует подозрительные запросы и часто триггерит блокировку. Вы и так знаете, что WAF есть — незачем его «обнаруживать» повторно.
--no-cast — отключает приведение типов через CAST() / CONVERT(). Некоторые WAF блокируют именно эти SQL-функции, которые sqlmap вставляет по умолчанию для нормализации извлечённых данных.
--test-filter="AND boolean-based" — по данным PayloadsAllTheThings, ограничивает набор тестовых payload’ов шаблоном имени. Радикально сокращает количество запросов: вместо сотен тестов — только те, что соответствуют маске.
--not-string='nameEntity=""' — критичный параметр для boolean-based blind инъекции за WAF, который я не встречал ни в одном русскоязычном гайде. Из кейса Vaadata: sqlmap должен знать, как выглядит «ложный» ответ, чтобы отличить true от false. Этот флаг говорит: «если в ответе есть строка nameEntity=""— инъекция вернула false». Альтернатива — --string='конкретное_значение', указывающая маркер true-ответа.
--flush-session — сбрасывает кеш предыдущих проверок. Критично при итеративном подборе: без этого флага sqlmap может использовать закешированный «негативный» результат от предыдущего запуска с другими tamper’ами.
| Критерий | Режим автопилота | Режим «хирурга» |
|---|---|---|
| Техники инъекции | Все 6 (BEUSTQ) | Одна целевая (B или T) |
| Целевая СУБД | Все 40+ | Конкретная (mysql / postgresql) |
| User-Agent | sqlmap/1.x | Случайный браузерный |
| Tamper-обфускация | Нет | Собранная цепочка |
| Prefix / suffix | Автоподбор | Ручной под точку входа |
| Тесты наличия WAF | Да | Пропущены |
| Количество запросов | Сотни — тысячи | Десятки |
Tamper-цепочка sqlmap: подбор от простого к рабочему
Tamper-скрипты sqlmap — Python-модули, которые модифицируют каждый payload перед отправкой на сервер. Встроенных скриптов более 50, и ключ не в том, чтобы навалить побольше, а в том, чтобы подобрать минимально необходимую комбинацию. Каждый лишний tamper в цепочке может сломать синтаксис SQL-конструкции, которую sqlmap пытается исполнить.
Подбор — итеративный процесс. Вот как он выглядит на практике.
Шаг 1. Запустите sqlmap с --random-agent, но без tamper’ов. Если инъекция обнаружена — WAF фильтровал только User-Agent. Из кейса Vaadata: достаточно было флага -A "NONE" (замена User-Agent на строку «NONE»), чтобы обойти фильтрацию Cloudflare на конкретном таргете.
Шаг 2. Не помогло — добавьте --tamper=space2comment. Он заменяет все пробелы в payload’е на SQL-комментарий /**/. Большинство WAF (ModSecurity с CRS, ряд конфигураций Cloudflare) фильтруют паттерн «пробел + ключевое слово», но пропускают /**/UNION/**/SELECT. Из кейса Hackmosphere: при тестировании приложения на PostgreSQL замена пробелов на /**/ стала единственным условием, при котором сервер перестал отклонять запросы — он блокировал любые формы стандартного пробела (%20, +, табуляцию).
Шаг 3. WAF ловит ключевые слова независимо от пробелов? Добавьте randomcase — он превращает SELECT в sElEcT, UNION в UnIoN. Работает против WAF с case-sensitive правилами, но бесполезен против Cloudflare Enterprise и других WAF, которые приводят весь запрос к нижнему регистру перед анализом.
Шаг 4. Добавьте between — заменяет оператор > на NOT BETWEEN 0 AND, обходя сигнатуры, настроенные на символы сравнения. По данным OWASP, синонимы функций и операторов — один из базовых методов обхода: substring() заменяется на mid() или substr(), ascii() на hex() или bin(), benchmark() на sleep().
Какой tamper под какой триггер WAF:
| Что блокирует WAF | Tamper-скрипт | Что делает |
|---|---|---|
| Пробелы | space2comment | Заменяет пробелы на /**/ |
| Ключевые слова (case-sensitive) | randomcase | SeLeCt вместо SELECT |
| Операторы сравнения (>, <) | between | NOT BETWEEN 0 AND |
| Кавычки | apostrophemask | Fullwidth-апостроф вместо обычного |
| Стандартные кодировки | charencode | URL-encode каждого символа |
Рабочая комбинация для Cloudflare (из write-up’а на Habr): --tamper=between,space2comment. Для ModSecurity CRS: --tamper=space2comment,randomcase. Каждый раз: добавили tamper → запустили с --flush-session → если не сработало — пустили трафик через Burp (--proxy=http://127.0.0.1:8080) и в HTTP History посмотрели, какие payload’ы получили нормальный ответ, а какие — пустой 200 OK или 403 Forbidden. Payload’ы заблокированных запросов покажут, какое конкретно слово или конструкция триггерит фильтр — и подскажут следующий tamper.
Кастомный prefix и suffix для слепой инъекции
Опции --prefix и --suffix (описаны в PayloadsAllTheThings) добавляют произвольную строку в начало и конец каждого payload’а sqlmap. Зачем? Когда SQL-запрос в приложении имеет нестандартную структуру, sqlmap не может правильно «вписать» свой payload в контекст: закрыть открытую кавычку, сбалансировать скобки, добавить оператор конкатенации.
Конкретный пример из пентеста (по данным Hackmosphere): приложение на PostgreSQL, уязвимый параметр text в GET-запросе. Серверный SQL выглядит как SELECT ... WHERE text = '<ввод>'. Для time-based инъекции payload должен закрыть кавычку, выполнить PG_SLEEP, и открыть кавычку обратно: a'||((select/**/pg_sleep(3)))||'. В терминах sqlmap — --prefix="'" и --suffix="'".
python sqlmap.py --level=5 --risk=3 \
-u "https://target/api?text=test*" \
--dbms=postgresql --random-agent \
--tamper=space2comment --skip-urlencode \
--technique=T --skip-waf --delay=0.2 \
--prefix="'" --suffix="'" \
--time-sec=3 --flush-session
Что делает каждый флаг:
text=test*— звёздочка обозначает custom injection point, куда sqlmap вставит payload--technique=T— только time-based инъекция; boolean-based на этом таргете не дала результатов--skip-urlencode— не кодировать спецсимволы; некоторые WAF блокируют URL-encoded payload’ы, пропуская raw--delay=0.2— пауза 200 мс между запросами, чтобы не упереться в rate limit WAF--time-sec=3— ждать задержку 3 секунды как индикатор true-условия--level=5 --risk=3— максимальная глубина тестов;--levelрасширяет набор проверяемых точек (Cookie, User-Agent, Referer),--riskразрешает потенциально деструктивные payload’ы. Для GET-запросов с time-based техникой--risk=3безопасен: GET не модифицирует данные в базе
При успешном обходе WAF sqlmap выведет строку вроде GET parameter 'text' appears to be injectable. После этого — стандартный workflow: --current-db для имени базы, --tables для списка таблиц, -T users --columns --dump для извлечения данных.
Пишем кастомный tamper-скрипт на Python
Когда встроенных tamper’ов недостаточно — а на серьёзных WAF это случается регулярно — пишем свой. Архитектура tamper-скрипта sqlmap проста: три обязательных элемента (по данным PayloadsAllTheThings):
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.NORMAL
def dependencies():
pass # можно проверить --dbms
def tamper(payload, **kwargs):
if payload:
payload = payload.replace("SLEEP", "PG_SLEEP")
payload = payload.replace(" ", "/*x*/")
return payload
Файл сохраняется как my_waf.py в директорию tamper/ внутри каталога sqlmap и вызывается через --tamper=my_waf. Можно комбинировать со встроенными: --tamper=space2comment,my_waf.
Разбор структуры:
__priority__— порядок применения в цепочке (от 0 до 100). Если ваш tamper должен сработать ПОСЛЕspace2comment, ставьте число больше, чем у негоdependencies()— вызывается перед стартом. Можно добавить предупреждение, что скрипт предназначен только для PostgreSQLtamper(payload, **kwargs)— главная функция. Получает строку payload’а (то, что sqlmap собирается отправить), возвращает модифицированную. В примере: заменаSLEEPнаPG_SLEEP(для PostgreSQL) и пробелов на кастомный комментарий/*x*/
Реальный сценарий, где кастомный tamper необходим: WAF блокирует стандартный /**/ (пустой комментарий), но пропускает /*любой_текст*/ (комментарий с содержимым). Ни один из 50+ встроенных tamper’ов этого не делает. Кастомный скрипт с заменой пробелов на /* + случайная строка + */ решает задачу за 10 строк кода.
Пара советов по разработке:
- Тестируйте tamper изолированно: простой скрипт
print(tamper("' OR SLEEP(5)--"))покажет, не ломает ли замена SQL-синтаксис - Не трогайте служебные маркеры sqlmap: payload содержит
[INFERENCE],[SLEEPTIME],[RANDNUM]— sqlmap подставляет в них свои значения на следующем этапе - Один tamper — одна задача. Два маленьких скрипта в цепочке лучше, чем один большой с ветвлениями
Ограничения: когда обход WAF SQL инъекцией не работает
Знание границ инструмента — такая же часть экспертизы, как и умение им пользоваться.
Stateful WAF с машинным обучением. Cloudflare Enterprise, Imperva Advanced, AWS WAF с managed rules анализируют не отдельные запросы, а поведение сессии. Десятки однотипных запросов с одного IP за минуту — аномалия независимо от содержимого payload’а. Против таких WAF нужна ротация IP через ProxyChains с пулом резидентных прокси, что выходит за рамки настройки самого sqlmap.
Rate limiting на уровне приложения. HTTP 429 или CAPTCHA после N запросов. Флаги --delay и --safe-freq замедлят sqlmap, но time-based blind инъекция с задержкой 5 секунд между запросами и 3-секундным SLEEP на каждый true-тест — это дни на извлечение одной таблицы. Иногда быстрее написать 50 строк на Python с requests и точечным payload’ом.
Параметризованные запросы (prepared statements) и ORM. Если бэкенд использует prepared statements — SQL-инъекции не существует как класс уязвимости на этом endpoint’е. sqlmap может дать false positive на уровне эвристики, но реальная эксплуатация невозможна. Никакой tamper-скрипт не изменит этого факта.
Whitelist WAF. Некоторые WAF работают не по blacklist (блокировать известные паттерны), а по whitelist (пропускать только явно разрешённое). Если WAF-правило разрешает в параметре id только цифры — любой SQL-синтаксис будет отброшен на входе, до анализа сигнатур.
Детектируемость. Даже с идеальной tamper-цепочкой sqlmap генерирует характерный паттерн в логах: серия запросов с инкрементально меняющимися условиями (ORD(MID((SELECT ...),1,1))=65, затем =66, =67…). Любой SOC-аналитик с настроенным SIEM (система сбора и корреляции логов — Elastic SIEM, Splunk, MaxPatrol SIEM) обнаружит это за минуты по аномальной частоте запросов к одному endpoint’у. На реальном пентесте координируйте действия с blue team, если это не red team engagement с полной скрытностью.
Русскоязычные гайды по sqlmap в подавляющем большинстве заканчиваются на «добавьте --tamper=space2comment,randomcase и получите дамп». На практике в каждом втором кейсе за серьёзным WAF нужна итерация: запустили — посмотрели в Burp, что заблокировано — скорректировали tamper или prefix — запустили снова. Это не «нажал кнопку — получил результат», а методичная работа с HTTP-трафиком, понимание конкретного WAF и конкретной точки инъекции.
Я вижу устойчивую тенденцию: чем мощнее WAF, тем чаще пентестеры уходят от sqlmap в сторону ручных payload’ов через Burp Intruder или кастомных скриптов на Python. Для time-based blind инъекции через enterprise-WAF с ML-моделью иногда проще написать 50 строк на requests с одним выверенным payload’ом, чем подбирать комбинацию из встроенных tamper’ов. Но базу работы с sqlmap знать нужно: это по-прежнему самый быстрый путь от «подтверждённая инъекция» к «дамп базы» — если понимаешь, за какие рычаги дёргать, а не копируешь команды вслепую из чужих writeup’ов. На курсе WAPT в codeby.school эту цепочку — от ручной верификации до tamper-скрипта — проходят в лабах с реальными WAF-конфигурациями.
Эту тему и смежные навыки разбирают на практике в курсе «Тестирование веб-приложений на проникновение (WAPT)» Codeby Academy.