Recon & Enumeration
Recon is how you turn a program’s scope into a concrete map of what to test: which hosts exist, which are alive, what they run, and what endpoints they expose. This page builds an end-to-end terminal pipeline with the standard toolkit, then ties it together.
Scope discipline first
Section titled “Scope discipline first”Before running a single tool, write down the in-scope roots and the out-of-scope exclusions from the policy, and keep them where your commands can read them. Feed tools from an allow-list file, not from memory.
mkdir -p ~/bounty/example/recon && cd ~/bounty/example/reconcat > roots.txt <<'EOF'example.comexample-cdn.netEOF# Note the date and the policy version you read, in your session log.date -u; echo 'scope read from HackerOne policy vX' >> notes.logAs results come in, filter them back against scope. A found subdomain that resolves to a third-party provider is out of bounds even though it carries the target’s name — flag and skip it.
Subdomain enumeration
Section titled “Subdomain enumeration”Subdomains multiply your surface: api., staging., internal., legacy. hosts often run older or less-guarded code. Enumeration comes in two flavors:
- Passive — query third-party datasets (certificate transparency logs, DNS aggregators, search APIs). No packets to the target; quiet and safe.
- Active — resolve/brute-force names yourself (DNS brute forcing, permutations). Louder; make sure it’s within the rules of engagement.
subfinder (passive, fast, many sources):
subfinder -dL roots.txt -all -silent -o subs-subfinder.txt# -dL: file of root domains -all: use every source -silent: just the resultsassetfinder (quick passive pull):
assetfinder --subs-only example.com > subs-assetfinder.txtamass (deeper; supports passive and active). Passive keeps you quiet:
amass enum -passive -d example.com -o subs-amass.txt# Active (only if the rules permit brute forcing) adds resolution & permutations:# amass enum -active -d example.com -o subs-amass-active.txtMerge and de-duplicate everything into one candidate list:
cat subs-*.txt | sort -u > subs-all.txtwc -l subs-all.txtResolving and probing live hosts
Section titled “Resolving and probing live hosts”A name in the list means nothing until it resolves and answers. Two ProjectDiscovery tools do this cleanly.
dnsx resolves the candidates and drops the dead ones:
dnsx -l subs-all.txt -silent -a -resp -o resolved.txt# -a: A records -resp: show the resolved value keeps only names that resolvecut -d' ' -f1 resolved.txt | sort -u > live-names.txthttpx probes each host over HTTP/HTTPS and reports what’s actually there — status, title, and detected technology. This is where a raw name list becomes a triaged target list.
httpx -l live-names.txt \ -status-code -title -tech-detect -web-server -follow-redirects \ -silent -o http-live.txt
# JSON output when you want to post-process with jq:httpx -l live-names.txt -json -sc -title -td -silent -o http-live.jsonjq -r 'select(.status_code==200) | "\(.url)\t\(.title)"' http-live.jsonRead the output for signal: unusual titles (“Admin”, “Swagger UI”, “GraphQL Playground”), non-standard servers, staging banners, and 401/403 hosts that hint at protected apps worth a closer look.
Content and endpoint discovery
Section titled “Content and endpoint discovery”Now go inside each live host and find paths the links don’t reveal. Two fuzzers dominate.
ffuf for directory/endpoint discovery (also your parameter fuzzer in Testing REST APIs):
ffuf -u https://api.example.com/FUZZ \ -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt \ -mc 200,204,301,302,401,403 \ -ac -rate 20 -o ffuf-api.json -of jsonferoxbuster does recursive content discovery with sensible defaults — it follows discovered directories down automatically:
feroxbuster -u https://api.example.com \ -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt \ -x json,txt -d 2 -r --rate-limit 20 -o ferox-api.txt# -x: also try these extensions -d 2: recursion depth -r: follow redirectsWordlists — SecLists. The community standard is SecLists, a large collection of curated lists. Pick the list to the job: Discovery/Web-Content/ for paths, .../api/ for API routes, burp-parameter-names.txt for params.
git clone --depth 1 https://github.com/danielmiessler/SecLists /usr/share/seclistsHistorical URLs
Section titled “Historical URLs”Archives remember endpoints that are no longer linked — old API versions, deprecated paths, parameters that still work. Two tools pull them from public sources (Wayback Machine, Common Crawl, etc.).
gau (“Get All URLs”):
gau --subs example.com > urls-gau.txt# or feed it your live hosts:cat live-names.txt | gau --threads 5 > urls-gau.txtwaybackurls (Wayback Machine specifically):
cat live-names.txt | waybackurls > urls-wayback.txtMerge, then mine the combined list for interesting shapes — endpoints with parameters, API paths, and file types worth a look:
cat urls-*.txt | sort -u > urls-all.txt
grep -E '\?|/api/|/graphql|/v[0-9]/' urls-all.txt | sort -u > urls-interesting.txtgrep -Eo 'https?://[^ ]+\.(json|js|xml|bak|config|env)' urls-all.txt | sort -u > urls-files.txtConfirm which archived URLs still respond before you build on them — many are dead:
cat urls-interesting.txt | httpx -silent -mc 200,401,403 -o urls-alive.txtExtracting endpoints and secrets from JavaScript
Section titled “Extracting endpoints and secrets from JavaScript”Single-page apps ship their API routes, parameter names, and sometimes leaked keys inside JS bundles. Two moves:
Collect the JS files from your crawl and archives, then fetch them:
grep -Eo 'https?://[^ ]+\.js' urls-all.txt | sort -u > js-urls.txtmkdir -p js && while read -r u; do fn="js/$(echo "$u" | md5sum | cut -c1-12).js" curl -sS "$u" -o "$fn" sleep 1 # be politedone < js-urls.txtGrep them for endpoints and obvious secrets. This is the manual core of what LinkFinder automates — LinkFinder uses regexes to pull endpoint-like strings out of JS; the same idea in grep:
# Endpoint-like paths and full URLs inside the bundles:grep -rhoE '"(/[a-zA-Z0-9_./-]+)"' js/ | sort -u | head -50grep -rhoE 'https?://[a-zA-Z0-9./?=_%:-]+' js/ | sort -u
# Obvious key patterns (verify before trusting; do NOT use any live key found):grep -rhoE '(api[_-]?key|secret|token)["'\'' :=]+[A-Za-z0-9_\-]{16,}' js/ | sort -uTemplated scanning with nuclei
Section titled “Templated scanning with nuclei”nuclei runs a large library of YAML templates — checks for known CVEs, exposures, misconfigurations, default credentials, and information leaks — against your target list. It’s fast, community-maintained, and low-false-positive when tuned.
nuclei -update-templates # refresh the template library first
# Scan your triaged live hosts:nuclei -l http-live.txt \ -severity medium,high,critical \ -rl 20 \ -o nuclei-out.txt# -l: list of URLs -severity: focus on what matters -rl: rate limit (req/sec)Target specific categories with tags, and save structured output for triage:
nuclei -l http-live.txt -tags exposure,misconfig,cve -jsonl -o nuclei.jsonljq -r 'select(.info.severity=="high" or .info.severity=="critical") | "\(.info.severity)\t\(.template-id)\t\(.matched-at)"' nuclei.jsonlOrganizing results
Section titled “Organizing results”A recon session produces many files; a flat, consistent layout keeps them usable and your eventual report traceable.
~/bounty/example/recon/├── roots.txt # in-scope roots (your allow-list)├── subs-all.txt # merged subdomain candidates├── live-names.txt # resolved, alive├── http-live.json # httpx probe results (status/title/tech)├── urls-all.txt # gau + waybackurls, deduped├── js/ # downloaded JS bundles├── nuclei.jsonl # templated scan hits└── notes.log # timestamps, scope version, decisionsDe-dup aggressively (sort -u), keep raw tool output alongside your filtered views, and re-run the pipeline periodically — scope and infrastructure change, and new subdomains appear.
The end-to-end pipeline
Section titled “The end-to-end pipeline”Chained together, throttled, and kept inside scope:
#!/usr/bin/env bashset -euo pipefailcd ~/bounty/example/recon
# 1) Enumerate subdomains (passive) from the in-scope rootssubfinder -dL roots.txt -all -silent -o subs-subfinder.txtamass enum -passive -df roots.txt -o subs-amass.txtcat subs-*.txt | sort -u > subs-all.txt
# 2) Resolve, then probe what's alivednsx -l subs-all.txt -silent -a | sort -u > live-names.txthttpx -l live-names.txt -sc -title -td -follow-redirects -silent -json -o http-live.jsonjq -r '.url' http-live.json > http-live.txt
# 3) Historical URLs, filtered to interesting shapescat live-names.txt | gau --threads 5 | sort -u > urls-all.txtgrep -E '\?|/api/|/graphql|/v[0-9]/' urls-all.txt | sort -u > urls-interesting.txt
# 4) Templated scan of live hosts (rate-limited, safe tags)nuclei -l http-live.txt -tags exposure,misconfig,cve -severity medium,high,critical \ -rl 20 -jsonl -o nuclei.jsonl
echo "Recon complete. Review http-live.json, urls-interesting.txt, nuclei.jsonl."From here you pivot into targeted testing: pick a live host, map its endpoints, and apply the REST and GraphQL methodologies — always within the scope you wrote down at the start.
Key points
Section titled “Key points”- Recon converts scope into a concrete target map: subdomains → live hosts → tech → endpoints.
- subfinder/amass/assetfinder enumerate subdomains (prefer passive); dnsx resolves and httpx probes status/title/tech.
- ffuf and feroxbuster discover content with SecLists wordlists; gau/waybackurls recover historical URLs; grep (LinkFinder’s idea) mines JS for endpoints and secrets.
- nuclei runs templated checks — keep
-rllow, use safe tags, and verify every hit by hand. - Discipline throughout: allow-list roots, throttle everything, organize output, and never enumerate or scan outside authorized scope.