ENUM^:Enumeration

wfuzz

Calibrate FUZZ requests against real misses, control concurrency, save results, and validate virtual-host or parameter hits manually.

intermediateupdated 2026-10-12wfuzz · ffuf

wfuzz

A web fuzzer built around one idea: put the keyword FUZZ anywhere in a request (URL, header, POST body, cookie) and wfuzz substitutes each wordlist entry there. Older than ffuf, still all over HTB writeups. Check your installed build with wfuzz --version and inspect wfuzz --help before reusing options.

The model

wfuzz -w wordlist.txt [filters] <url-with-FUZZ>

Everything is “fire N requests, hide the boring ones.” You almost always start by hiding the baseline response so only interesting results remain.

FlagMeaning
-w filewordlist (repeat for multiple FUZZ, FUZ2Z, …)
-z payloadpayload generator (range, file, list — e.g. -z range,1-1000)
-u urltarget URL (or give it as the last arg)
-H "Hdr: val"add/override a header (put FUZZ here for vhosts)
-d "body"POST body (put FUZZ here for form/param fuzzing)
-b "cookie"cookie
-X METHODHTTP method
-t Nconcurrent connections (default 10)
-p ip:port:typeproxy (HTTP/SOCKS4/SOCKS5)
-ccolorized output

Hide / show filters (the important part)

HideShowFilters on
--hc CODE--sc CODEHTTP status code
--hl N--sl Nnumber of lines
--hw N--sw Nnumber of words
--hh N--sh Nnumber of response size as reported by wfuzz
--hs regex--ss regextext in the response body

Run once with no filter, see what the uninteresting responses measure, then --hh/--hw/--hl that value away.

Recipes

# directory / file brute
wfuzz -c -z file,/usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt \
  --hc 404 http://10.10.10.5/FUZZ

# vhost / subdomain brute via Host header — hide the default-response size
wfuzz -c -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt \
  -H "Host: FUZZ.trick.htb" --hh 5480 http://10.10.10.5
# then narrow on a found prefix (HTB Trick):
wfuzz -c -w subdomains.txt -H "Host: preprod-FUZZ.trick.htb" --hh 5480 http://10.10.10.5

# GET parameter name discovery (HTB StreamIO)
wfuzz -c -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt \
  -H "Cookie: PHPSESSID=..." --hh 1678 "https://streamio.htb/admin/?FUZZ="

# POST body — login/param fuzz
wfuzz -c -w pass.txt -d "username=admin&password=FUZZ" --hs "Invalid" http://10.10.10.5/login

# two payloads at once (user x pass)
wfuzz -c -w users.txt -w pass.txt -d "user=FUZZ&pass=FUZ2Z" --hc 401 http://10.10.10.5/login

# numeric range (IDOR / id sweep)
wfuzz -c -z range,1-500 --hh 0 "http://10.10.10.5/item?id=FUZZ"

[!tip] vhost fuzzing needs the IP in the URL For -H "Host: FUZZ.site.htb", point the URL at the IP (or the bare domain), not FUZZ.site.htb — you’re varying the Host header against one server, not resolving each name. Add every discovered vhost to /etc/hosts afterward.

[!tip] Calibrate, don’t guess A wildcard vhost or catch-all route returns a constant size for every miss. One probe (wfuzz ... FUZZ with a clearly-bogus value) tells you that size; --hh <that> removes the noise. If sizes vary slightly, filter on --hw (words) or --hl (lines) instead of --hh.

[!warning] Watch out

  • Zero displayed hits can mean every response was filtered; inspect an unfiltered control before concluding there are no candidates.
  • --hc 404 alone is weak against apps that return 200 + a “not found” page; filter on size/words instead.
  • Use ffuf when its calibration/output model better suits the run; the equivalent filters are mapped below.

ffuf equivalents

wfuzzffuf
-w list FUZZ in URL-w list -u http://host/FUZZ
-H "Host: FUZZ.x"-w list -u http://IP -H "Host: FUZZ.x"
--hh 5480-fs 5480 (filter size) / -mc all -ac (auto-calibrate)
--hc 404-fc 404
--hw 12-fw 12
# the StreamIO/Trick vhost brute, in ffuf:
ffuf -w subdomains.txt -u http://10.10.10.5 -H "Host: FUZZ.trick.htb" -mc all -ac

Build a baseline and retain the result

# Three misses reveal whether the wildcard response is stable
wfuzz -c -z list,missing_alpha-missing_beta-missing_gamma   -H 'Host: FUZZ.domain.htb' http://10.10.10.5/

# Replace 5480 with the size measured above, not a value from another box
wfuzz -c -t 5 -s 0.2 -w subdomains.txt   -H 'Host: FUZZ.domain.htb' --hh 5480   -f evidence-vhosts.json,json http://10.10.10.5/

# Verify a candidate against the same IP, outside the fuzzer
curl -i --resolve portal.domain.htb:80:10.10.10.5 http://portal.domain.htb/

Success: the candidate has repeatable behaviour distinct from multiple misses. A single size difference can be a timestamp, reflection or rate-limit page. Save the request shape and filter as well as the hits, so the result is reproducible.

For HTTPS, the Host header and TLS SNI are separate. A changed Host header cannot repair the wrong TLS virtual host; use a hostname URL with a deliberate connection mapping when testing that case. Avoid following redirects at first if their destination could leave the intended server.

Multiple payloads change the request count

# Two files produce combinations: endpoint × parameter name
wfuzz -c -t 3 -w endpoints.txt -w parameter-names.txt \
  'http://app.example.test/FUZZ?FUZ2Z=lab'

With the default product iterator, 100 endpoints and 50 parameters create 5,000 requests. Inspect the planned payload sizes before starting. Paired credentials need a deliberately selected iterator and matching lists; otherwise a two-list login example can become a full combination attack.

Keep authentication stable during discovery

wfuzz -c -t 3 -s 0.2 -w parameter-names.txt \
  -b 'session=<CURRENT_LAB_SESSION>' \
  --hs 'Unknown parameter' \
  'http://app.example.test/search?FUZZ=lab'

Measure the authenticated baseline first. If the session expires, the login redirect or unauthenticated page can look like a new hit for every payload. CSRF tokens can expire or be single-use; wfuzz does not automatically reconstruct arbitrary browser state for each request.

POST and JSON requests

wfuzz -c -t 3 -w field-names.txt \
  -H 'Content-Type: application/json' \
  -d '{"FUZZ":"lab-proof"}' http://app.example.test/api/search

Payload text is substituted into the request; it is not automatically JSON-escaped for arbitrary values. Use a wordlist appropriate to the selected field and check one generated request through your inspection proxy. A malformed JSON response measures parsing failure rather than hidden parameter behaviour.

Compare a hit with multiple controls

curl --resolve portal.example.test:80:192.0.2.20 \
  --dump-header portal-headers.txt --output portal-body.txt \
  http://portal.example.test/
curl -H 'Host: missing-alpha.example.test' \
  --dump-header miss-headers.txt --output miss-body.txt http://192.0.2.20/
wc -c portal-body.txt miss-body.txt

Compare title, redirect destination, cookies and stable body content as well as length. A reflected Host value can change length without selecting a different site. Repeat the request after reducing concurrency if rate-limit pages appeared.

Failure diagnosis

ObservationNext check
Every request is a hitWrong baseline, reflected input, expired auth or catch-all routing
Every response hiddenRerun a known-good control without filters
429 or increasing timeoutsLower concurrency/delay and observe server behaviour
TLS site differs from HTTP HostUse the intended hostname/SNI and connection mapping
Unexpected off-site redirectInspect Location before choosing to follow it

Save payload lists, exact request shape, filters and result file. Name discovery is complete when a candidate produces repeatable behaviour; identifying a vulnerability in that endpoint is the next investigation.

See Also

Sources

On this page

☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.