August 7, 2026
How to Build a Recon Workflow That Actually Finds Bugs (Not Just Data)
10,000 subdomains is not recon. It’s noise. Here is the pipeline that turns raw output into testable findings.

By Abhishek meena
8 min read
30-second version
Most hunters do recon to collect data. The hunters who get paid do recon to build a testable shortlist. The difference is a pipeline that collapses 10,000 subdomains into a handful of targets with a hypothesis attached to each one. Here are the 5 stages that do it: collect with intent, collapse the noise, fingerprint the technology, surface the attack surface, and test with a hypothesis. Every stage has tools you can copy.
I used to run recon like a collector.
subfinder on the root domain. amass for good measure. httpx to check what's alive. nuclei on everything that responded. Save the output. Move to the next programme.
A month later I would have 40,000 subdomains across 10 targets, and a growing suspicion that I was doing something deeply wrong.
The suspicion was right.
Recon is not data collection. It's compression.
The goal is not to know every subdomain your target owns. The goal is to hand yourself a shortlist of testable targets, each one with a reason attached. If your recon output is a folder, it failed. If it's a list of 12 targets with hypotheses, it worked.
Here is the pipeline that turned my recon around. Five stages, in order.
Stage 1 — Collect with intent
Most hunters run the same 15 tools on every target, in the same order, with no idea what they are looking for.
Before you run a single command, decide what this hunt is for.
- Hunting subdomain takeovers? You need a different pipeline than an IDOR hunt.
- Hunting authentication bugs? You care about the apps, not the static assets.
- Hunting business logic? You need to understand the product, not enumerate hosts.
The goal changes the pipeline. A takeover hunt lives or dies on DNS records. An IDOR hunt lives or dies on API enumeration and JS analysis. If you run the same pipeline for both, half your output is irrelevant before you start.
The collection toolset, by goal:
# Passive subdomain discovery (start here, every hunt)
subfinder -d target.com -all -silent -o subs_raw.txt
# Add passive sources that catch what subfinder misses
assetfinder --subs-only target.com | sort -u >> subs_raw.txt
# Certificate transparency logs (catches staging/dev/internal names)
curl -s "https://crt.sh/?q=%25.target.com&output=json" | jq -r '.[].name_value' | sort -u >> subs_raw.txt
# Historical URLs from web archives (old endpoints still live)
waybackurls target.com > urls_wayback.txt
gau --subs target.com > urls_gau.txt
# For a feature/API-focused hunt, go straight to the JS
katana -u https://target.com -jc -d 2 -o js_files.txt# Passive subdomain discovery (start here, every hunt)
subfinder -d target.com -all -silent -o subs_raw.txt
# Add passive sources that catch what subfinder misses
assetfinder --subs-only target.com | sort -u >> subs_raw.txt
# Certificate transparency logs (catches staging/dev/internal names)
curl -s "https://crt.sh/?q=%25.target.com&output=json" | jq -r '.[].name_value' | sort -u >> subs_raw.txt
# Historical URLs from web archives (old endpoints still live)
waybackurls target.com > urls_wayback.txt
gau --subs target.com > urls_gau.txt
# For a feature/API-focused hunt, go straight to the JS
katana -u https://target.com -jc -d 2 -o js_files.txtThe intent stage is 10 minutes. It changes everything after it.
Stage 2 — Collapse
This is where most hunters fail. They collect, and then they stop.
Raw recon output is not useful. It is the raw material for useful. The difference is filtering.
Collapse rule: keep only what you would actually test.
# Dedupe and normalise
sort -u subs_raw.txt -o subs_clean.txt
# Find every open port (not just 80/443)
naabu -l subs_clean.txt -top-ports 1000 -o ports_naabu.txt
# Keep only live hosts with a web service (the only ones you can test)
httpx -l subs_clean.txt -ports 80,443,8080,8443,3000 \
-status-code -tech-detect -follow-redirects -title \
-o live_hosts.txt
# Split by what you might actually hunt on
httpx -l subs_clean.txt -status-code -tech-detect \
-mc 200,301,302,401,403 -o web_apps.txt
# Dedupe URLs that point to the same page
cat urls_wayback.txt urls_gau.txt | uro | sort -u > urls_clean.txt# Dedupe and normalise
sort -u subs_raw.txt -o subs_clean.txt
# Find every open port (not just 80/443)
naabu -l subs_clean.txt -top-ports 1000 -o ports_naabu.txt
# Keep only live hosts with a web service (the only ones you can test)
httpx -l subs_clean.txt -ports 80,443,8080,8443,3000 \
-status-code -tech-detect -follow-redirects -title \
-o live_hosts.txt
# Split by what you might actually hunt on
httpx -l subs_clean.txt -status-code -tech-detect \
-mc 200,301,302,401,403 -o web_apps.txt
# Dedupe URLs that point to the same page
cat urls_wayback.txt urls_gau.txt | uro | sort -u > urls_clean.txthttpx with -status-code and -tech-detect is not a scanner. It is a sieve. It turns 10,000 hosts into 200 live ones, then into 40 web apps worth your time.
naabu matters more than most hunters think. The app on port 8443 is a different app from the one on 443, and it is almost never in the scope list. Find the ports, then probe them with httpx.
uro is the unsung hero of URL compression. It collapses thousands of archived URLs down to the unique endpoints, which is exactly what you want before you grep for parameters.
If a host did not respond, drop it. If it responded with a default page, note it. If it responds with an application, that is your shortlist.
Stage 3 — Fingerprint the technology
This is the stage most guides skip, and it is where the leverage is.
Technology tells you what to look for. A Grafana 8.2 instance has a public path traversal. A Laravel app has specific debug routes. A Spring Boot app leaks through /actuator. If you know the stack, you know which bugs are even possible.
Never guess a stack. Fingerprint it with tools.
# httpx already detects tech on the way through (headers, body, favicon)
httpx -l web_apps.txt -tech-detect -o fingerprinted.txt
# whatweb is slower but deeper (headers, cookies, JS markers)
whatweb -i web_apps.txt --log-json=whatweb.json
# fingerprintx identifies the SERVICE on any port (not just web)
fingerprintx -l ports_naabu.txt -o service_fingerprints.txt
# Favicon hashing: the same icon = the same product across hosts
httpx -l web_apps.txt -favicon -o favicons.txt
# Then search that hash on Shodan for every host sharing it:
# https://www.shodan.io/search?query=http.favicon.hash:HASH
# Nuclei has dedicated technology-detection templates
nuclei -l web_apps.txt -tags tech -silent -o tech_identified.txt# httpx already detects tech on the way through (headers, body, favicon)
httpx -l web_apps.txt -tech-detect -o fingerprinted.txt
# whatweb is slower but deeper (headers, cookies, JS markers)
whatweb -i web_apps.txt --log-json=whatweb.json
# fingerprintx identifies the SERVICE on any port (not just web)
fingerprintx -l ports_naabu.txt -o service_fingerprints.txt
# Favicon hashing: the same icon = the same product across hosts
httpx -l web_apps.txt -favicon -o favicons.txt
# Then search that hash on Shodan for every host sharing it:
# https://www.shodan.io/search?query=http.favicon.hash:HASH
# Nuclei has dedicated technology-detection templates
nuclei -l web_apps.txt -tags tech -silent -o tech_identified.txtAnd the manual checks that tools miss:
- Response headers.
Server:,X-Powered-By:,X-Generator:reveal the stack in one line. - Cookies. Session cookie naming conventions (
PHPSESSID,JSESSIONID,ASP.NET_SessionId) identify the framework instantly. - Error pages. Trigger a 404 or 500 and read the footer. Framework names show up there.
- JS framework markers.
window.__NEXT_DATA__means Next.js.__NUXT__means Nuxt. React app shells have a specific DOM shape. - Favicon reuse. Companies run many apps on the same internal framework. Fingerprint one favicon, search the hash, find the rest.
The output is not "a host with a website." It is:
target.com Next.js 14 - check SSR data leaks, API routes
admin.target.com Grafana 8.2 - known path traversal CVE
api.target.com FastAPI (Python) - enumerate OpenAPI schema
status.target.com S3 static site - check for takeover, then drop
legacy.target.com Laravel 7 - debug mode? /_ignition/health-checktarget.com Next.js 14 - check SSR data leaks, API routes
admin.target.com Grafana 8.2 - known path traversal CVE
api.target.com FastAPI (Python) - enumerate OpenAPI schema
status.target.com S3 static site - check for takeover, then drop
legacy.target.com Laravel 7 - debug mode? /_ignition/health-checkTechnology changes the priority. Known vulnerable versions get tested first. Unknown stacks get fingerprinted before a single payload is fired. And the moment you know the stack, you know the bug classes that are actually possible, which is the entire point of recon.
Stage 4 — Surface the attack surface
Now you know what's alive and what it runs. The next step is deciding which hosts deserve your attention, and why — and finding the surfaces that don't show up in a subdomain list.
Ask three questions per host:
- Is this an app or a static asset? Static assets get one pass. Apps get a full review.
- What does it do? A dev dashboard is worth more than a marketing page. A file upload page is worth more than a docs page.
- Is there an API underneath? Most modern apps are a thin UI over an API, and the API is where the bugs live.
Finding the surfaces nobody lists:
# Extract API endpoints from JS files (the goldmine)
cat js_files.txt | while read url; do curl -s "$url"; done \
| grep -oE '(/api/[a-zA-Z0-9_./{}?-]+)' | sort -u > api_endpoints.txt
# Or let linkfinder do it across all JS
linkfinder -i https://target.com/js/app.js -o endpoints.html
linkfinder -i https://target.com -d -o all_endpoints.html
# Look for exposed API documentation
echo "target.com/api-docs target.com/swagger target.com/openapi.json target.com/_ignition/health-check" | while read u; do
code=$(curl -s -o /dev/null -w "%{http_code}" "$u")
[ "$code" != "404" ] && echo "$u -> $code"
done
# Git history leaks endpoints and secrets
gitleaks detect --source . --no-git --report-path gitleaks.json# Extract API endpoints from JS files (the goldmine)
cat js_files.txt | while read url; do curl -s "$url"; done \
| grep -oE '(/api/[a-zA-Z0-9_./{}?-]+)' | sort -u > api_endpoints.txt
# Or let linkfinder do it across all JS
linkfinder -i https://target.com/js/app.js -o endpoints.html
linkfinder -i https://target.com -d -o all_endpoints.html
# Look for exposed API documentation
echo "target.com/api-docs target.com/swagger target.com/openapi.json target.com/_ignition/health-check" | while read u; do
code=$(curl -s -o /dev/null -w "%{http_code}" "$u")
[ "$code" != "404" ] && echo "$u -> $code"
done
# Git history leaks endpoints and secrets
gitleaks detect --source . --no-git --report-path gitleaks.jsonThe 4 surfaces worth hunting that most hunters skip:
- API documentation. Swagger, OpenAPI, and Postman collections expose the full route map. If it returns 200, you just found a feature nobody else tests.
- Old API versions. Inon Shkedy's tip: if you see
/api/v3/login, check/api/v1/login. Older versions have weaker auth and no rate limiting. - Admin and debug routes.
/actuator,/_ignition,/debug/pprof,/server-statusare common and often unauthenticated. - The changelog. The feature that shipped last Tuesday has been tested by zero hunters. The homepage has been tested by 500. Read release notes and app store updates, then test what's new.
The output of Stage 4 is a shortlist, not a list. And the column that matters most is the last one: the hypothesis.
Stage 5 — Test with a hypothesis
This is the stage that separates collectors from hunters.
Every target on your shortlist gets a hypothesis: "I am testing this because X, and I am looking for Y."
- "admin.target.com runs Grafana 8.2, which has a public unauthenticated path traversal. I am testing for that."
- "api.target.com has a
/users/{id}endpoint. I am testing for IDOR." - "target.com ships weekly. I am testing the changelog for new endpoints that nobody else has tested yet."
A hypothesis changes how you test. You stop firing generic payloads and start testing a specific claim. When it turns out wrong, you learn something. When it's right, you found a bug nobody else was looking for, because they were scanning, not hypothesizing.
Recon without a hypothesis produces scans. Scans produce false positives. Hypotheses produce findings.
The 3 failure modes that break every workflow
Even with the pipeline, most hunters sabotage themselves. Three patterns, all fixable.
Failure mode 1 — Data hoarding
You collect 50,000 URLs, save them, and never look at them again. The folder becomes a graveyard.
The fix: Never save recon output you have not filtered. If the output does not survive Stage 2, delete it. A shortlist you look at is worth more than a hard drive of data you don't.
Failure mode 2 — Re-testing the same surface
Every hunt, you run the same pipeline against the same targets and find the same things other hunters already found.
The fix: Track what you tested. A simple text file per programme, listing endpoints tested, params fuzzed, and access control checks attempted. Next month, you skip the work you already did and go straight to what changed.
This is the single most under-rated habit in bug bounty. Most hunters start from zero every time. That is the real reason they duplicate themselves — not because the bug was already reported, but because they re-test the same surface and find the same things everyone else found.
Failure mode 3 — Confusing volume with coverage
More subdomains feels like progress. It isn't. Coverage is not how many hosts you enumerated. It's how many distinct features, APIs, and trust boundaries you actually tested.
A hunter who deeply tests 5 features on one app has more coverage than a hunter who scans 2,000 subdomains. Volume is comfort. Coverage is results.
A worked example (sanitized)
Here is what the pipeline actually produced on a real engagement, with numbers rounded and details removed.
Stage 1 — Collect: 3,400 subdomains, 18,000 archived URLs (subfinder + crt.sh + wayback)
Stage 2 — Collapse: 128 live hosts, 11 non-standard ports (naabu + httpx)
Stage 3 — Fingerprint: 24 apps identified, 3 on known-vulnerable versions (httpx + whatweb)
Stage 4 — Surface: 12 testable targets, 2 exposed API docs (linkfinder + swagger check)
Stage 5 — Test: 3 findings from the shortlist
1 IDOR (user profile enumeration via API)
1 exposed staging env with debug endpoints
1 stale subdomain takeoverStage 1 — Collect: 3,400 subdomains, 18,000 archived URLs (subfinder + crt.sh + wayback)
Stage 2 — Collapse: 128 live hosts, 11 non-standard ports (naabu + httpx)
Stage 3 — Fingerprint: 24 apps identified, 3 on known-vulnerable versions (httpx + whatweb)
Stage 4 — Surface: 12 testable targets, 2 exposed API docs (linkfinder + swagger check)
Stage 5 — Test: 3 findings from the shortlist
1 IDOR (user profile enumeration via API)
1 exposed staging env with debug endpoints
1 stale subdomain takeover3,400 hosts became 12 targets. 12 targets produced 3 findings. Most of the pipeline was deletion.
The findings did not come from scanning more. They came from filtering, fingerprinting, and surfacing better.
The full pipeline, as one script
Here is the whole thing in one place. Copy it, adapt the target, run it.
#!/bin/bash
# recon.sh — collect, collapse, fingerprint, surface
TARGET=$1
mkdir -p recon && cd recon
# 1. Collect
subfinder -d $TARGET -all -silent -o subs_raw.txt
assetfinder --subs-only $TARGET | sort -u >> subs_raw.txt
curl -s "https://crt.sh/?q=%25.$TARGET&output=json" | jq -r '.[].name_value' | sort -u >> subs_raw.txt
sort -u subs_raw.txt -o subs_clean.txt
# 2. Collapse
naabu -l subs_clean.txt -top-ports 1000 -o ports_naabu.txt
httpx -l subs_clean.txt -status-code -tech-detect -follow-redirects -title -o live_hosts.txt
# 3. Fingerprint
httpx -l live_hosts.txt -tech-detect -o fingerprinted.txt
whatweb -i live_hosts.txt --log-json=whatweb.json
# 4. Surface (manual review required from here)
cat live_hosts.txt | cut -d' ' -f1 | while read u; do
curl -s -o /dev/null -w "%{http_code} $u/api-docs\n" "$u/api-docs"
done | grep -v 404 > api_docs.txt
echo "Done. Review live_hosts.txt and api_docs.txt."#!/bin/bash
# recon.sh — collect, collapse, fingerprint, surface
TARGET=$1
mkdir -p recon && cd recon
# 1. Collect
subfinder -d $TARGET -all -silent -o subs_raw.txt
assetfinder --subs-only $TARGET | sort -u >> subs_raw.txt
curl -s "https://crt.sh/?q=%25.$TARGET&output=json" | jq -r '.[].name_value' | sort -u >> subs_raw.txt
sort -u subs_raw.txt -o subs_clean.txt
# 2. Collapse
naabu -l subs_clean.txt -top-ports 1000 -o ports_naabu.txt
httpx -l subs_clean.txt -status-code -tech-detect -follow-redirects -title -o live_hosts.txt
# 3. Fingerprint
httpx -l live_hosts.txt -tech-detect -o fingerprinted.txt
whatweb -i live_hosts.txt --log-json=whatweb.json
# 4. Surface (manual review required from here)
cat live_hosts.txt | cut -d' ' -f1 | while read u; do
curl -s -o /dev/null -w "%{http_code} $u/api-docs\n" "$u/api-docs"
done | grep -v 404 > api_docs.txt
echo "Done. Review live_hosts.txt and api_docs.txt."This is not a finished scanner. It is a starting point. The stages after "Surface" require a human, because hypotheses require judgment.
The reframe
Most hunters treat recon as the boring part before the real work.
It isn't. Recon is where the real work happens. The difference between a hunter who finds bugs and a hunter who gets duplicates is not exploitation skill. It is how they decide where to point the exploitation skill.
The tools change every year. The pipeline doesn't. Collect with intent. Collapse ruthlessly. Fingerprint the stack. Surface what matters. Test with a hypothesis. Track what you tested so you never repeat yourself.
If your recon output is a folder, you are collecting data.
If it's a shortlist with hypotheses, you are hunting.