ffuf: фаззинг виртуальных хостов и скрытых параметров — от wordlist до обхода rate-limit

На пентесте интернет-магазина основной домен показал стандартную витрину — ни одной зацепки. Один запрос с подменённым заголовком Host раскрыл staging-окружение с включённым debug-режимом и полным доступом к панели администратора. На том же IP-адресе, за тем же nginx. От запуска ffuf до находки — четыре минуты. По данным Verizon DBIR 2025, 26% подтверждённых инцидентов связаны с веб-атаками, и часть из них начинается именно с таких скрытых точек входа, которые разработчики считали невидимыми.
Место ffuf в цепочке разведки
ffuf (Fuzz Faster U Fool) — инструмент веб-фаззинга на Go с открытым исходным кодом. Фаззинг — это автоматизированная подстановка значений из словаря в HTTP-запросы, чтобы обнаружить скрытый контент: директории, файлы, виртуальные хосты, параметры.
Если говорить в терминах MITRE ATT&CK (открытая база тактик и техник атак; T-коды вроде T1595 — её идентификаторы), ffuf работает на этапе разведки — Wordlist Scanning (T1595.003, Reconnaissance). Результат фаззинга расширяет поверхность атаки и готовит почву для следующих шагов: от обнаружения Domain Properties (T1590.001) до эксплуатации Exploit Public-Facing Application (T1190, Initial Access).
Цепочка выглядит так: пассивная разведка (OSINT, DNS-записи) → активный фаззинг с ffuf → анализ найденных эндпоинтов → эксплуатация. Без качественного фаззинга половина поверхности атаки — скрытые панели, тестовые API, забытые виртуальные хосты — остаётся невидимой.
Статус инструмента: ffuf v2.1.0, репозиторий github.com/ffuf/ffuf, активно поддерживается, основной мейнтейнер — joohoi. Используется как стандарт наряду с gobuster и wfuzz.
Требования к окружению
Прежде чем запускать команды — проверьте готовность:
- ОС: Kali Linux 2023+, любой Linux-дистрибутив, macOS, Windows через WSL
- RAM: от 2 ГБ свободных (4 ГБ, если параллельно крутится Docker-стенд)
- Go: версия 1.21+ для ffuf v2.1.0 (нужна только при сборке из исходников; загляните в go.mod репозитория)
- Установка ffuf: в Kali —
sudo apt install ffuf; на любой ОС с Go —go install github.com/ffuf/ffuf/v2@latest(эта же команда обновит до последней версии) - Словари: коллекция SecLists —
git clone https://github.com/danielmiessler/SecLists.git - Права: root для ffuf не нужен, хватит сетевого доступа к цели
- Для минилаба: Docker и Docker Compose, интернет для скачивания образа
ffuf vs альтернативы: когда что использовать
| Критерий | ffuf | gobuster | wfuzz | Burp Intruder |
|---|---|---|---|---|
| Скорость | Высокая (Go) | Высокая (Go) | Средняя (Python) | Средняя-низкая |
| Где ставить FUZZ | Где угодно: URL, заголовки, тело, cookie | Фиксированные режимы (dir/vhost/dns) | Где угодно | Где угодно |
| Несколько словарей | Да (clusterbomb, pitchfork) | Нет | Да | Да |
| Автокалибровка фильтров | Да (-ac) |
Нет | Нет | Нет нативно |
| Replay через Burp | Да (-replay-proxy) |
Нет | Нет | Нативно |
| Когда использовать | Универсальный фаззер: vhost, params, dirs | Простой dir/vhost перебор | Сложные цепочки с зависимостями | GUI-анализ, ручная работа |
| Когда НЕ использовать | Многошаговая аутентификация с CSRF | Нужна подстановка в произвольное место | Критична скорость на больших словарях | Community edition (throttled) |
Ключевое отличие: ffuf — не аналог dirb для перебора директорий. Слово FUZZ ставится в любое место HTTP-запроса, что по гибкости приближает его к Burp Intruder. Но ffuf бесплатен и живёт в терминале — идеален для автоматизации и встраивания в pipeline разведки.
Фаззинг виртуальных хостов: от теории к практике
Чем vhost fuzzing отличается от DNS-перебора
Виртуальный хост (vhost) — механизм, при котором один IP-адрес обслуживает несколько сайтов. Какой сайт отдать, сервер решает по заголовку Host в HTTP-запросе или по расширению SNI (Server Name Indication — указание имени сервера при TLS-рукопожатии) для HTTPS.
Два способа обнаружить скрытые хосты:
- DNS brute force — отправляет DNS-запросы к DNS-серверу, проверяя: резолвится ли
staging.target.com? Находит только поддомены, зарегистрированные в публичном DNS. - Vhost fuzzing — отправляет HTTP-запросы напрямую к серверу, подменяя заголовок
Host. Находит виртуальные хосты, которых нет в DNS — внутренние панели, staging, dev-окружения.
Второй способ раскрывает самое интересное: разработчики создают виртуальный хост для staging без DNS-записи и считают его невидимым. Поэтому vhost fuzzing — одна из самых недооценённых техник разведки в bug bounty, о чём справедливо пишут в thehacker.recipes.
Практика: находим скрытый виртуальный хост
Базовая команда для ffuf vhost fuzzing из официального репозитория:
ffuf -w SecLists/Discovery/DNS/subdomains-top1million-5000.txt \
-u http://target.com \
-H "Host: FUZZ.target.com" \
-fs 4242
Что делает каждый флаг:
-w— путь к словарю. Файлsubdomains-top1million-5000.txtсодержит 5000 популярных поддоменов: staging, dev, admin, api и подобные. Для более глубокого перебора беритеsubdomains-top1million-20000.txt-u— URL цели (основной домен или IP-адрес)-H "Host: FUZZ.target.com"— тут вся магия: ffuf подставляет каждое слово из словаря вместоFUZZв заголовокHost-fs 4242— фильтрует ответы размером 4242 байт. Это размер «дефолтного» ответа, который сервер отдаёт, когда виртуальный хост не найден
Как узнать размер дефолтного ответа? Перед запуском ffuf отправьте запрос с заведомо несуществующим поддоменом: curl -s -H "Host: thisDoesNotExist123.target.com" http://target.com | wc -c. Полученное число подставьте в -fs. Или — и это удобнее — используйте автокалибровку: замените -fs 4242 на -ac, и ffuf определит дефолтный ответ сам.
Вывод при обнаружении виртуального хоста выглядит так:
staging [Status: 200, Size: 15234, Words: 345, Lines: 122, Duration: 84ms]
dev [Status: 403, Size: 287, Words: 12, Lines: 8, Duration: 91ms]
Status 200 — хост доступен, копаем дальше. Status 403 — хост существует, но закрыт. Это тоже ценная находка: 403 означает, что доступ ограничен (по IP или через авторизацию), но сам хост реальный. Дальше можно пробовать обходить ограничения.
Если приложение не позволяет фаззить через заголовок Host — например, за обратным прокси с жёсткой валидацией — используйте альтернативный синтаксис: ffuf -c -w wordlist.txt -u "http://FUZZ.target.com/". Здесь FUZZ в URL заставляет HTTP-клиент ffuf автоматически выставить корректные SNI и заголовок Host для каждого кандидата. Флаг -r (follow redirects) добавляйте отдельно, если сервер отвечает редиректами.
Контекст применения виртуальных хостов в пентесте:
- Внешний пентест: ищете staging/dev-окружения за тем же IP, что и основной сайт
- Внутренний пентест: за одним IP корпоративного сервера часто живут десятки приложений (Jira, GitLab, Jenkins), невидимых через публичный DNS. Тут vhost fuzzing особенно результативен
- Bug bounty: при скоупе
*.target.com— первый шаг после пассивной DNS-разведки
Фаззинг скрытых параметров ffuf
Веб-приложение может обрабатывать параметры, которые нигде не видны в интерфейсе: debug-флаги, скрытые поля форм, внутренние API-ключи. Фаззинг параметров помогает их обнаружить, и ffuf справляется с этим не хуже, чем с vhost-перебором.
GET-параметры
Для перебора имён GET-параметров FUZZ ставится на место имени: ffuf -w SecLists/Discovery/Web-Content/burp-parameter-names.txt -u "https://target.com/page.php?FUZZ=test_value" -fs 4242. Значение test_value произвольное — задача найти имя параметра, которое меняет поведение приложения (размер ответа, HTTP-код, количество слов).
В результатах ищите ответы, которые отличаются от дефолтного. Если параметр debug вернул ответ на 500 байт больше — он включает отладочную информацию. Параметр admin с кодом 403 — скрытая проверка привилегий. И то, и другое — точки для дальнейшей эксплуатации.
POST-параметры и Content-Type фильтрация ffuf
Для POST-запросов добавляются флаги -X POST и -d для тела запроса: ffuf -w paramnames.txt -X POST -d "FUZZ=test" -H "Content-Type: application/x-www-form-urlencoded" -u https://target.com/api/action -fc 401. Флаг -fc 401 отфильтрует ответы с кодом 401 (неавторизован): если эндпоинт возвращает 401 на любой невалидный параметр, нас интересуют все остальные коды.
Для JSON API меняйте Content-Type и формат данных: ffuf -w paramnames.txt -X POST -d '{"FUZZ":"test"}' -H "Content-Type: application/json" -u https://target.com/api/v2/action -mc 200. Флаг -mc 200 покажет только ответы с кодом 200 — успешно обработанные запросы.
Фильтрация ответов и автокалибровка
При фаззинге сервер возвращает тысячи ответов — большинство одинаковые. Без фильтрации работать невозможно. У ffuf два механизма.
Ручные фильтры (флаги -f*): -fc 404,403 исключает ответы с указанными кодами; -fs 1234 — ответы размером 1234 байт; -fw 42 — с 42 словами; -fl 10 — с 10 строками. Фильтры убирают шум.
Матчеры (флаги -m*) работают наоборот, показывают только совпадения: -mc 200,301 отображает ответы с кодами 200 и 301; -mr "root:x" — ответы, содержащие строку root:x. Последний вариант полезен при тестировании LFI (Local File Inclusion — уязвимость, позволяющая читать файлы на сервере через манипуляцию путём к файлу).
Автокалибровка (-ac) — одна из сильных сторон ffuf, которую русскоязычные гайды обычно игнорируют. Вместо ручного определения дефолтного размера через curl, ffuf сам отправляет несколько случайных запросов, определяет типичный ответ и фильтрует его: ffuf -w wordlist.txt -u http://target.com/FUZZ -ac. Для нестандартных случаев есть -acc (custom calibration string) и -acs (custom strategies), но в подавляющем большинстве сценариев хватает -ac.
Обход rate-limit при фаззинге ffuf
На реальных целях — программы bug bounty, продакшн-серверы — агрессивный фаззинг быстро приведёт к бану по IP. По умолчанию ffuf работает на 40 потоках, и это серьёзная нагрузка. Три механизма управления скоростью решают проблему.
ffuf -w wordlist.txt \
-u http://target.com \
-H "Host: FUZZ.target.com" \
-ac \
-t 5 \
-p "0.5-1.5" \
-rate 10
Что делает каждый флаг:
-t 5— снижает параллелизм до 5 потоков вместо 40 по умолчанию-p "0.5-1.5"— случайная пауза от 0.5 до 1.5 секунд между запросами. Случайность тут принципиальна: фиксированный интервал проще детектировать на стороне WAF (Web Application Firewall — межсетевой экран для веб-приложений)-rate 10— жёсткий лимит: максимум 10 запросов в секунду.-rateограничивает RPS через token bucket независимо от числа потоков; при совместном использовании с-tфактическая скорость определяется обоими параметрами
Для совсем жёстких ограничений: -maxtime 300 ограничивает общее время сканирования (в секундах). При рекурсии добавьте -maxtime-job 60 — каждая поддиректория будет обрабатываться максимум минуту, после чего ffuf перейдёт к следующей.
Replay через Burp Suite: добавьте -replay-proxy http://127.0.0.1:8080, и ffuf будет дублировать все найденные матчи через Burp. Основной трафик идёт напрямую (быстро), а каждая находка дополнительно появляется в Burp History для детального разбора. Так вы не гоните весь поток через прокси и не теряете в скорости.
Чего делать не стоит: подмена X-Forwarded-For для обхода rate-limit встречается в старых гайдах, но на практике серьёзные WAF (Cloudflare, AWS WAF) не доверяют этому заголовку от клиента. На уровне базового nginx/Apache — может сработать, если rate-limit настроен по $http_x_forwarded_for, но это скорее ошибка конфигурации цели, а не надёжная техника. WAF с ML-детектом (Cloudflare Bot Management) распознаёт фаззинг по поведенческим паттернам независимо от заголовков.
Ограничения и когда ffuf не подходит
- Словарь = потолок. ffuf перебирает то, что вы ему дали. Если
staging-v2-internalотсутствует в словаре — вы его не найдёте. Решение: генерируйте кастомные словари через CeWL (извлечение слов с сайта цели), добавляйте паттерны из собранных данных разведки - Сложная аутентификация. Если цель требует CSRF-токен на каждый запрос или многошаговый OAuth-флоу, ffuf не справится. Тут нужен Burp Suite Turbo Intruder с Python-скриптами или кастомный фаззер
- Риск DoS. На 40 потоках по умолчанию ffuf создаёт нагрузку, сопоставимую с лёгкой DoS-атакой для слабого сервера. На продакшне без согласования с владельцем — прямой путь к инциденту. Начинайте с
-t 5и увеличивайте постепенно - HTTPS и SNI. При vhost fuzzing через HTTPS сервер использует SNI для определения хоста на этапе TLS-рукопожатия. Если SNI не совпадает с заголовком
Host— получите ошибку TLS вместо HTTP-ответа. Решение: фаззьте через HTTP (порт 80) или используйте синтаксис сFUZZв URL - Поведенческий WAF. Современные WAF детектируют фаззинг по паттернам поведения, а не только по RPS. Обход таких систем — отдельная тема, выходящая за рамки базового ffuf
Минилаб: стенд за 10 минут
Требования: Docker, Docker Compose, 2 ГБ свободного RAM, интернет для скачивания образа.
Шаг 1. Клонируйте стенд ffuf.me — среду, созданную специально для отработки навыков фаззинга с ffuf: git clone https://github.com/adamtlangley/ffufme.git && cd ffufme. Стенд упоминается на странице официального репозитория ffuf как рекомендуемая площадка для практики.
Шаг 2. Запустите контейнер: docker-compose up -d. Проверка: curl -s http://localhost | head -3 — должна вернуться HTML-страница с инструкциями. Видите HTML — стенд работает.
Шаг 3. Попробуйте ffuf vhost fuzzing на стенде: ffuf -w SecLists/Discovery/DNS/subdomains-top1million-5000.txt -u http://localhost -H "Host: FUZZ.ffuf.me" -ac -t 10. Автокалибровка отсечёт шум, а реальные виртуальные хосты появятся в результатах. Пустой вывод — все ответы одинаковые, скрытых хостов нет (на этом стенде основной акцент на директории и параметры).
Шаг 4. Переключитесь на фаззинг параметров: ffuf -w SecLists/Discovery/Web-Content/burp-parameter-names.txt -u "http://localhost/cd/param/data?FUZZ=1" -ac. Найденные параметры покажут, как приложение реагирует на скрытые входные данные.
Онлайн-версия стенда доступна по адресу http://ffuf.me — подходит для тренировки без Docker, но скорость ограничена сетевым соединением.
Большинство русскоязычных гайдов по ffuf сводятся к перечислению флагов и паре команд для перебора директорий — по сути, пересказ ffuf -h. Реальная ценность ffuf не в скорости перебора /admin и /backup — gobuster делает то же самое. Ценность — в гибкости: FUZZ в заголовке Host превращает перебиратель директорий в инструмент, который находит то, чего нет ни в одном DNS-резолвере. Но есть проблема, о которой редко говорят: качество результата на 90% определяется словарём, а не инструментом. На одном из engagement я видел, как коллега два часа фаззил цель с дефолтным common.txt на 4700 записей — ноль результатов. Потом — 15 минут с кастомным словарём из 200 слов, собранных из JavaScript-файлов через linkfinder и cewl, дали три скрытых эндпоинта. Один из них привёл к полноценному RCE. Инструмент — это рука. Голова — это понимание, что именно вы ищете, и словарь, собранный под конкретную цель. Кто начинает путь в пентест и хочет выстроить эту логику системно — базовый трек IB Basics в Codeby Academy для тех, кто не хочет в одиночку ковыряться в Kali.
Эту тему и смежные навыки разбирают на практике в курсе «Тестирование веб-приложений на проникновение (WAPT)» Codeby Academy.