Passive and Active Reconnaissance: Subdomain Enumeration with Amass and ffuf

Tools & Defense
Time it takes to read this article 6 minutes.

Legal & Ethical Disclaimer. Everything below is for education and for testing on assets you own or are explicitly authorized to assess (signed scope, bug-bounty policy, or written engagement letter). Passive recon over public data is generally low-risk, but active probing — DNS brute force, vhost fuzzing, port scanning — touches infrastructure you may not own (shared CDNs, third-party SaaS). Stay inside scope, respect rate limits, and never resolve or fuzz hosts you cannot prove you are permitted to touch.

Introduction / Overview

Reconnaissance is the first phase of any engagement, and it disproportionately decides the outcome. A target's real attack surface is rarely the single domain in the scope document — it is the forgotten staging., the vpn-old., the jira. box that nobody patched, and the IP ranges that the marketing site never mentions.

This article splits recon into two disciplines and shows how to combine them:

  • Passive reconnaissance — gathering intel without sending packets to the target. Certificate Transparency logs, DNS aggregators, search engines, and OSINT databases. The target never sees you.
  • Active reconnaissance — directly probing the target: DNS brute force, virtual-host fuzzing, content discovery. Faster and more complete, but noisy and logged.

You'll learn DNS footprinting, large-scale subdomain enumeration with Amass, and high-speed fuzzing of vhosts and paths with ffuf, plus a full Blue Team section so defenders can detect and reduce this exact activity.

How it works / Background

DNS is the backbone of footprinting. A domain like example.com is served by authoritative name servers and exposes record types that leak structure:

Record What it reveals
A / AAAA Host → IPv4/IPv6 mapping
CNAME Aliases, third-party SaaS (potential subdomain takeover)
MX Mail providers (Google, O365, Proofpoint)
NS Authoritative name servers / hosting hints
TXT SPF, DKIM, verification tokens (cloud providers in use)
SOA Admin contact, zone serial

Passive subdomain discovery leans on Certificate Transparency (CT) logs. Since browsers require CT, every TLS certificate issued is published to public append-only logs (crt.sh, Censys). A cert for internal-api.example.com permanently leaks that hostname even if no DNS record is public. Aggregators like SecurityTrails, VirusTotal, and AlienVault OTX add historical DNS data on top.

Active discovery fills the gaps CT misses: hosts that never got a public cert. Two techniques dominate:

  1. DNS brute force — guess dev, vpn, gitlab against the zone and keep what resolves.
  2. Virtual-host (vhost) fuzzing — many sites share one IP behind a reverse proxy. You hit the IP with different Host: headers and diff the responses to find sites that have no public DNS at all.

Amass orchestrates passive sources and active brute force; ffuf handles the high-throughput HTTP fuzzing. MITRE ATT&CK maps this to T1595 (Active Scanning) and T1590 (Gather Victim Network Information).

Prerequisites / Lab setup

Install the toolchain. On Kali/Debian:

# Amass (OWASP) and ffuf
sudo apt update && sudo apt install -y amass ffuf

# Or pull current releases via Go
go install -v github.com/owasp-amass/amass/v4/...@master
go install github.com/ffuf/ffuf/v2@latest

# Helpers
sudo apt install -y dnsutils    # dig / host
go install github.com/projectdiscovery/dnsx/cmd/dnsx@latest
go install github.com/tomnomnom/anew@latest
Bash

Grab a good wordlist (SecLists is the standard):

sudo apt install -y seclists
ls /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt
Bash

Use a domain you control (e.g. a lab domain you registered) as the target placeholder TARGET=example.com throughout.

Attack walkthrough / PoC

Step 1 — Manual DNS footprinting

Start light and passive-ish. Pull the obvious records before any brute force:

TARGET=example.com

dig +short A    "$TARGET"
dig +short NS   "$TARGET"
dig +short MX   "$TARGET"
dig +short TXT  "$TARGET"        # SPF/DKIM → which cloud they use
dig +short CNAME www."$TARGET"
Bash

Try a zone transfer — almost always refused, but a misconfigured secondary NS is an instant full zone dump:

for ns in $(dig +short NS "$TARGET"); do
  dig AXFR "$TARGET" @"$ns" +noall +answer
done
Bash

Step 2 — Passive enumeration with Amass

amass enum -passive queries dozens of OSINT sources and sends zero packets to the target's own infrastructure:

amass enum -passive -d "$TARGET" -o passive.txt
Bash

Amass works far better with API keys. Drop them into the config and it will pull SecurityTrails, Censys, Shodan, VirusTotal, etc.:

# ~/.config/amass/config.ini  (datasources section, snippet)
# [data_sources.SecurityTrails]
# [data_sources.SecurityTrails.Credentials]
# apikey = YOUR_KEY
amass enum -passive -d "$TARGET" -config ~/.config/amass/config.ini -o passive.txt
Bash

Pull CT logs directly as a cross-check:

curl -s "https://crt.sh/?q=%25.$TARGET&output=json" \
  | jq -r '.[].name_value' | sed 's/\*\.//g' | sort -u | anew passive.txt
Bash

Step 3 — Active DNS brute force

Now we touch the target. Amass active mode adds brute force, recursive resolution, and resolves candidates against trusted resolvers:

amass enum -active -d "$TARGET" -brute \
  -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt \
  -rf <(echo -e "1.1.1.1\n8.8.8.8\n9.9.9.9") \
  -o active.txt
Bash

Merge and resolve everything to live hosts with dnsx:

cat passive.txt active.txt | sort -u > all_subs.txt
dnsx -l all_subs.txt -a -resp -silent > resolved.txt
wc -l resolved.txt
Bash

Step 4 — Virtual-host fuzzing with ffuf

Some hosts share one IP and never appear in DNS. Pick a live IP and fuzz the Host: header. First fingerprint a baseline (a bogus host) so you can filter it out:

IP=203.0.113.10

# Baseline: note the response size of a non-existent vhost, e.g. 1256 bytes
curl -s -o /dev/null -w "%{size_download}\n" -H "Host: nope.$TARGET" "https://$IP/" -k

ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt \
     -u "https://$IP/" \
     -H "Host: FUZZ.$TARGET" \
     -fs 1256 \
     -t 50 -o vhosts.json
Bash

-fs 1256 filters the default-vhost size; anything with a different size is a distinct site. Use -fc 301,302 or -ac (auto-calibration) instead when sizes are unstable.

Step 5 — Content / path discovery with ffuf

For each live web host, discover hidden paths:

ffuf -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt \
     -u "https://app.$TARGET/FUZZ" \
     -recursion -recursion-depth 2 \
     -mc 200,204,301,302,401,403 \
     -e .php,.bak,.zip,.config \
     -t 40 -o paths.json
Bash

-mc matches interesting status codes, -e appends extensions, and -recursion dives into discovered directories. 401/403 responses are gold — they confirm something exists but gated.

Mermaid diagram

Passive and Active Reconnaissance: Subdomain Enumeration with Amass and ffuf diagram 1

The diagram shows passive and active recon feeding a merged subdomain set, which is then resolved, vhost-fuzzed, and content-discovered into a final attack-surface map.

Detection & Defense (Blue Team)

Recon is hard to stop entirely (CT logs are public by design), but you can shrink the surface and detect the active half.

Reduce the passive surface

  • CT log hygiene. Use wildcard certificates (*.example.com) for internal hosts so individual hostnames don't leak into crt.sh. Pair with CAA records to restrict who can issue.
  • Audit DNS records. Remove stale A/CNAME entries. Dangling CNAMEs pointing to deprovisioned cloud resources enable subdomain takeover — see Subdomain Takeover via Dangling DNS.
  • Split-horizon DNS. Keep internal hostnames on internal resolvers only; never publish vpn-admin or jenkins in public zones.
  • Disable zone transfers to untrusted IPs (allow-transfer { trusted; }; in BIND). Test with dig AXFR from outside.

Detect the active half

  • DNS brute force generates a flood of NXDOMAIN responses from one source. Alert on a high ratio of failed lookups per client on your authoritative servers. Enable DNS query logging and ship it to your SIEM (maps to ATT&CK T1595.003).
  • vhost / path fuzzing is loud at the web tier: thousands of requests, many 404/403, varied Host: headers, a recognizable User-Agent (ffuf/2.x). WAF rules should rate-limit per source IP and block known scanner UAs. Sigma rules for high-404 bursts catch ffuf and gobuster well.
  • Honeytokens. Publish a fake subdomain (e.g. forgotten-admin.example.com) that resolves only to a sensor. Any hit is, by definition, recon — no legitimate user knows it exists. This dovetails with Canary Tokens for Intrusion Detection.
  • Rate-limit and geo-fence authoritative DNS and admin vhosts. Cloudflare/Akamai bot-management scores catch distributed enumeration that single-IP rules miss.

Don't forget upstream OPSEC. Much of what attackers find comes from your own people: GitHub commits leaking internal hostnames, public Trello boards, verbose SPF records. Secret scanning and a hostname-naming policy that avoids descriptive names (pci-db01) limit the OSINT payoff. For the credential-leak angle, see Finding Secrets in Git History.

Conclusion

Passive recon gives you breadth with zero footprint; active recon gives you the hosts CT logs never saw. The winning workflow is sequential: exhaust passive sources (CT + Amass passive + DNS records), then escalate to active brute force and ffuf vhost/path fuzzing only inside an authorized scope. Feed everything into one deduplicated, resolved list and you have a real attack-surface map instead of the single domain on the scope sheet. Defenders win by treating CT logs as public, killing dangling DNS, and alerting on the NXDOMAIN and 404 bursts that active enumeration cannot hide.

References

Comments

Copied title and URL