Virtual Host Enumeration
Virtual host enumeration finds applications routed by hostname on the same IP. When a server returns different content depending on the Host header, there are likely multiple apps running — some not linked from the main site. Run this after tech fingerprinting confirms Host header sensitivity, or after subdomain bruteforce returns names resolving to the same IP.
Confirm Virtual Host Routing First
Before bruteforcing, verify the server responds differently to different Host headers:
612
Explain command
| -s | Silent mode, suppresses progress and error messages. |
| -k | Allow insecure SSL connections, skipping certificate verification. |
| http://$TARGET_IP/ | Target URL with variable placeholder for the target IP address. |
14832
Explain command
| -s | Silent mode; suppresses progress and error output. |
| -k | Allow insecure TLS connections, skipping certificate verification. |
| http://$TARGET_IP/ | Target URL using the variable IP address. |
| -H "Host: $DOMAIN" | Sets custom Host header to the specified domain variable. |
Different response sizes confirm virtual host routing is active. If sizes match, the server either has no vhost routing or falls back to a default for unknown hosts — bruteforcing will still run but filter results need attention.
ffuf Virtual Host Bruteforce
ffuf injects wordlist entries into the Host header. The key is filtering out the default response size so you only see results that differ from the baseline:
Explain command
| -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt | Specifies the wordlist file used for fuzzing input. |
| -u http://$TARGET_IP/ | Target URL where FUZZ keyword will be substituted during requests. |
| -H "Host: FUZZ.$DOMAIN" | Sets a custom Host header, fuzzing subdomain portion with wordlist entries. |
| -mc 200,204,301,302,307,401,403 | Match only responses with these specific HTTP status codes. |
Filter the default response size to remove noise. Measure it first, then add -fs:
612
Explain command
| -s | Silent mode; suppresses progress meter and error messages. |
| -k | Allow insecure TLS connections, skipping certificate verification. |
| http://$TARGET_IP/ | Target URL using the variable placeholder for the target IP address. |
| -H "Host: invalid.invalid" | Sets a custom Host header to an invalid/non-existent hostname. |
dev [Status: 200, Size: 14832, Words: 1234] admin [Status: 302, Size: 0, Words: 0]
Explain command
| -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt | Wordlist file path used for fuzzing input. |
| -u http://$TARGET_IP/ | Target URL with placeholder for the destination host. |
| -H "Host: FUZZ.$DOMAIN" | Custom Host header; FUZZ keyword is replaced with each wordlist entry. |
| -mc 200,204,301,302,307,401,403 | Match only responses with these HTTP status codes. |
| -fs 612 | Filter out responses with a body size of exactly 612 bytes. |
Any result with a size different from the baseline is a valid virtual host. Add the discovered names to /etc/hosts and browse them directly:
Explain command
| $TARGET_IP | Variable placeholder for the target IP address to map. |
| dev.$DOMAIN | Hostname constructed from the DOMAIN variable with 'dev.' subdomain. |
| admin.$DOMAIN | Hostname constructed from the DOMAIN variable with 'admin.' subdomain. |
| -a | Appends output to the file instead of overwriting it. |
gobuster vhost Mode
gobuster has a dedicated vhost mode that also measures response differences. Useful as a second pass when ffuf results look incomplete:
Explain command
| vhost | Mode to enumerate virtual hostnames via HTTP Host header fuzzing |
| -u | Target base URL to use for vhost enumeration |
| http://$TARGET_IP/ | Placeholder for the target IP address with HTTP scheme |
| -w | Specifies the wordlist file path for brute-forcing vhosts |
| /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt | Wordlist containing top 20000 common subdomains |
| --append-domain | Appends the base domain to each wordlist entry in Host header |
| -t | Sets the number of concurrent threads for faster enumeration |
| 50 | Thread count value — 50 parallel goroutines used |
The --append-domain flag appends the base domain to each wordlist entry, producing dev.example.com rather than just dev. Required for most modern setups.
HTTPS Virtual Hosts
For HTTPS targets, add -k to skip certificate validation and specify the full HTTPS URL:
Explain command
| -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt | Wordlist file used for fuzzing input. |
| -u https://$TARGET_IP/ | Target URL with $TARGET_IP as the host placeholder. |
| -H "Host: FUZZ.$DOMAIN" | Sets Host header; FUZZ is replaced by wordlist entries, $DOMAIN is the base domain. |
| -mc 200,204,301,302,307,401,403 | Match only responses with these HTTP status codes. |
| -fs 612 | Filter out responses with a body size of 612 bytes. |
| -k | Disable TLS/SSL certificate verification. |
Non-Standard Ports
When the target runs on a non-standard port, specify it in both the URL and the Host header:
Explain command
| -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt | Wordlist file used for fuzzing subdomain values. |
| -u http://$TARGET_IP:$PORT/ | Target URL with IP and port placeholders to fuzz against. |
| -H "Host: FUZZ.$DOMAIN:$PORT" | Sets Host header, inserting wordlist entry as subdomain via FUZZ keyword. |
| -mc 200,204,301,302,307,401,403 | Match only responses with these HTTP status codes. |
| -fs 612 | Filter out responses with a body size of exactly 612 bytes. |
After discovering virtual hosts, run content discovery separately against each vhost — they often have completely different directory structures and applications.
References
-
Host header fuzzing, size filtering, and output options
-
vhost mode usage and append-domain flag
-
SecLists — DNS subdomains wordlistsgithub.com/danielmiessler/SecLists (opens in new tab)
subdomains-top1million wordlists for vhost bruteforce
Was this helpful?
Your feedback helps improve this page.