Skip to content

Web Fuzzing & Automation

Web fuzzing is the act of throwing large input lists at HTTP endpoints to find unexpected behavior. Every manual web pentester eventually hits the wall where the eyes glaze over and the brain demands a script. The post-2015 tooling — ffuf (Joohoi), nuclei (ProjectDiscovery), feroxbuster, Burp Suite’s scanner, wfuzz, gobuster — turned what was hours of clicking into minutes of command line. The 2019 Capital One breach was an SSRF + S3-bucket enumeration that a ffuf-style run would have found in minutes; the 2020 TikTok subdomain takeover was a subjack-style scan. The skill in fuzzing isn’t the tool — it’s the wordlist, the rate limit, the filter, and the follow-up: every false positive you can avoid saves hours of triage.

Fuzzing is a loop: send request → compare response to a baseline → categorize (interesting / not). The interesting categories are: unexpected status codes, unexpected response sizes, unexpected response time, unexpected content (strings). The naive approach is “send N payloads, list results”; the skilled approach is “send N payloads, cluster by response, investigate the clusters that differ from the baseline.”

Fuzz loop:
for payload in wordlist:
response = send(payload)
features = extract(response)
if features != baseline:
queue_for_investigation(payload, features)
Features per response:
- HTTP status code
- Content-Length
- Word count (whitespace-tokenized)
- Line count
- Response time
- Specific strings ("error", "warning", "exception", "stack trace")
- Set-Cookie presence/absence
- Location header (redirects)
Wordlist choice:
directory: raft-large, directory-list-2.3-medium
parameters: burp-parameter-names, ArAVeL-params
subdomain: subdomains-top1million-110000, jhaddix-all.txt
values: rockyou.txt, SecLists-Usernames
payloads: SecLists-Fuzzing, Polyglot payloads
Rate limit:
- concurrent: -t (ffuf) — threads
- delay: -p (ffuf), --delay (gobuster)
- rate: --rate-limit (nuclei), -rate (ffuf)
- smart backoff: 429 → slow down, 200 → maintain
Filters (post-send):
-mc (match codes) -fc (filter codes)
-ms (match size) -fs (filter size)
-ml (match lines) -fl (filter lines)
-mw (match words) -fw (filter words)
-mr (match regex) -fr (filter regex)
-mt (match time) -ft (filter time)
Recursion:
-recursion, -recursion-depth (ffuf)
--depth (feroxbuster)
Spider (Burp)
Output:
-o, -of json/csv/html
-od (output directory for full responses)
Visualization:
- Burp Suite Pro: Intruder + Scanner
- ffuf2burp (ffuf results importable as Burp history)
  1. Scope — list the hosts, paths, parameters. Whitelist hosts in /etc/hosts or scope file. Write the scope down so you can defend the rate-limit decision later.
  2. Recon — port scan (nmap -p- --min-rate 5000), service fingerprint (httpx, nuclei -tags tech), subdomain enum (subfinder, amass, sublist3r).
  3. Directory fuzzing — ffuf or feroxbuster against the in-scope web hosts. Use raft-large-words.txt or directory-list-2.3-medium.txt. Filter out common false positives (404s, 200s that match the homepage size).
  4. Parameter fuzzing — for each discovered endpoint, fuzz parameter names with Arathorn-params or burp-parameter-names. Use reflection-based detection: send ?FUZZ=specifictoken, look for the token in the response.
  5. Value fuzzing — for each discovered parameter, fuzz values from SecLists/Fuzzing or rockyou.txt. Watch for SQLi/XSS/SSTI/SSRF/NoSQLi signatures (W2-W21).
  6. CVE scanning — nuclei -t cves against each in-scope host. Manually verify the positives; nuclei has a ~5-10% false positive rate depending on template quality.
  7. Follow-up — every positive is a hand-investigation. Don’t trust automated scanners for exploitation; trust them for surface discovery.
TechniqueDescriptionDetection Difficulty
Directory bruteffuf -u https://t/FUZZ -w raft-large-wordsEasy — perimeter rate limit fires
Recursive directoryferoxbuster --depth 3 -u https://t/Easy — recursion amplifies rate
File extension bruteffuf -u https://t/indexFUZZ -w ext.txtEasy
Subdomain bruteffuf -u https://FUZZ.t.com -w subs.txt -H "Host: FUZZ.t.com"Easy — many requests to non-existent DNS
VHost bruteffuf -u https://t.com -H "Host: FUZZ.t.com" -w subs.txtMedium — same IP, different Host header
Parameter brutearjun -u https://t/page or ffuf -u 'https://t/page?FUZZ=x' -w params.txtMedium — harder to detect (one endpoint)
Value brute (SQLi)ffuf -u 'https://t/page?id=FUZZ' -w sqli.txt -mr 'sql syntax'Medium — signature-based
Value brute (XSS)ffuf -u 'https://t/page?q=FUZZ' -w xss.txt -mr 'FUZZ'Easy — reflection
CVE scannuclei -u https://t -t cves/Easy — nuclei uses many specific CVE templates
Tech detectionnuclei -u https://t -t technologies/ -tags techEasy
LFI fuzzffuf -u 'https://t/page?file=FUZZ' -w lfi.txt -mr 'root:'Medium
SSRF fuzzffuf -u 'https://t/page?url=FUZZ' -w ssrf.txt -mr 'metadata'Medium
Takeover scansubjack -w subs.txt -t 50 for subdomain takeoverMedium
S3 bucket bruteffuf -u 'https://FUZZ.s3.amazonaws.com' -w names.txt -mc 200,403Easy
GraphQL endpointffuf -u 'https://t/FUZZ' -w graphql-paths.txtEasy
API endpoint brutekiterunner -u https://t/api -w routes-large.kiteEasy — many API endpoints
Auth brutehydra -L users.txt -P pass.txt http-post-formEasy — high rate of failed logins
Tech version brutenuclei -t technologies -tags tech for fingerprint, then versionEasy
Backup file bruteffuf -u 'https://t/FUZZ.bak' -w files.txt -mc 200Easy
Wordlist generationcewl https://t to extract site-specific wordsEasy — stealthy
Terminal window
# ============================================================
# Reconnaissance phase
# ============================================================
# Subdomain enumeration (passive)
subfinder -d target.com -all -o subs_passive.txt
# Subdomain enumeration (active brute)
ffuf -u 'https://FUZZ.target.com' -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt \
-H "Host: FUZZ.target.com" -mc 200,301,302,401,403 -t 50 -o subs_active.json -of json
# Resolve to live hosts
cat subs_passive.txt subs_active.json | sort -u | httpx -silent -title -tech-detect -o live.txt
# ============================================================
# Directory fuzzing
# ============================================================
# Classic directory brute
ffuf -u 'https://target.com/FUZZ' \
-w /usr/share/seclists/Discovery/Web-Content/raft-large-words.txt \
-mc 200,204,301,302,307,401,403,405 \
-t 50 \
-recursion -recursion-depth 2 \
-o dirs.json -of json
# Filter out the "default 200" — every directory exists with a 200 of size X
ffuf -u 'https://target.com/FUZZ' -w dirs.txt -fs 1782 # filter size 1782
# Recursive scan with feroxbuster (auto-recursion)
feroxbuster -u https://target.com -w raft-large-words.txt -t 50 --depth 3 \
-o ferox.txt -A --filter-status 404
# File extension brute
ffuf -u 'https://target.com/indexFUZZ' -w /usr/share/seclists/Discovery/Web-Content/raft-large-extensions.txt \
-mc 200,403
# Backup file brute
ffuf -u 'https://target.com/FUZZ' \
-w /usr/share/seclists/Discovery/Web-Content/raft-large-words.txt \
-mc 200 \
-e .bak,.old,.txt,.zip,.tar.gz,.sql,.swp,.git,.svn
# ============================================================
# VHost brute (same IP, different Host header)
# ============================================================
ffuf -u 'https://target.com' -w subdomains.txt -H "Host: FUZZ.target.com" \
-mc 200,301,302 -fs <baseline-size>
# ============================================================
# Parameter fuzzing
# ============================================================
# Arjun — hidden parameter discovery
arjun -u https://target.com/page -m GET -o params.json
# FFUF — reflection-based parameter brute
# Send ?FUZZ=arbitrarytoken; check if response contains "arbitrarytoken"
ffuf -u 'https://target.com/page?FUZZ=zxqt99' -w params.txt -mr 'zxqt99' -t 30
# ============================================================
# Value fuzzing — match on signatures
# ============================================================
# SQLi detection via error strings
ffuf -u 'https://target.com/page?id=FUZZ' -w /usr/share/seclists/Fuzzing/SQLi/Generic-SQLi.txt \
-mr 'sql syntax\|mysql\|postgres\|oracle\|sqlite\|mssql' -t 20
# XSS reflection
ffuf -u 'https://target.com/page?q=FUZZ' -w /usr/share/seclists/Fuzzing/XSS/XML.txt \
-mr 'FUZZ' -t 20 # but FUZZ in mr is literal; use a specific token
# SSTI detection — payload returns 49
ffuf -u 'https://target.com/page?name=FUZZ' -w /usr/share/seclists/Fuzzing/SSTI/ssti.txt \
-mr '49' -t 20
# SSRF — match on AWS metadata response
ffuf -u 'https://target.com/page?url=FUZZ' \
-w /usr/share/seclists/Fuzzing/SSRF/SSRF.txt \
-mr 'ami-id\|instance-id\|iam' -t 10
# LFI — match on root:/ etc
ffuf -u 'https://target.com/page?file=FUZZ' \
-w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt \
-mr 'root:\|bin/bash\|etc/passwd' -t 10
# NoSQLi — match on a different response
ffuf -u 'https://target.com/login' -X POST \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"FUZZ"}' \
-w /usr/share/seclists/Fuzzing/NoSQLi.txt -mc 200
# ============================================================
# Nuclei — CVE & vulnerability scanning
# ============================================================
# All CVE templates (large, slow)
nuclei -u https://target.com -t ~/nuclei-templates/http/cves/ -o cves.json -json
# Technology-specific
nuclei -u https://target.com -t ~/nuclei-templates/http/technologies/ -tags tech,wordpress,apache,nginx
nuclei -u https://target.com -t ~/nuclei-templates/http/exposures/ -tags config,backup,debug,swagger
# Specific CVEs (template ID format)
nuclei -u https://target.com -t ~/nuclei-templates/http/cves/2024/CVE-2024-XXXX.yaml
nuclei -u https://target.com -t ~/nuclei-templates/http/cves/2021/CVE-2021-44228.yaml # Log4Shell
nuclei -u https://target.com -t ~/nuclei-templates/http/cves/2021/CVE-2021-26855.yaml # ProxyLogon
nuclei -u https://target.com -t ~/nuclei-templates/http/cves/2017/CVE-2017-5638.yaml # Struts
# Multi-target scan from a file
nuclei -l live.txt -t cves/ -o all_cves.json -json -rl 50 # rate limit 50/s
# Tags scan (cross-template)
nuclei -u https://target.com -tags sqli,xss,lfi,ssrf,ssti,rce
# ============================================================
# Burp Suite — manual + scanner
# ============================================================
# Burp Pro: scanner uses a different methodology than ffuf/nuclei
# - Crawl phase: spider the app
# - Audit phase: per-endpoint payload injection (SQLi, XSS, SSTI, etc.)
# - Probe phase: CVEs
# Configure: Project options → Scan → set rate limit, scope, scan features
# Burp Intruder — manual fuzzing
# - Sniper: one payload, multiple positions
# - Battering ram: same payload at all positions
# - Pitchfork: different payloads, parallel
# - Cluster bomb: all combinations
# Resource pool: set max concurrent requests (5-10 is polite, 30+ is fast)
# ============================================================
# Wordlist generation
# ============================================================
# CeWL — extract words from a target site
cewl https://target.com -d 2 -w target-words.txt
# Seclists — install the standard wordlist set
sudo apt install seclists # /usr/share/seclists/
# Or: git clone https://github.com/danielmiessler/SecLists
# ============================================================
# Subdomain takeover
# ============================================================
# Takeover scan (CNAME → unclaimed S3, Heroku, GitHub Pages, etc.)
subjack -w subs.txt -t 50 -ssl -o takeover.txt -a
# nuclei takeover templates
nuclei -u https://target.com -t ~/nuclei-templates/http/takeovers/
# ============================================================
# S3 bucket brute
# ============================================================
ffuf -u 'https://FUZZ.s3.amazonaws.com' -w company-prefixes.txt -mc 200,403 -t 50
# Or with the s3scanner
python3 -m s3scanner -f subdomain-derived.txt --out s3-results.txt
# ============================================================
# Rate-limiting & stealth
# ============================================================
# ffuf rate limit — delay per request
ffuf -u 'https://target.com/FUZZ' -w words.txt -p 0.5-2.0 # random delay 0.5-2s
# Per-host rate limiting
ffuf -u 'https://target.com/FUZZ' -w words.txt -rate 50 # 50 req/s cap
# Bypass rate limit via header rotation
ffuf -u 'https://target.com/FUZZ' -w words.txt \
-H "X-Forwarded-For: FUZZ2.2.3.4" -w2 ips.txt # one IP per request
# ============================================================
# Output & triage
# ============================================================
# ffuf JSON output
ffuf -u 'https://target.com/FUZZ' -w words.txt -o results.json -of json -mc 200,301
# Then: jq '.' results.json | jq '.results[] | {url, status, length, words}'
# Burp: import ffuf results via ffuf2burp extension
# Save full responses for offline analysis
ffuf -u 'https://target.com/FUZZ' -w words.txt -od /tmp/ffuf-responses/
# ============================================================
# Automation pipeline
# ============================================================
# Full pipeline: subs → live → dirs → cves
subfinder -d target.com | httpx -silent | tee live.txt | \
feroxbuster -u stdin --depth 2 | tee dirs.txt | \
nuclei -l stdin -t cves/ -o cves.txt

ModSecurity OWASP CRS — every fuzz request hits the CRS rule families:

  • 920120 (multipart) — catches large upload probes
  • 920300 (protocol) — catches malformed requests
  • 942100 (SQLi) — catches SQLi payload words
  • 941100 (XSS) — catches XSS payload words
  • 932100 (RCE) — catches shell metacharacters

Suricata SID 2010935 — Nmap NSE Scan (general scanning signature) [verify]. Suricata SID 2221017 — known scanner user-agent strings [verify].

MITRE ATT&CK T1595 — Active Scanning (the recon stage that fuzzing supports). MITRE ATT&CK T1595.002 — Active Scanning: Vulnerability Scanning (nuclei cves). MITRE ATT&CK T1595.001 — Active Scanning: Scanning IP Blocks (subdomain brute → IP blocks). MITRE ATT&CK T1592 — Gather Victim Host Info. MITRE ATT&CK T1592.004 — Gather Victim Host Info: Client Configurations.

Wazuh rule 31103 — Nmap-style scan (rate-based on SYN without ACK) [verify]. Wazuh rule 920/621 — fuzz pattern (repeated 404s followed by 200) [verify]. Wazuh decoder for HTTP access log — alert on:

  • 50 requests/minute to non-existent paths from one IP — directory brute.

  • 100 requests/minute to different subdomains of one parent — subdomain brute.

  • 10 requests/minute with User-Agent: ffuf / nuclei / gobuster — naive scanner (some tools send UA).

  • Nuclei-specific request shapes (e.g., /login.action, /api/jsonws — known CVE probe paths).

Defender log signatures:

  • 404 rate spike on a normally-quiet host → directory brute in progress.
  • 401 rate spike on /login → auth brute in progress.
  • 200 response after 100 404s to /admin → directory brute found admin path; alert on the IP.
  • High-volume traffic from a single IP with User-Agent strings matching python-requests, Go-http-client, Mozilla/5.0 (compatible; Nuclei) — automated scanner.

Custom User-Agent. Default ffuf uses ffuf/2.x. Many WAFs block this. Use --user-agent "Mozilla/5.0 (X11; Linux x86_64)..." or randomize from a UA list:

Terminal window
ffuf -u https://t/FUZZ -w words.txt -H "User-Agent: FUZZ" -w2 ua-list.txt

Defense: behavioral rate limit, not just UA-based block. Also: device fingerprinting (JA3/JA4 hash).

Rate limit spread. If the target rate-limits per IP, rotate IPs (residential proxy). Or delay (per-host --delay 1s).

Terminal window
ffuf -u https://t/FUZZ -w words.txt -p 1.0-3.0 # random 1-3s delay

Defense: device fingerprinting instead of IP rate limit; per-device or per-account rate limit.

Distributed scanning. Split the wordlist across multiple IPs and merge results:

Terminal window
split -l 1000 words.txt chunk_ && for f in chunk_*; do
ffuf -u https://t/FUZZ -w $f -mc 200 -t 10 -o results_${f}.json &
done; wait; cat results_chunk_*.json | jq -s '.[] | .results[]' > all_results.json

Defense: global rate limit at the CDN (Akamai, Cloudflare, Fastly all support this).

Wordlist refinement. Generic wordlists produce mostly false positives. Use site-specific wordlists generated from the target’s own content (CeWL) — fewer requests, lower rate, better hit rate.

Defense: detect requests that map to a site-specific dictionary; harder to distinguish from organic traffic.

Slow scanning. --rate 1 — one request per second. Below any rate-based detection. 86400 requests per day = 30 million per year.

Defense: long-tail detection (cumulative count over 24h, 7d, 30d); not just short-window rate.

Nuclei random UA + rate limit. nuclei supports --random-agent and --rate-limit. For stealth:

Terminal window
nuclei -u https://t -t cves/ --rate-limit 10 --random-agent --timeout 5

Defense: behavioral detection (CVE-path patterns even with random UA).

Gobuster speed + ffuf stealth. Use gobuster for fast directory brute (its recursion is simpler), use ffuf for parameter and value fuzzing with custom filters.

EvasionWhat it bypassesDefense
Custom User-AgentUA-based blockBehavioral rate limit; device fingerprinting
IP rotationIP rate limitPer-device fingerprint; per-account rate
Distributed scanningPer-IP rate limitGlobal rate at CDN
Site-specific wordlistGeneric-wordlist detectionBehavioral + dictionary-pattern detection
Slow scanning (1 req/s)Short-window rateLong-tail cumulative detection
Random UA + rateNuclei fingerprintCVE-path-pattern detection

⚠️ What caught me: A target had Cloudflare in front with a strict rate limit at 100 req/min per IP. I rotated IPs but got detected — Cloudflare correlated by JA3 fingerprint (the TLS client hello hash), not just IP. Same fingerprint across IPs = same client = banned. The fix was randomizing the TLS client hello (curl with --tls-max 1.3 --ciphers random selection), which is hard from a script. The eventual bypass was a SaaS residential proxy that randomized both IP and TLS profile per request. Lesson: rate-limit defenses today don’t key on IP alone; they key on TLS fingerprint + IP + behavioral sequence. If you want to evade detection, you need to vary all of them. If you’re a defender, you should be fingerprinting TLS, not just IP.

Container: Build a target with multiple discoverable endpoints.

Terminal window
# Use DVWA + a few endpoints with custom parameters
docker run --rm -d -p 80:80 vulnerables/web-dvwa
# Plus a custom Nginx with hidden directories:
docker run --rm -d -p 8080:80 --name fuzz-target nginx
# Inside the container, create directories:
docker exec fuzz-target mkdir -p /usr/share/nginx/html/{admin,backup,api/v1,test}
docker exec fuzz-target sh -c 'echo "secret" > /usr/share/nginx/html/admin/flag.txt'
docker exec fuzz-target sh -c 'echo "test" > /usr/share/nginx/html/backup/old.zip'
# Now fuzz:
ffuf -u http://localhost:8080/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-large-words.txt -mc 200,301,403 -t 30
# Then run nuclei on the live hosts:
nuclei -u http://localhost:8080 -t ~/nuclei-templates/http/technologies/
# Burp Suite Community Edition has all the manual scanning features.

Alternative: install the Damn Vulnerable Web Application (DVWA) + a deliberately-insecure container like webgoat/goatandwolf and enumerate both.

Objective: discover at least 5 hidden paths via directory brute, find at least 2 hidden parameters via parameter fuzzing, and run nuclei to detect at least 1 technology (e.g., Nginx). Document the wordlist used, the rate, the filter that worked, and the IPs/requests it took to find each item.

Expected flag format: flag{ffuf_or_nuclei_or_burp_directory_or_param_discovery}.

Detection that should fire: ModSecurity CRS rule family — every payload triggers some rule. Suricata SID 2010935 on the scanner UA pattern. Wazuh rule 31103 on the SYN-without-ACK pattern. Cloudflare-style WAF should fire on ffuf User-Agent. The application access log will show a sudden spike of 404s from a single IP — the directory brute signature.

Conceptual: explain why fuzzing wordlist selection matters more than tool selection. Give a concrete scenario where a generic wordlist fails but a site-specific wordlist succeeds with 10x fewer requests.

Technical: walk through a ffuf command that fuzzes parameters via reflection. What does the -mr flag do, why does it work, and what is the false-positive risk?

Scenario: you are tasked with scanning a production web app for vulnerabilities without affecting normal traffic. Describe the rate-limit strategy, the host isolation, the detection you’ll generate, and the cleanup post-scan.

Compare ffuf, feroxbuster, nuclei, and Burp Suite Scanner. For each, give the specific class of finding it’s best at, and the operational tradeoff (speed, false-positive rate, depth).

What is the defender’s “rate-limit fingerprint” (IP + UA + TLS + behavior) and why is it more effective than any single dimension? Give a concrete attacker evasion that defeats each dimension alone.

→ W1 (OWASP Top 10 Overview) · R2 (Scanning) · R8 (Stealth Recon)