Skip to content

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.

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.

Terminal window
mkdir -p ~/bounty/example/recon && cd ~/bounty/example/recon
cat > roots.txt <<'EOF'
example.com
example-cdn.net
EOF
# Note the date and the policy version you read, in your session log.
date -u; echo 'scope read from HackerOne policy vX' >> notes.log

As 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.

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):

Terminal window
subfinder -dL roots.txt -all -silent -o subs-subfinder.txt
# -dL: file of root domains -all: use every source -silent: just the results

assetfinder (quick passive pull):

Terminal window
assetfinder --subs-only example.com > subs-assetfinder.txt

amass (deeper; supports passive and active). Passive keeps you quiet:

Terminal window
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.txt

Merge and de-duplicate everything into one candidate list:

Terminal window
cat subs-*.txt | sort -u > subs-all.txt
wc -l subs-all.txt

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:

Terminal window
dnsx -l subs-all.txt -silent -a -resp -o resolved.txt
# -a: A records -resp: show the resolved value keeps only names that resolve
cut -d' ' -f1 resolved.txt | sort -u > live-names.txt

httpx 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.

Terminal window
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.json
jq -r 'select(.status_code==200) | "\(.url)\t\(.title)"' http-live.json

Read 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.

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):

Terminal window
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 json

feroxbuster does recursive content discovery with sensible defaults — it follows discovered directories down automatically:

Terminal window
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 redirects

Wordlists — 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.

Terminal window
git clone --depth 1 https://github.com/danielmiessler/SecLists /usr/share/seclists

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”):

Terminal window
gau --subs example.com > urls-gau.txt
# or feed it your live hosts:
cat live-names.txt | gau --threads 5 > urls-gau.txt

waybackurls (Wayback Machine specifically):

Terminal window
cat live-names.txt | waybackurls > urls-wayback.txt

Merge, then mine the combined list for interesting shapes — endpoints with parameters, API paths, and file types worth a look:

Terminal window
cat urls-*.txt | sort -u > urls-all.txt
grep -E '\?|/api/|/graphql|/v[0-9]/' urls-all.txt | sort -u > urls-interesting.txt
grep -Eo 'https?://[^ ]+\.(json|js|xml|bak|config|env)' urls-all.txt | sort -u > urls-files.txt

Confirm which archived URLs still respond before you build on them — many are dead:

Terminal window
cat urls-interesting.txt | httpx -silent -mc 200,401,403 -o urls-alive.txt

Extracting 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:

Terminal window
grep -Eo 'https?://[^ ]+\.js' urls-all.txt | sort -u > js-urls.txt
mkdir -p js && while read -r u; do
fn="js/$(echo "$u" | md5sum | cut -c1-12).js"
curl -sS "$u" -o "$fn"
sleep 1 # be polite
done < js-urls.txt

Grep 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:

Terminal window
# Endpoint-like paths and full URLs inside the bundles:
grep -rhoE '"(/[a-zA-Z0-9_./-]+)"' js/ | sort -u | head -50
grep -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 -u

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.

Terminal window
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:

Terminal window
nuclei -l http-live.txt -tags exposure,misconfig,cve -jsonl -o nuclei.jsonl
jq -r 'select(.info.severity=="high" or .info.severity=="critical")
| "\(.info.severity)\t\(.template-id)\t\(.matched-at)"' nuclei.jsonl

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, decisions

De-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.

Chained together, throttled, and kept inside scope:

#!/usr/bin/env bash
set -euo pipefail
cd ~/bounty/example/recon
# 1) Enumerate subdomains (passive) from the in-scope roots
subfinder -dL roots.txt -all -silent -o subs-subfinder.txt
amass enum -passive -df roots.txt -o subs-amass.txt
cat subs-*.txt | sort -u > subs-all.txt
# 2) Resolve, then probe what's alive
dnsx -l subs-all.txt -silent -a | sort -u > live-names.txt
httpx -l live-names.txt -sc -title -td -follow-redirects -silent -json -o http-live.json
jq -r '.url' http-live.json > http-live.txt
# 3) Historical URLs, filtered to interesting shapes
cat live-names.txt | gau --threads 5 | sort -u > urls-all.txt
grep -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.

  • 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 -rl low, 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.