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.
The Core Concept
Section titled “The Core Concept”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)How It Works
Section titled “How It Works”- Scope — list the hosts, paths, parameters. Whitelist hosts in
/etc/hostsor scope file. Write the scope down so you can defend the rate-limit decision later. - Recon — port scan (
nmap -p- --min-rate 5000), service fingerprint (httpx,nuclei -tags tech), subdomain enum (subfinder,amass,sublist3r). - Directory fuzzing —
ffuforferoxbusteragainst the in-scope web hosts. Useraft-large-words.txtordirectory-list-2.3-medium.txt. Filter out common false positives (404s, 200s that match the homepage size). - Parameter fuzzing — for each discovered endpoint, fuzz parameter names with
Arathorn-paramsorburp-parameter-names. Use reflection-based detection: send?FUZZ=specifictoken, look for the token in the response. - Value fuzzing — for each discovered parameter, fuzz values from
SecLists/Fuzzingorrockyou.txt. Watch for SQLi/XSS/SSTI/SSRF/NoSQLi signatures (W2-W21). - CVE scanning —
nuclei -t cvesagainst each in-scope host. Manually verify the positives; nuclei has a ~5-10% false positive rate depending on template quality. - Follow-up — every positive is a hand-investigation. Don’t trust automated scanners for exploitation; trust them for surface discovery.
Common Techniques
Section titled “Common Techniques”| Technique | Description | Detection Difficulty |
|---|---|---|
| Directory brute | ffuf -u https://t/FUZZ -w raft-large-words | Easy — perimeter rate limit fires |
| Recursive directory | feroxbuster --depth 3 -u https://t/ | Easy — recursion amplifies rate |
| File extension brute | ffuf -u https://t/indexFUZZ -w ext.txt | Easy |
| Subdomain brute | ffuf -u https://FUZZ.t.com -w subs.txt -H "Host: FUZZ.t.com" | Easy — many requests to non-existent DNS |
| VHost brute | ffuf -u https://t.com -H "Host: FUZZ.t.com" -w subs.txt | Medium — same IP, different Host header |
| Parameter brute | arjun -u https://t/page or ffuf -u 'https://t/page?FUZZ=x' -w params.txt | Medium — 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 scan | nuclei -u https://t -t cves/ | Easy — nuclei uses many specific CVE templates |
| Tech detection | nuclei -u https://t -t technologies/ -tags tech | Easy |
| LFI fuzz | ffuf -u 'https://t/page?file=FUZZ' -w lfi.txt -mr 'root:' | Medium |
| SSRF fuzz | ffuf -u 'https://t/page?url=FUZZ' -w ssrf.txt -mr 'metadata' | Medium |
| Takeover scan | subjack -w subs.txt -t 50 for subdomain takeover | Medium |
| S3 bucket brute | ffuf -u 'https://FUZZ.s3.amazonaws.com' -w names.txt -mc 200,403 | Easy |
| GraphQL endpoint | ffuf -u 'https://t/FUZZ' -w graphql-paths.txt | Easy |
| API endpoint brute | kiterunner -u https://t/api -w routes-large.kite | Easy — many API endpoints |
| Auth brute | hydra -L users.txt -P pass.txt http-post-form | Easy — high rate of failed logins |
| Tech version brute | nuclei -t technologies -tags tech for fingerprint, then version | Easy |
| Backup file brute | ffuf -u 'https://t/FUZZ.bak' -w files.txt -mc 200 | Easy |
| Wordlist generation | cewl https://t to extract site-specific words | Easy — stealthy |
Hands-On Commands
Section titled “Hands-On Commands”# ============================================================# 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 hostscat subs_passive.txt subs_active.json | sort -u | httpx -silent -title -tech-detect -o live.txt
# ============================================================# Directory fuzzing# ============================================================# Classic directory bruteffuf -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 Xffuf -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 bruteffuf -u 'https://target.com/indexFUZZ' -w /usr/share/seclists/Discovery/Web-Content/raft-large-extensions.txt \ -mc 200,403
# Backup file bruteffuf -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 discoveryarjun -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 stringsffuf -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 reflectionffuf -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 49ffuf -u 'https://target.com/page?name=FUZZ' -w /usr/share/seclists/Fuzzing/SSTI/ssti.txt \ -mr '49' -t 20
# SSRF — match on AWS metadata responseffuf -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:/ etcffuf -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 responseffuf -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-specificnuclei -u https://target.com -t ~/nuclei-templates/http/technologies/ -tags tech,wordpress,apache,nginxnuclei -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.yamlnuclei -u https://target.com -t ~/nuclei-templates/http/cves/2021/CVE-2021-44228.yaml # Log4Shellnuclei -u https://target.com -t ~/nuclei-templates/http/cves/2021/CVE-2021-26855.yaml # ProxyLogonnuclei -u https://target.com -t ~/nuclei-templates/http/cves/2017/CVE-2017-5638.yaml # Struts
# Multi-target scan from a filenuclei -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 sitecewl https://target.com -d 2 -w target-words.txt
# Seclists — install the standard wordlist setsudo 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 templatesnuclei -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 s3scannerpython3 -m s3scanner -f subdomain-derived.txt --out s3-results.txt
# ============================================================# Rate-limiting & stealth# ============================================================# ffuf rate limit — delay per requestffuf -u 'https://target.com/FUZZ' -w words.txt -p 0.5-2.0 # random delay 0.5-2s
# Per-host rate limitingffuf -u 'https://target.com/FUZZ' -w words.txt -rate 50 # 50 req/s cap
# Bypass rate limit via header rotationffuf -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 outputffuf -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 analysisffuf -u 'https://target.com/FUZZ' -w words.txt -od /tmp/ffuf-responses/
# ============================================================# Automation pipeline# ============================================================# Full pipeline: subs → live → dirs → cvessubfinder -d target.com | httpx -silent | tee live.txt | \ feroxbuster -u stdin --depth 2 | tee dirs.txt | \ nuclei -l stdin -t cves/ -o cves.txtDetection — Defender View
Section titled “Detection — Defender View”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-Agentstrings matchingpython-requests,Go-http-client,Mozilla/5.0 (compatible; Nuclei)— automated scanner.
Evasion Techniques
Section titled “Evasion Techniques”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:
ffuf -u https://t/FUZZ -w words.txt -H "User-Agent: FUZZ" -w2 ua-list.txtDefense: 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).
ffuf -u https://t/FUZZ -w words.txt -p 1.0-3.0 # random 1-3s delayDefense: 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:
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.jsonDefense: 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:
nuclei -u https://t -t cves/ --rate-limit 10 --random-agent --timeout 5Defense: 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.
| Evasion | What it bypasses | Defense |
|---|---|---|
| Custom User-Agent | UA-based block | Behavioral rate limit; device fingerprinting |
| IP rotation | IP rate limit | Per-device fingerprint; per-account rate |
| Distributed scanning | Per-IP rate limit | Global rate at CDN |
| Site-specific wordlist | Generic-wordlist detection | Behavioral + dictionary-pattern detection |
| Slow scanning (1 req/s) | Short-window rate | Long-tail cumulative detection |
| Random UA + rate | Nuclei fingerprint | CVE-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 --ciphersrandom 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.
Lab Practice
Section titled “Lab Practice”Container: Build a target with multiple discoverable endpoints.
# Use DVWA + a few endpoints with custom parametersdocker 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.
Interview Questions
Section titled “Interview Questions”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.
Related Modules
Section titled “Related Modules”→ W1 (OWASP Top 10 Overview) · R2 (Scanning) · R8 (Stealth Recon)