wfuzz Explained — A Flexible Web Fuzzer Written in Python thumbnail

wfuzz Explained — A Flexible Web Fuzzer Written in Python

⏱ approx. 17 min views 32 likes 0 LOG_DATE:2026-06-08
TOC

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.

01

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.

▸ Relation to ffuf — same idea, different implementation

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.

02

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.

▸ Targets you may use wfuzz against
  • 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.

03

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.

A minimal directory scan
$ 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`)BehaviorTypical use
product (default)All combinations of the payloads (Cartesian product)Every username × password pair
zipConsumes each list in parallel at the same positionVerifying paired user/pass entries
chainConcatenates multiple lists into one and consumes them in orderThrowing several dictionaries at one position
Brute-force two payloads as a product
$ 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)
04

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*. -mc does 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).

Drop misses (soft-404) wholesale by char count
$ 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 all
▸ The field move — observe the "shape of a miss" first

Hit 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).

Write compound conditions with a --filter expression
$ 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 responses
05

Modes 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).

Fuzzing parameter names
$ wfuzz -w params.txt --hh 0 "https://target/page?FUZZ=test" # only parameter names that changed the body length remain

POST 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.

Brute-forcing a login form
$ 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).

Brute-force id with a range payload
$ wfuzz -z range,1-500 --sc 200 "https://target/user?id=FUZZ" # feed 1..500 into id, show only ids that return 200
06

Encoders, 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.)
▸ wfuzz's footprint from the defender's side

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.

07

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.

ToolLanguageCharacteristics
wfuzzPythonThe originator. Flexible (payload types, encoders, the `--filter` expression) but slow. Active maintenance has stalled
ffufGoFast. A general fuzzer that places FUZZ anywhere; a successor that reimplemented wfuzz's ideas at speed
gobusterGoFast. A simple mode-based design (dir/dns/vhost)
feroxbusterRustFast with strong recursion. Good for digging deep via auto-recursion
Narrowingwfuzzffuf
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
▸ Bottom line — ffuf for speed, wfuzz for expressiveness

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.

𝕏 Post B! Hatena