Wildcard Vhost Noise on northwind-labs.example
Scope is one root domain, northwind-labs.example, including all subdomains; the window is two working days and the client has asked for no automated scanning of production. Your first dig shows a wildcard: any random label resolves to 203.0.113.10. You run ffuf with 5,000 words against the Host header and every single entry returns 200 with Content-Length 1,503, so the output is useless. Burp's sitemap contains exactly one host you know is real, portal.northwind-labs.example, which serves a React SPA. From the SPA's main.4f2c1a.js you extract references to /api/v2/reports, /api/v1/users and /internal/metrics. robots.txt disallows /backup-2023/, which returns the same 1,503-byte page as the wildcard. The client insists the site is "marketing only" and that there is no public API. You have three hours of testing time before the daily standup.
1. Explain why the vhost brute-force produced nothing usable, and describe concretely how you would rebuild it to produce signal in the next 30 minutes.
Read the worked answer
The wildcard means the edge has a catch-all vhost: any Host header it does not recognise is served the same 1,503-byte page with status 200. ffuf is reporting accurately and the responses simply carry no information, so adding more words will not help. Fix the methodology first.
- Capture a real baseline. Request a hostname that cannot exist (e.g.
zzqq1234.northwind-labs.example) and record status, Content-Length, body SHA-256,Server,X-Powered-By,Set-CookieandLocation. Everything identical to that baseline is noise by definition. - Filter on a stable field.
-fs 1503breaks the moment the page embeds a timestamp or CSRF token. Prefer-ac(ffuf auto-calibration, which sends random Hosts first and derives filters automatically) or-fragainst a regex matching the unique string in the catch-all body. - Look for structural differences, not just length. A real vhost frequently 301s to its canonical name, sets its own cookie, or returns a different
Server/X-Backend. Grep headers across results and sort by body hash rather than by word count. - Move the brute-force to TLS. Resolve 203.0.113.10 yourself and send a per-candidate SNI (
curl -k --resolve candidate:443:203.0.113.10 https://candidate). Edges often select a certificate or backend by SNI even when the Host header is ignored, so a non-matching certificate for a candidate is a strong signal. - Go passive first. Certificate transparency (crt.sh), the Wayback CDX API and passive DNS will surface names no dictionary contains. In 30 minutes this beats 5,000 words of guessing.
Then re-run the Host-header brute with the calibration filters and diff against the baseline. You are not looking for 200s - you are looking for the one response that is not 1,503 bytes of nothing.
2. Rank the four leads - /api/v2/reports, /api/v1/users, /internal/metrics, /backup-2023/ - in the order you would test them, and justify the order in terms of expected value and client impact.
Read the worked answer
Order: /internal/metrics → /api/v1/users → /api/v2/reports → /backup-2023/.
1. /internal/metrics. A path pulled from a JS bundle that is prefixed internal is the highest-value lead because it is the one the developer did not intend you to reach, which means it is the one most likely to be unauthenticated. Metrics endpoints routinely leak internal hostnames, service names, database DSNs and version strings - exactly the inventory you need later for SSRF, host-header and injection work. It is also low-noise: one GET. If it is auth-protected you learn the auth model cheaply.
2. /api/v1/users. The JS bundle references v2 as the current API, so v1 is legacy. Legacy versions are where authorisation checks were retrofitted or never added, and a users collection endpoint is a classic object-level authorisation failure. Test unauthenticated first, then with your lowest-privilege session, then compare the shape of the v1 and v2 responses for fields v2 hides.
3. /api/v2/reports. Current, maintained code. Worth testing, but expect rate limiting and proper checks; it is where you go after you have an auth model from steps 1–2.
4. /backup-2023/. Last, because it is the most likely decoy - it returns the identical 1,503-byte catch-all, which suggests robots.txt is bait or simply stale. Confirm cheaply with a directory listing and a couple of common filenames, but do not spend engagement time there.
Operational note: the client asked for no automated scanning of production, so each of these is a handful of hand-crafted requests, not a fuzz run. Document each request as you send it; the write-up at standup is as much a deliverable as the finding.