ffuf (Fuzz Faster U Fool) is a fast web fuzzer written in Go, released by Joona Hoikkala (@joohoi) in 2018. The word "fuzzing" usually evokes throwing malformed input at protocols or binaries, but what ffuf does is brute-force part of an HTTP request against a wordlist to surface "what exists." It replaces a marker called FUZZ placed in the request with each line of a wordlist, then reads the responses to discover hidden directories, files, parameters, subdomains, and virtual hosts. Go's concurrency makes it very fast, so it has become the de-facto tool for the web-enumeration phase of pentests and CTFs.
What ffuf is — the FUZZ keyword at its core #
There is exactly one key to understanding ffuf: the FUZZ keyword. Wherever you place that string in a request, ffuf replaces it with each line of the wordlist (-w) and sends the request. Where you put it determines what you are looking for.
Where you place FUZZ |
What you end up searching for |
|---|---|
End of the URL path (/FUZZ) |
Directory / file discovery |
A query/body value (?id=FUZZ) |
Parameter value brute-force |
A query key (?FUZZ=x) |
Hidden parameter-name discovery |
The Host header (Host: FUZZ.target) |
vhost / subdomain enumeration |
For example, brute-forcing https://target/FUZZ with admin / login / backup … makes the paths that actually exist on the server stand out by their status code or response size. ffuf itself doesn't understand "hit vs. miss" from context — it relies on differences in the response for a human to judge the hits. That is exactly why the "matchers and filters" below are its lifeblood.
ffuf is mostly about content discovery and enumeration. It points in a different direction from classic fuzzers that crash a target with malformed values — it's closer to "a brute-forcer that fires a dictionary fast and looks for hits."
Legal and ethical considerations #
ffuf sends thousands to tens of thousands of requests in a short time. Technically that is a load close to a DoS, and against the wrong target it is clearly an attack. Firing ffuf at someone else's server without permission can be an illegal act under Japan's Unauthorised Computer Access Act or laws against obstruction of business, and the equivalents elsewhere.
- Servers / apps you own or operate — a local test environment, a VPS you pay for, an isolated learning lab
- Targets with explicit written permission — a pentest or vulnerability-assessment contract where the scope (hosts, period, rate limits) is documented
- Legitimate learning platforms — Hack The Box, TryHackMe, PortSwigger Web Security Academy, and similar environments that allow fuzzing
The faster the tool, the more lethal a casual shot at someone else's box becomes. Fire at a production service without a rate limit (-rate / -t) and you can knock it over — leading to damages and criminal liability in the worst case. Always confirm the target and your authorization first.
The basics — directory/file discovery and wordlists #
The most basic use is directory/file discovery, placing FUZZ in the URL path.
$ ffuf -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt -u https://target/FUZZ
# -w the wordlist to use
# -u target URL (where FUZZ is substituted)Whether a fuzz run hits or misses is decided by the quality of the wordlist. The de-facto choice is SecLists. Some representative lists by purpose:
| Wordlist | Purpose |
|---|---|
Discovery/Web-Content/raft-medium-directories.txt |
Directory list based on real-site observations (balanced) |
Discovery/Web-Content/raft-medium-files.txt |
File-name list (raft-* comes in directories/files/words variants) |
Discovery/Web-Content/directory-list-2.3-medium.txt |
The classic large list (from DirBuster) |
Discovery/Web-Content/common.txt |
Light and fast opener (~4,700 entries) |
Discovery/DNS/subdomains-top1million-*.txt |
For subdomain enumeration |
To also try extensions, use -e. For each candidate of FUZZ, it additionally sends requests with the given extensions appended.
$ ffuf -w wordlist.txt -u https://target/FUZZ -e .php,.html,.bak,.txt -recursion -recursion-depth 2
# -e also try each candidate with these extensions
# -recursion dig further inside directories that hitMatchers and filters — how to kill false positives #
Just "firing" ffuf drowns your results in a sea of false positives. Many servers return a custom 404 page (soft-404) with a 200 status for nonexistent paths, or answer every request identically via a wildcard. Controlling this is what matchers and filters are for. Whether you can wield ffuf at all comes down almost entirely to these two.
- Matchers (
-m*) — show only what matches the criteria (a whitelist) - Filters (
-f*) — exclude what matches the criteria (a blacklist)
| Criterion | Matcher | Filter |
|---|---|---|
| Status code | -mc 200,301,403 |
-fc 404,400 |
| Response size (bytes) | -ms 1234 |
-fs 0,4242 |
| Word count | -mw 56 |
-fw 56 |
| Line count | -ml 10 |
-fl 10 |
| Regex | -mr "regex" |
-fr "regex" |
| Response time | -mt >100 |
-ft >100 |
By default ffuf shows -mc 200,204,301,302,307,401,403,405,500. To see only HTTP 200, for instance, state it explicitly with -mc 200. Multiple codes are comma-separated (-mc 200,301,403), and -mc all captures every status.
$ ffuf -w wordlist.txt -u https://target/FUZZ -mc 200
# -mc 200 show only responses with status 200
# -mc 200,301 show only 200 and 301 (comma-separated)
# -mc all capture every status (pair with filters or -ac)Manually hit a nonexistent path once (e.g. /zzz-not-exist-123) and note its status, size, and word count. If misses are constant at "200 / 4242 bytes / 56 words," exclude them wholesale with -fs 4242 or -fw 56, and whatever remains is a candidate hit. Size and word-count filters are often more powerful than status-code ones.
When miss responses vary per candidate and can't be killed with a fixed value, hand it to auto-calibration.
$ ffuf -w wordlist.txt -u https://target/FUZZ -ac
# -ac send a few dummy requests at startup, learn the
# "miss response," and turn it into a filter automatically
# pair with -mc all to capture every status and let -ac drop misses-ac first hits a few random, nonexistent paths and registers their responses (size, word count, etc.) as "this is a miss" into an automatic filter. On servers that return wildcard responses or soft-404s, having it or not makes a night-and-day difference in how readable the results are.
Fuzzing modes and attack modes #
Where you place FUZZ switches the mode. The four representative ones:
Virtual-host / subdomain enumeration #
When one IP hosts multiple domains (virtual hosting), swap the Host header to find hidden sites — including internal hosts that aren't in DNS.
$ ffuf -w subdomains.txt -u https://target.com/ -H "Host: FUZZ.target.com" -fs 4242
# substitute FUZZ in the Host header; drop the miss size with -fsDiscovering GET parameters #
Placing FUZZ in the query string lets you find hidden parameter names the app accepts (e.g. a debug flag, a file selector).
# find parameter names
$ ffuf -w params.txt -u https://target/page?FUZZ=test -fs 0
# brute-force a parameter value (e.g. finding valid id values)
$ ffuf -w ids.txt -u https://target/page?id=FUZZPOST data / login brute-force #
With -X POST and -d you can put FUZZ in the body to brute-force a login form's password, for example. Drop the failure responses with -fc / -fs and keep only the successes.
$ ffuf -w passwords.txt -u https://target/login -X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=admin&password=FUZZ" -fc 200
# if a failed login returns 200, drop it and the success (e.g. 302) remainsMultiple FUZZ and attack modes #
Name your wordlists (-w list.txt:KEYWORD) to brute-force several positions at once. As in Burp Suite's Intruder, the attack mode (-mode) chooses how they combine.
| Attack mode | Behavior | Typical use |
|---|---|---|
clusterbomb | All combinations across wordlists (cartesian product) | Every username × password pair |
pitchfork | Consumes each list in parallel by row index | Validating paired user/pass entries |
sniper | Applies a single list one position at a time | Testing several candidate positions with one dictionary |
$ ffuf -w users.txt:USER -w pass.txt:PASS \
-u https://target/login -X POST \
-d "username=USER&password=PASS" -mode clusterbomb -fc 200Operational tips — rate, output, disguise #
The main options for tuning how you fire. Rate control especially matters — to avoid knocking the target over, to evade detection, and simply as courtesy.
| Option | Effect |
|---|---|
-t 40 |
Concurrent threads (default 40). Lower it for slower, lighter load |
-rate 50 |
Max requests per second. Near-mandatory for real engagements |
-p 0.1 |
Delay between requests (seconds). -p "0.1-0.5" randomizes it |
-H "..." / -b "..." |
Add headers / cookies (to explore authenticated pages) |
-x http://127.0.0.1:8080 |
Through a proxy (pipe into Burp to observe and record) |
-o out.json -of json |
Write results to a file (json / html / csv / md, etc.) |
-ic |
Ignore # comment lines in the wordlist |
-c -v |
Color + verbose (clearer hit URLs and redirect targets) |
-timeout 10 |
Request timeout in seconds |
An ffuf scan is easy to detect: access logs fill with many 404/403 responses in a short window, and if the User-Agent is left default, a Fuzz Faster U Fool-style string remains. A WAF, rate limiting, or 404 monitoring will stop most of it. Attackers dodge this with -rate throttling or -H UA spoofing — but in a legitimate engagement leaving a trace is not the problem; staying within authorized scope is the prerequisite.
Where ffuf sits among similar tools #
There are several web-content discovery tools; ffuf's distinguishing traits are speed + FUZZ anywhere + powerful filters.
| Tool | Language | Characteristics |
|---|---|---|
ffuf | Go | Fast. General fuzzer that puts FUZZ anywhere. Rich matchers/filters |
gobuster | Go | Fast. Simple, mode-based design (dir/dns/vhost) |
feroxbuster | Rust | Fast + strong recursion. Good for digging deep automatically |
wfuzz | Python | ffuf's ancestor. Flexible but slow; being replaced by ffuf |
dirb / dirbuster | C / Java | Classics. Slow but mature. Linger for learning/compatibility |
(1) Open with a light list (common.txt) → (2) observe the shape of a miss and set filters (-fs / -fw / -ac) → (3) dig into directories that hit with recursion or a bigger list. That staged approach is the standard. Far more than raw speed, setting filters correctly so the output is readable is what matters — much more than firing the largest list at full force from the start.