All articles

Web Fuzzing — ffuf, Directories, Parameters, VHosts and APIs (HTB CWES)

Built from the Web Fuzzing module of the HTB Academy Web Penetration Tester path (HTB CWES). Revision notes: every command and filter flag kept, the theory condensed, a complete cheatsheet at the end. Labs and lessons are on HTB Academy.

Fuzzing discovers what the app doesn't link to: hidden directories, files, parameters, virtual hosts and API endpoints. It's how you turn a blank homepage into an attack surface.

1. Fuzzing vs brute-forcing, and the core vocabulary

Fuzzing casts a wide net — throw many inputs (wordlists, mutations, junk) at the app and watch how it reacts. Brute-forcing is narrow — try every value for one specific thing (a password, an ID). In web work the line is blurry; both drive a wordlist of payloads against the target and judge the responses.

Term Meaning
Wordlist The dictionary of names/values to try (admin, backup, config, productID…).
Payload The actual data sent (' OR 1=1 --).
Response analysis Reading status codes / sizes / errors to spot anomalies.
False positive / negative A non-issue flagged / a real issue missed.
Scope The part of the app you're fuzzing.

Every tool here works on the FUZZ keyword: you put FUZZ where the wordlist entry goes, and the tool substitutes each word and sends a request. http://target/FUZZ → /admin, /backup, /uploads, …

2. Tooling and wordlists

Four workhorses (install once):

go install github.com/ffuf/ffuf/v2@latest          # ffuf — fast all-rounder (Go)
go install github.com/OJ/gobuster/v3@latest         # gobuster — dirs/files/dns/vhost (Go)
curl -sL https://raw.githubusercontent.com/epi052/feroxbuster/main/install-nix.sh | sudo bash -s $HOME/.local/bin   # feroxbuster — recursive (Rust)
pipx install git+https://github.com/WebFuzzForge/wenum   # wenum — wfuzz fork, great for params

None ship wordlists — they read external files. The standard collection is SecLists (on Pwnbox at /usr/share/seclists/). The go-to web-content lists:

  • Discovery/Web-Content/common.txt — fast general starting point
  • Discovery/Web-Content/DirBuster-2007_directory-list-2.3-medium.txt — deeper directory list
  • Discovery/Web-Content/raft-large-directories.txt — huge directory list
  • Discovery/Web-Content/big.txt — large dirs + files
  • Discovery/DNS/subdomains-top1million-*.txt — subdomain/vhost lists

3. Directory and file fuzzing

Find hidden folders and files (backups, configs, old scripts). Directories:

ffuf -w /usr/share/seclists/Discovery/Web-Content/DirBuster-2007_directory-list-2.3-medium.txt \
     -u http://IP:PORT/FUZZ

Files, with extensions — -e appends each extension to every word:

ffuf -w /usr/share/seclists/Discovery/Web-Content/common.txt \
     -u http://IP:PORT/somedir/FUZZ -e .php,.html,.txt,.bak,.js -v

A config.php.bak or test.php left lying around can leak DB credentials or vulnerable code. Read the output columns: Status, Size, Words, Lines — an entry that differs from the rest is the lead.

4. Recursive fuzzing

When a directory is found, fuzz inside it automatically, down a tree, until a depth limit:

ffuf -w WORDLIST -u http://IP:PORT/FUZZ -e .html -recursion -recursion-depth 2 -rate 500 -ic -v
  • -recursion — queue each discovered dir for its own fuzz pass
  • -recursion-depth N — cap how deep it goes (be kind to the server)
  • -rate — requests/second cap; -timeout — per-request timeout
  • -ic — ignore wordlist comments (# lines)

Recursive fuzzing is powerful but heavy — it can overwhelm a target or trip a WAF, so bound the depth and rate.

5. Parameter and value fuzzing

Parameters are how you talk to the app — GET in the URL (?query=…&category=…), POST in the body (application/x-www-form-urlencoded or multipart/form-data). Fuzz them to find hidden params or the one value that unlocks different behaviour (a step toward IDOR, XSS, SQLi).

Probe first with curl to learn the app's responses, then fuzz. GET value with wenum:

curl "http://IP:PORT/get.php?x=1"              # see how it reacts
wenum -w /usr/share/seclists/Discovery/Web-Content/common.txt --hc 404 \
      -u "http://IP:PORT/get.php?x=FUZZ"       # --hc 404 hides the noise

POST value with ffuf — payload goes in the body via -d:

curl -d "" http://IP:PORT/post.php             # baseline
ffuf -u http://IP:PORT/post.php -X POST \
     -H "Content-Type: application/x-www-form-urlencoded" \
     -d "y=FUZZ" -w .../common.txt -mc 200 -v

The valid value stands out with a 200 OK; confirm it with curl.

6. Virtual host and subdomain fuzzing

Different things (see also Information Gathering): vhosts are picked by the Host header on one IP; subdomains resolve via DNS.

# VHost fuzzing — vary the Host header against the IP (add the base domain to /etc/hosts first)
echo "IP inlanefreight.htb" | sudo tee -a /etc/hosts
gobuster vhost -u http://inlanefreight.htb:81 -w .../common.txt --append-domain

# Subdomain fuzzing — resolve candidates via DNS
gobuster dns -d inlanefreight.com -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt

--append-domain is required for vhost mode in current Gobuster. Note: in the latest release -d sets a request delay — use --domain/--do for the target. Interesting vhosts are the 200 ones.

7. Filtering the output — the real skill

Fuzzers generate floods of results; filtering is what makes them usable. Match (m* = keep only) vs filter (f* = drop) on code, size, words, lines, time.

ffuf (by default matches 200-299,301,302,307,401,403,405,500 — -mc all shows everything, usually a mistake):

Flag Keeps/drops by
-mc / -fc status code (-fc 404, -mc 200, ranges 400-499)
-ms / -fs response size (-fs 0, -ms 3456, -fs 0-1023)
-mw / -fw word count
-ml / -fl line count
-mt time-to-first-byte (-mt >500)
ffuf -u http://example.com/FUZZ -w WORDLIST -fc 404,401,302        # drop common noise
ffuf -u http://example.com/FUZZ -w WORDLIST -mc 200 -fw 427        # keep 200s, drop a boilerplate size

gobuster (-s/-b only in dir mode): -s 200,301 include, -b 404 exclude, --exclude-length 0,404. wenum: --sc/--hc (code), --sl/--hl (lines), --sw/--hw (words), --ss/--hs (size), --sr/--hr (regex on body), --filter/--hard-filter. feroxbuster: -s include status, -C exclude status, -S filter size, -W words, -N lines, -X regex, --filter-similar-to error.html, --dont-scan /uploads.

8. Validating findings

Fuzzing produces leads, not confirmed bugs — validate before you report. Reproduce the request manually, analyse the response, and build a harmless proof of concept (e.g. make a SQLi return the DB version, don't dump data).

curl http://IP:PORT/backup/            # directory listing? ("Index of /backup/")
curl -I http://IP:PORT/backup/password.txt   # headers only — Content-Type + Content-Length

Reading Content-Type (e.g. application/sql) and a non-zero Content-Length confirms a sensitive file exists and has content without you downloading it — responsible validation.

9. Fuzzing Web APIs

APIs differ from web pages: they exchange JSON/XML over endpoints, not HTML pages — so fuzz endpoints and parameters, not directories.

Style Shape Discover endpoints by
REST Resource URLs (/users/123), HTTP verbs = CRUD, JSON docs (Swagger/OpenAPI, often /docs), traffic analysis, parameter-name fuzzing
SOAP One endpoint, XML envelopes the WSDL file, traffic analysis, fuzzing
GraphQL One endpoint (/graphql), queries + mutations introspection query, GraphiQL/Playground, traffic

Three API-fuzzing angles: parameter fuzzing (query/header/body values → injection, XSS, tampering), data-format fuzzing (break the JSON/XML structure → parser bugs), sequence fuzzing (order/timing of calls → race conditions, IDOR, authz bypass).

Even documented APIs hide undocumented endpoints — fuzz for them:

# REST docs usually at /docs  →  then fuzz for hidden endpoints
git clone https://github.com/PandaSt0rm/webfuzz_api.git && cd webfuzz_api
pip3 install -r requirements.txt
python3 api_fuzzer.py http://IP:PORT     # finds e.g. an undocumented /cz... endpoint
curl http://IP:PORT/cz...                 # validate

A 405 Method Not Allowed on an endpoint is a hint you used the wrong verb (try POST/PUT/DELETE). Parameter fuzzing on APIs is where BOLA/BFLA (broken object/function-level authorization) and SSRF surface — covered in the API Attacks module.

10. What to carry into the CWES exam

  • FUZZ goes anywhere — path, extension, parameter value, Host header, API endpoint. Same tool, different placement.
  • Filtering is the skill, not the scan. Learn -fc/-mc, -fs/-ms, -fw/-ml cold; calibrate against the noise (baseline size/words of a 404) and keep only the outliers.
  • Probe with curl first, fuzz second, validate with curl last. Know the normal response before you hunt for the abnormal one.
  • Separate vhost from subdomain fuzzing, and add every host you find to /etc/hosts.
  • For APIs, fuzz endpoints and parameters — read /docs, the WSDL, or run introspection; then look for the undocumented routes.
  • Skills assessment is all of the above with common.txt — dirs, files, params, vhosts, filtered and validated.

Cheatsheet — Web Fuzzing

Install

go install github.com/ffuf/ffuf/v2@latest
go install github.com/OJ/gobuster/v3@latest
curl -sL https://raw.githubusercontent.com/epi052/feroxbuster/main/install-nix.sh | sudo bash -s $HOME/.local/bin
pipx install git+https://github.com/WebFuzzForge/wenum
# wordlists: SecLists → /usr/share/seclists/Discovery/Web-Content/{common.txt,big.txt,raft-large-directories.txt,DirBuster-2007_directory-list-2.3-medium.txt}
#            /usr/share/seclists/Discovery/DNS/subdomains-top1million-*.txt

ffuf — directories / files / recursion

ffuf -w WORDLIST -u http://IP:PORT/FUZZ                      # directories
ffuf -w WORDLIST -u http://IP:PORT/dir/FUZZ -e .php,.html,.txt,.bak,.js -v   # files + extensions
ffuf -w WORDLIST -u http://IP:PORT/FUZZ -e .html -recursion -recursion-depth 2 -rate 500 -ic -v

ffuf — parameters (POST) & wenum (GET)

wenum -w WORDLIST --hc 404 -u "http://IP:PORT/get.php?x=FUZZ"
ffuf -u http://IP:PORT/post.php -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "y=FUZZ" -w WORDLIST -mc 200 -v

gobuster — vhost / subdomain / dir

echo "IP inlanefreight.htb" | sudo tee -a /etc/hosts
gobuster vhost -u http://inlanefreight.htb:81 -w WORDLIST --append-domain
gobuster dns   -d inlanefreight.com -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt
gobuster dir   -u http://IP/ -w WORDLIST -s 200,301 --exclude-length 0

Filtering (match = keep · filter = drop)

ffuf        -mc/-fc code · -ms/-fs size · -mw/-fw words · -ml/-fl lines · -mt time  (-mc all = everything)
gobuster     -s include · -b exclude · --exclude-length   (dir mode only)
wenum        --sc/--hc code · --sl/--hl lines · --sw/--hw words · --ss/--hs size · --sr/--hr regex
feroxbuster  -s include · -C exclude · -S size · -W words · -N lines · -X regex · --filter-similar-to

Validation

curl http://IP:PORT/backup/              # directory listing?
curl -I http://IP:PORT/backup/file       # Content-Type + Content-Length, no download

API fuzzing

curl http://IP:PORT/docs                 # REST/Swagger docs  (SOAP: WSDL · GraphQL: introspection)
git clone https://github.com/PandaSt0rm/webfuzz_api.git && cd webfuzz_api
pip3 install -r requirements.txt && python3 api_fuzzer.py http://IP:PORT
# ffuf a parameter name: ffuf -u "http://IP:PORT/items/1?FUZZ=test" -w params.txt -fc 404

Built from HTB Academy's Web Fuzzing module — labs, lessons and the CWES exam are on HTB Academy.