Skip to content
Book a test

home / research

// writeup · recon · bug-bounty · methodology

Recon Is Where Bugs Are Found

Nov 4, 2026 · @sdx · 8 min read

Most bugs aren’t hiding behind clever payloads. They sit on a forgotten subdomain, an old API version, or an endpoint mentioned once in a JavaScript file. Whoever maps the attack surface best usually finds them first.

This post walks through the recon pipeline we use at SecureDX on pentests and bug bounty programs: five stages, the tools for each, and what to look at once the output lands. Every command uses example.com. Run them only against targets you are authorized to test.

Rule zero: scope before tools

Recon starts with the program’s rules, not with a terminal. Passive recon only reads public data, but probing, crawling and fuzzing all send traffic to the target.

  • Read the scope line by line. A wildcard like *.example.com covers subdomains; a single host does not. Third-party services (Zendesk, Shopify, a CDN) are usually out of scope even when they sit on the target’s domain.
  • Respect rate limits and banned tests. Many programs cap requests per second and ban denial of service, brute force and heavy scanning. Set rate limits on every tool.
  • Identify yourself when asked. Some programs want a custom header or a registered IP. Add it to your tool configs once.
  • Pentests need a signed authorization letter naming targets, dates and test types. Without one, active recon is unauthorized access.

Keep a scope file next to your output and filter every list against it before testing anything.

The pipeline at a glance

  1. 01Subdomainssubfinder · crt.sh
  2. 02Alivednsx · httpx · naabu
  3. 03URLskatana · gau
  4. 04JavaScriptendpoints · keys
  5. 05Fuzz & quick winsffuf · nuclei
  6. →Manual testingwhere bugs are found

Each stage feeds the next, and every file it produces is a list of leads, not findings. The bugs come from the manual testing at the end.

Stage 1: Find every subdomain

The goal is the widest list of names the company owns, gathered without touching its servers. Use several sources, because each one sees different hosts.

# Passive sources: certificate logs, DNS datasets, search engines
subfinder -d example.com -all -silent -o subs_subfinder.txt

# Certificate transparency directly from crt.sh
curl -s "https://crt.sh/?q=%25.example.com&output=json" \
  | jq -r '.[].name_value' | sed 's/\*\.//g' > subs_crtsh.txt

# Merge and de-duplicate
cat subs_*.txt | sort -u > subs_all.txt

What to watch for in the list:

  • Environment names like dev, staging, uat, test and old. They often run older code with weaker controls.
  • Internal tools such as jenkins, grafana, kibana, vpn and admin.
  • Acquisitions and other root domains. Check the company’s other brands in WHOIS records, certificates and the footer of its website, then repeat this stage for each in-scope root.

Once passive sources run dry, brute-force names with a wordlist (puredns bruteforce) and generate permutations of what you already found (dev-api, api-v2). Only do this where the program allows active DNS queries.

Stage 2: Keep what’s alive

A long subdomain list is mostly dead names. This stage keeps what resolves and answers, and records what each host runs.

# Which names resolve, and to which IPs
dnsx -l subs_all.txt -a -resp -silent -o resolved.txt

# Which hosts serve web: status, title, tech stack
httpx -l subs_all.txt -sc -title -td -ip -silent \
  -rate-limit 50 -o live.txt
cut -d ' ' -f1 live.txt > live_urls.txt   # bare URLs for the next stages

# Common ports beyond 80 and 443 (only if the program allows port scans)
naabu -list subs_all.txt -top-ports 1000 -rate 200 -silent -o ports.txt

Sort live.txt before you test anything:

  • Status 401 and 403 pages often guard admin panels. Try other paths and HTTP methods later; the host itself is worth noting.
  • Unusual titles like “Index of /”, default server pages or login screens for internal tools.
  • Old or rare tech in the fingerprint, such as an outdated CMS or framework version you can check against known CVEs.
  • Non-web ports such as 22, 3306, 6379 or 9200. An exposed database or Elasticsearch instance is a finding on its own.

Stage 3: Collect every URL

Now map what each live host exposes. Combine what a crawler finds today with what archives saw in the past, because removed pages often still work on the server.

# Crawl live hosts, parsing JavaScript for links
katana -list live_urls.txt -jc -d 3 -rl 20 -silent -o urls_crawl.txt

# URLs from archives: Wayback Machine, Common Crawl, URLScan
gau --subs example.com > urls_history.txt

# Merge, then pull out URLs that carry parameters
cat urls_*.txt | sort -u > urls_all.txt
grep '=' urls_all.txt | sort -u > urls_params.txt

Grep the merged list for patterns that usually pay off:

  • API versions: /api/v1/ next to /api/v2/. The older version may lack checks the newer one has.
  • IDs in paths and parameters: user_id=, account=, /orders/1042. These are where IDOR bugs live.
  • URL parameters: url=, redirect=, next=, callback=. Candidates for open redirect and SSRF.
  • Files: .json, .bak, .sql, .zip, .log, .env. Archived backups and exports sometimes still download.

Stage 4: Read the JavaScript

Modern front ends ship their whole API map to the browser. JavaScript files list endpoints the UI never links to, and sometimes keys that were never meant to leave the build server.

# Collect JS file URLs and download them
grep -iE '\.js(\?|$)' urls_all.txt | sort -u > js_urls.txt
mkdir -p js && while read -r u; do
  curl -s "$u" -o "js/$(echo "$u" | md5sum | cut -c1-12).js"
done < js_urls.txt

# Endpoints and paths referenced in the code
grep -rhoE '"/(api|v[0-9]|graphql|internal|admin)[^"]*"' js/ | sort -u > js_endpoints.txt

# Likely secrets: then verify each by hand
grep -rhoE '(AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{35}|sk_live_[0-9a-zA-Z]{24,})' js/ | sort -u

What to do with the results:

  • Hidden endpoints: request each one with your normal user’s session. Admin and internal routes that answer to a low-privilege user are access control bugs.
  • Source maps: if app.js.map exists, it rebuilds the original source code, comments included.
  • Keys: a match is not a finding until you prove impact. Many keys are public by design (Firebase web config, Google Maps keys restricted by referrer). Check what the key can actually do, within the program’s rules, before reporting.
  • GraphQL: a /graphql reference is worth checking for introspection and for resolvers that skip authorization.

Stage 5: Fuzz for what isn’t linked, and grab the quick wins

Some paths never appear in links, archives or JavaScript. Fuzzing finds them, and a few targeted checks catch the misconfigurations that take minutes to confirm.

# Content discovery on one interesting host, auto-calibrated against false positives
ffuf -u https://dev.example.com/FUZZ -w sensitive-paths.txt \
  -mc 200,204,301,302,307,401,403 -ac -rate 30 -o ffuf_dev.json

# Subdomain takeover candidates (dangling CNAMEs to deleted cloud services)
nuclei -l live_urls.txt -t http/takeovers/ -rl 20 -silent

# Exposed files that should never be public
nuclei -l live_urls.txt -tags exposure,config -rl 20 -silent

Fuzz deliberately. A short wordlist that fits the target’s stack (.env and /actuator for Spring, wp-config.php.bak for WordPress, /.git/HEAD everywhere) beats a 200,000-line list sprayed at every host.

Quick wins to confirm by hand:

Check Sign of a bug Typical impact
Subdomain takeover CNAME points to a deleted S3 bucket, Heroku app or GitHub Pages site Attacker serves content on the company’s domain
Exposed .git /.git/HEAD returns ref: refs/heads/... Full source code, often with secrets in history
Environment files /.env returns DB_PASSWORD= or API keys Database or cloud account access
Debug and actuator endpoints /actuator/env, /debug, stack traces Config values, internal hostnames
Open cloud storage Bucket lists files without credentials Customer data or backups exposed
Exposed API docs /swagger.json, /openapi.json A full map of endpoints to test for access control

Nuclei results are leads, not findings. Reproduce every hit manually and show real impact before you write the report.

From recon to bugs

Recon output is a to-do list, not a result. Rank it, then test by hand, starting where impact is highest and other hunters are least likely to have looked.

  1. Hosts other hunters miss: newly issued certificates, acquisitions, dev and staging environments.
  2. Endpoints that handle other users’ data: anything with an ID in the path or a parameter. Test with two accounts of your own and swap the IDs between them.
  3. Old API versions and hidden routes found in JavaScript, tested with a low-privilege session.
  4. Features that fetch URLs: webhooks, PDF generators, image importers and link previews, for SSRF.
  5. Login, signup and password reset flows, for account enumeration, token reuse and logic flaws.

Two habits matter more than any tool:

  • Run recon again. Companies ship new hosts and endpoints every week. Diff each run against the last one and test what changed first; new code has had the least testing.
  • Read the output. Automation finds candidates. The bug is usually found by the person who noticed that one host returns a different header, or that /api/v1/ still answers when /api/v2/ refuses.

SDX Society is SecureDX’s invite-only community of bug bounty hunters. If you hunt this way and want access to private programs, request an invite at securedx.in/society.

Next: Part 2: Advanced Recon for 2027 covers organisation mapping, CDN origins, the AI attack surface and continuous recon.

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Keep it on topic and don't post secrets or other people's data.