wfuzz is a web fuzzer written in Python, developed primarily by Xavier Mendez (@xmendez). The de-facto tool today is the Go-based ffuf, but the "ancestor" that laid down its design ideas is wfuzz. What it does is the same as ffuf: brute-force part of an HTTP request against a dictionary to surface "what exists." It replaces a marker called FUZZ placed in the request with each element of a payload (a wordlist or numeric range), then reads the differences in the responses to discover hidden directories, files, parameters, and subdomains. Being Python, it is slower than ffuf, but the flexibility of combining payloads, encoders, iterators, and a filter expression language still makes it well worth learning.
What wfuzz is — the FUZZ keyword and payloads #
There are two keys to understanding wfuzz: the FUZZ keyword and payloads (-z). Wherever you place that string in a request, wfuzz replaces it with each element of the payload 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 |
To brute-force several positions at once, write the second and later markers as FUZ2Z / FUZ3Z …, matching them in the order you pass the -z options. wfuzz 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 "show/hide filters" below are its lifeblood.
The notion of "place FUZZ anywhere and substitute it from a dictionary" came from wfuzz first; ffuf is a fast reimplementation of it in Go. So the two are conceptually very similar, yet their option sets are different things. For example, to filter by status code, ffuf uses -mc / -fc while wfuzz uses --sc / --hc. They are easy to confuse, so this article keeps wfuzz's conventions clearly distinct.
Legal and ethical considerations #
wfuzz sends hundreds 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 wfuzz 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 administer — a local test environment, a VPS you pay for, an isolated learning lab.
- Targets you have explicit written permission for — a pentest or vulnerability-assessment contract where the scope (target hosts, period, rate limits) is documented.
- Legitimate learning platforms — Hack The Box, TryHackMe, the PortSwigger Web Security Academy, and other environments where the operator permits fuzzing.
The faster a tool fires a dictionary, the more fatal a careless shot at someone else becomes. If you fire at a production service without rate control (-s / -t), the worst case — the target going down, damages, and criminal liability — is entirely possible. Always confirm the target and your authorization first.
Payloads (-z) — the heart of wfuzz #
Where ffuf centers on a single wordlist (-w), wfuzz's hallmark is choosing "what material to feed in" via a payload type (-z). The form is -z type,params.
| Main payload types | How to write it | Purpose |
|---|---|---|
file |
-z file,wordlist.txt |
Feed each line of a wordlist (the basic case) |
range |
-z range,1-100 |
Feed sequential numbers (ID brute-force, etc.) |
list |
-z list,admin-login-backup |
A quick inline list, hyphen-separated |
hex_range |
-z hex_range,0-ff |
A hexadecimal range |
-w wordlist.txt is a shorthand for -z file,wordlist.txt. You may use -w just as you would in ffuf.
$ wfuzz -z file,/usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt https://target/FUZZ
# -z file,LIST payload of type file pointing at a wordlist
# FUZZ in the trailing URL is replaced by each line
# (writing -w LIST is equivalent)Passing multiple -z options maps them in order to FUZZ / FUZ2Z … . You choose how they combine with an iterator (-m) — the equivalent of Burp Suite Intruder's attack modes.
| Iterator (`-m`) | Behavior | Typical use |
|---|---|---|
product (default) | All combinations of the payloads (Cartesian product) | Every username × password pair |
zip | Consumes each list in parallel at the same position | Verifying paired user/pass entries |
chain | Concatenates multiple lists into one and consumes them in order | Throwing several dictionaries at one position |
$ wfuzz -z file,users.txt -z file,pass.txt -m product \
-d "username=FUZZ&password=FUZ2Z" https://target/login
# FUZZ = users.txt, FUZ2Z = pass.txt
# -m product for all combinations (-m zip pairs same-index lines)Show/hide filters — killing false positives #
If you just "fire wfuzz blindly," the results drown in a sea of false positives. Many servers return a custom 404 page with status 200 (soft-404) even for non-existent paths, or hand back the same response to every request. Controlling that is the job of the show family (limit what is displayed) and the hide family (exclude things). Whether you can wield wfuzz comes down almost entirely to this narrowing.
⚠️ This is the biggest difference from ffuf. ffuf uses matchers
-m*/ filters-f*, whereas wfuzz uses show--s*/ hide--h*.-mcdoes not exist in wfuzz (that is ffuf's syntax). To filter by status code in wfuzz, use--sc/--hc.
| Criterion | show (display only these) | hide (exclude these) |
|---|---|---|
| Status code | --sc 200,301,403 |
--hc 404,400 |
| Chars | --sh 1234 |
--hh 0 |
| Words | --sw 56 |
--hw 0 |
| Lines | --sl 10 |
--hl 10 |
| Regex in body | --ss "regex" |
--hs "regex" |
For instance, to see only HTTP 200 use --sc 200; to drop 404s use --hc 404. Note that h maps to chars (byte count), w to words, and l to lines (ffuf's -ms is size, but wfuzz uses char count --sh/--hh).
$ wfuzz -w wordlist.txt --hh 0 https://target/FUZZ
# --hh 0 hide responses with 0 chars (empty body)
# --hc 404 drop 404s / --sc 200 show only 200
# --hw 56 if misses are always 56 words, exclude them allHit a non-existent path once by hand (e.g. /zzz-not-exist-123) and note its status, char count, and word count. If misses are a constant "200 / 1234 chars / 56 words," excluding them wholesale with --hh 1234 or --hw 56 leaves the hits as candidates. Char- and word-count filters are often stronger than status code.
When misses vary slightly per candidate and can't be killed by a fixed value, wfuzz lets you write logical conditions with its powerful filter expression language (--filter) — a weapon ffuf lacks. The fields are c (code) / l (lines) / w (words) / h (chars), combined with and / or / = / != / > / < / =~ (regex).
$ wfuzz -w wordlist.txt --filter "c=200 and h>100" https://target/FUZZ
# keep only status 200 AND body over 100 chars
# --filter "c=200 and l!=5" 200 but drop the 5-line miss
# --filter "w=12 or w=34" pick up 12- or 34-word responsesModes in practice — discovery / parameters / brute-force #
Where you place FUZZ switches the mode. Three representative ones.
Parameter name / value discovery #
Place FUZZ in the query string to find hidden parameter names the app accepts, or valid values (e.g. a file that triggers LFI, a debug debug).
$ wfuzz -w params.txt --hh 0 "https://target/page?FUZZ=test"
# only parameter names that changed the body length remainPOST data / login brute-force #
Place FUZZ in the body with -d to brute-force a login form's password (passing -d makes the method POST automatically). Reject failure responses with --hc / --hh so only successes remain.
$ wfuzz -w passwords.txt \
-d "username=admin&password=FUZZ" \
--hc 200 https://target/login
# if a failed login returns 200, hiding it leaves success (302 etc.)Brute-force IDs with a numeric range #
With -z range you can feed sequential numbers without a wordlist file — handy for testing IDOR (broken access control).
$ wfuzz -z range,1-500 --sc 200 "https://target/user?id=FUZZ"
# feed 1..500 into id, show only ids that return 200Encoders, output, and operational tips #
To transform a payload before it is sent, put an encoder in the third field of -z (e.g. -z file,words.txt,urlencode). List the available encoders with wfuzz -e encoders.
| Encoder example | Effect |
|---|---|
urlencode |
URL-encode (send values containing symbols safely) |
base64 |
Base64-encode |
md5 / sha1 |
Send a hashed value |
none |
No transform (default) |
The main options for tuning how you fire. Rate control in particular matters — to avoid knocking the target over, to avoid detection, and as a matter of courtesy.
| Option | Effect |
|---|---|
-t 10 |
Concurrent connections (default 10). Lower it for slower, lighter load |
-s 0.1 |
Delay between requests (seconds). Essential for real assessments |
-H "..." / -b "..." |
Add headers / cookies (exploring post-auth pages) |
-p 127.0.0.1:8080:HTTP |
Go through a proxy (pipe into Burp to observe/record) |
-R 2 |
Recurse into hit directories up to depth 2 |
-L / --follow |
Follow redirects (3xx) |
--basic user:pass |
Basic auth (--ntlm / --digest also available) |
-f out.json,json |
Write results to a file (list printers with -e printers) |
-o html |
Format stdout (raw / html / json, etc.) |
A wfuzz scan is easy to detect: a burst of 404/403 in a short window lines up in the access log, and if the User-Agent is left at its default a Wfuzz/x.x string remains. A WAF, rate limiting, and 404 monitoring catch most of it. An attacker avoids this by slowing down with -s or spoofing with -H "User-Agent: ...", but in a legitimate assessment leaving traces is not itself a problem — the premise is that you operate within an authorized scope.
Where it sits among similar tools — when to pick wfuzz #
There are several web content-discovery tools, and wfuzz is defined by "Python flexibility + a filter expression language + encoders/iterators." It loses on speed to the Go-based tools.
| Tool | Language | Characteristics |
|---|---|---|
wfuzz | Python | The originator. Flexible (payload types, encoders, the `--filter` expression) but slow. Active maintenance has stalled |
ffuf | Go | Fast. A general fuzzer that places FUZZ anywhere; a successor that reimplemented wfuzz's ideas at speed |
gobuster | Go | Fast. A simple mode-based design (dir/dns/vhost) |
feroxbuster | Rust | Fast with strong recursion. Good for digging deep via auto-recursion |
| Narrowing | wfuzz | ffuf |
|---|---|---|
| Show by status | `--sc 200` | `-mc 200` |
| Exclude by status | `--hc 404` | `-fc 404` |
| Exclude by size/chars | `--hh 0` | `-fs 0` |
| Compound conditions via expression | `--filter "c=200 and h>0"` | not supported |
For day-to-day work firing large dictionaries fast, ffuf / feroxbuster are more comfortable. wfuzz, meanwhile, shines for the "elaborate single shot": when you want to transform payloads with an encoder, write a complex hit condition in one go with a --filter expression, or freely combine multiple payloads with an iterator. The first step to wielding both: never mix up ffuf's -mc / -fc with wfuzz's --sc / --hc.