| name | web2-recon |
| description | Web2 recon pipeline — subdomain enumeration (subfinder, Chaos API, assetfinder), live host discovery (dnsx, httpx), URL crawling (katana, waybackurls, gau), directory fuzzing (ffuf), JS analysis (LinkFinder, SecretFinder), continuous monitoring (new subdomain alerts, JS change detection, GitHub commit watch). Use when starting recon on any web2 target or when asked about asset discovery, subdomain enum, or attack surface mapping. |
| version | 1.1.0 |
| revision_date | "2026-07-25T00:00:00.000Z" |
| license | MIT |
| category | redteam |
| tags | ["web2","recon","redteam"] |
WEB2 RECON PIPELINE
Full asset discovery from nothing to a prioritized URL list ready for hunting.
SETUP (one-time)
export CHAOS_API_KEY="your-key-here"
echo 'export CHAOS_API_KEY="your-key-here"' >> ~/.zshrc
nuclei -update-templates
mkdir -p ~/.config/subfinder
cat > ~/.config/subfinder/config.yaml << 'EOF'
virustotal: [YOUR_VT_KEY]
securitytrails: [YOUR_ST_KEY]
censys_apiid: YOUR_CENSYS_ID
censys_secret: YOUR_CENSYS_SECRET
shodan: [YOUR_SHODAN_KEY]
EOF
which subfinder httpx dnsx nuclei katana waybackurls gau dalfox ffuf anew gf interactsh-client
THE 5-MINUTE RULE
If a target shows nothing interesting after 5 minutes of recon, move on. Don't burn hours on dead surface.
5-minute kill signals:
- All subdomains return 403 or static marketing pages
- No API endpoints visible in URLs
- No JavaScript bundles with interesting endpoint paths
- nuclei returns 0 medium/high findings
- No forms, no authentication, no user data
STANDARD RECON PIPELINE
Pre-Hunt: Always Run First
TARGET="target.com"
curl --max-time 30 --connect-timeout 10 -s "https://crt.sh/?q=%.${TARGET}&output=json" \
| jq -r '.[].name_value' \
| sed 's/\*\.//g' \
| sort -u > /tmp/subs.txt
echo "[+] crt.sh: $(wc -l < /tmp/subs.txt) subdomains"
curl --max-time 30 --connect-timeout 10 -s "https://dns.projectdiscovery.io/dns/$TARGET/subdomains" \
-H "Authorization: $CHAOS_API_KEY" \
| jq -r '.[]' >> /tmp/subs.txt
echo "[+] Chaos returned $(wc -l < /tmp/subs.txt) subdomains"
subfinder -d $TARGET -silent | anew /tmp/subs.txt
assetfinder --subs-only $TARGET | anew /tmp/subs.txt
echo "[+] Total subdomains after all sources: $(wc -l < /tmp/subs.txt)"
cat /tmp/subs.txt | dnsx -silent | httpx -silent -status-code -title -tech-detect | tee /tmp/live.txt
echo "[+] Live hosts: $(wc -l < /tmp/live.txt)"
cat /tmp/live.txt | awk '{print $1}' | katana -d 3 -jc -kf all -silent | anew /tmp/urls.txt
echo $TARGET | waybackurls | anew /tmp/urls.txt
gau $TARGET --subs | anew /tmp/urls.txt
echo "[+] Total URLs: "
nuclei -l /tmp/live.txt -t ~/nuclei-templates/ -severity critical,high,medium -o /tmp/nuclei.txt
Output to Organized Directory
TARGET="target.com"
RECON_DIR="recon/$TARGET"
mkdir -p $RECON_DIR
/tmp/subs.txt → $RECON_DIR/subdomains.txt
/tmp/live.txt → $RECON_DIR/live-hosts.txt
/tmp/urls.txt → $RECON_DIR/urls.txt
/tmp/nuclei.txt → $RECON_DIR/nuclei.txt
ATTACK SURFACE TRIAGE
Find Interesting Targets in URL List
cat /tmp/urls.txt | grep -E "[?&](id|user|file|path|url|redirect|next|src|token|key|api_key)=" | tee /tmp/interesting-params.txt
cat /tmp/urls.txt | grep -E "/api/|/v1/|/v2/|/v3/|/graphql|/rest/|/gql" | tee /tmp/api-endpoints.txt
cat /tmp/urls.txt | grep -E "upload|file|attachment|document|image|avatar|photo|media" | tee /tmp/uploads.txt
cat /tmp/urls.txt | grep -E "/admin|/internal|/debug|/test|/staging|/dev|/management|/console" | tee /tmp/admin-paths.txt
cat /tmp/urls.txt | grep -E "/oauth|/login|/auth|/sso|/saml|/oidc|/callback|/token" | tee /tmp/auth-paths.txt
gf Patterns (Quick Classification)
cat /tmp/urls.txt | gf xss | tee /tmp/xss-candidates.txt
cat /tmp/urls.txt | gf ssrf | tee /tmp/ssrf-candidates.txt
cat /tmp/urls.txt | gf idor | tee /tmp/idor-candidates.txt
cat /tmp/urls.txt | gf sqli | tee /tmp/sqli-candidates.txt
cat /tmp/urls.txt | gf redirect | tee /tmp/redirect-candidates.txt
cat /tmp/urls.txt | gf lfi | tee /tmp/lfi-candidates.txt
cat /tmp/urls.txt | gf rce | tee /tmp/rce-candidates.txt
JS ANALYSIS
SecretFinder (API keys, tokens in JS bundles)
source ~/tools/SecretFinder/.venv/bin/activate
python3 ~/tools/SecretFinder/SecretFinder.py -i "https://target.com/static/js/main.js" -o cli
cat /tmp/urls.txt | grep "\.js$" | head -50 | while read url; do
echo "=== $url ==="
python3 ~/tools/SecretFinder/SecretFinder.py -i "$url" -o cli 2>/dev/null
done
deactivate
LinkFinder (Endpoints hidden in JS)
source ~/tools/LinkFinder/.venv/bin/activate
python3 ~/tools/LinkFinder/linkfinder.py -i "https://target.com/app.js" -o cli
python3 ~/tools/LinkFinder/linkfinder.py -i "https://target.com" -d -o cli
deactivate
DIRECTORY FUZZING
ffuf — Standard Fuzzing
ffuf -u "https://target.com/FUZZ" \
-w ~/wordlists/common.txt \
-mc 200,201,204,301,302,307,401,403 \
-ac \
-t 40 \
-o /tmp/ffuf-dirs.json
ffuf -u "https://target.com/api/FUZZ" \
-w ~/wordlists/api-endpoints.txt \
-mc 200,201,204,301,302 \
-ac \
-t 20
ffuf -request /tmp/req.txt \
-request-proto https \
-w <(seq 1 10000) \
-fc 404 \
-ac \
-t 10
TARGET SCORING — GO / NO-GO
Score before spending time. Skip if score < 4.
| Criterion | Points |
|---|
| Max bounty >= $5K | +2 |
| Large user base (>100K) or handles money | +2 |
| Program launched < 60 days ago | +2 |
| Complex features: API, OAuth, file upload, GraphQL | +1 |
| Recent code/feature changes (GitHub, changelog) | +1 |
| Private program (less competition) | +1 |
| Tech stack you know | +1 |
| Source code available | +1 |
| Prior disclosed reports to study | +1 |
< 4: Skip
4-5: Only if nothing better available
6-8: Good — spend 1-3 days
>= 9: Excellent — spend up to 1 week
Pre-Dive Hard Kill Signals
- Max bounty < $500 → not worth your time
- All recent reports are N/A or duplicate → hunters saturated it
- Scope is only a static marketing page → no attack surface
- Company < 5 employees with no revenue → won't pay
- Explicitly excludes your planned bug class in rules
TECH STACK DETECTION (2 min)
curl --max-time 30 --connect-timeout 10 -sI https://target.com | grep -iE "server|x-powered-by|x-aspnet|x-runtime|x-generator"
Stack → Primary Bug Class Map
| Stack | Hunt First | Hunt Second |
|---|
| Ruby on Rails | Mass assignment | IDOR (:id routes) |
| Django | IDOR (ModelViewSet, no object perms) | SSTI (mark_safe) |
| Flask | SSTI (render_template_string) | SSRF (requests lib) |
| Laravel | Mass assignment ($fillable) | IDOR (Eloquent, no ownership) |
| Express (Node.js) | Prototype pollution | Path traversal |
| Spring Boot | Actuator endpoints (/actuator/env) | SSTI (Thymeleaf) |
| ASP.NET | ViewState deserialization | Open redirect (ReturnUrl) |
| Next.js | SSRF via Server Actions | Open redirect via redirect() |
| GraphQL | Introspection → auth bypass on mutations | IDOR via node(id:) |
| WordPress | Plugin SQLi | REST API auth bypass |
CONTINUOUS MONITORING SETUP
Set up once per target. Alerts you before other hunters.
New Subdomain Alerts (daily cron)
#!/bin/bash
TARGET="target.com"
KNOWN="/tmp/$TARGET-subs-known.txt"
subfinder -d $TARGET -silent > /tmp/$TARGET-subs-fresh.txt
curl --max-time 30 --connect-timeout 10 -s "https://dns.projectdiscovery.io/dns/$TARGET/subdomains" \
-H "Authorization: $CHAOS_API_KEY" \
| jq -r '.[]' >> /tmp/$TARGET-subs-fresh.txt
NEW=$(comm -23 <(sort /tmp/$TARGET-subs-fresh.txt) <(sort $KNOWN 2>/dev/null))
if [ -n "$NEW" ]; then
echo "NEW SUBDOMAINS: $NEW"
echo "$NEW" >> $KNOWN
fi
GitHub Commit Watch
#!/bin/bash
REPO="TargetOrg/target-app"
LAST_SHA="/tmp/$REPO-last-sha.txt"
CURRENT=$(curl --max-time 30 --connect-timeout 10 -s "https://api.github.com/repos/$REPO/commits?per_page=1" | jq -r '.[0].sha')
KNOWN=$(cat $LAST_SHA 2>/dev/null)
if [ "$CURRENT" != "$KNOWN" ]; then
echo "New commit on $REPO: $CURRENT"
echo $CURRENT > $LAST_SHA
curl --max-time 30 --connect-timeout 10 -s "https://api.github.com/repos/$REPO/commits/$CURRENT" \
| jq -r '.files[].filename' | grep -E "auth|middleware|route|permission|role|admin"
fi
Port Scanning (often skipped — don't skip)
cat /tmp/live.txt | awk '{print $1}' | naabu -port 80,443,8080,8443,3000,4000,5000,8000,8888,9000,9090,9200,6379 -silent | tee /tmp/open-ports.txt
Raw port connectivity test (when naabu unavailable)
for port in 22 80 443 3306 5432 6379 8080 8081 8443 27017; do
timeout 3 bash -c "echo >/dev/tcp/$TARGET/$port" 2>&1 && echo "PORT $port OPEN" || echo "PORT $port CLOSED/FILTERED"
done
SECRET SCANNING IN JS BUNDLES
pip install trufflehog3 2>/dev/null || true
trufflehog filesystem --only-verified recon/$TARGET/ 2>/dev/null
source ~/tools/SecretFinder/.venv/bin/activate
cat /tmp/urls.txt | grep "\\.js$" | head -100 | while read url; do
python3 ~/tools/SecretFinder/SecretFinder.py -i "$url" -o cli 2>/dev/null
done
deactivate
wget -q -r -l 1 -A "*.js" -P /tmp/js-files/ "https://$TARGET" 2>/dev/null
grep -rn "api_key\\|apiKey\\|client_secret\\|access_token\\|private_key\\|AWS_SECRET\\|AKIA" /tmp/js-files/ 2>/dev/null
JS bundle API key extraction (BusyBox-compatible fallback)
BusyBox grep does NOT support -P (PCRE/Perl regex). Use Python3 for regex matching on JS bundles in Alpine containers:
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET/static/js/main.*.js" 2>/dev/null | python3 -c "
import sys, re
content = sys.stdin.read()
for k in re.findall(r'AIza[0-9A-Za-z_-]{35}', content):
print(f'Firebase API Key: {k}')
for m in re.findall(r'(?:apiUrl|apiKey|authDomain|databaseURL|projectId)[\"\']?\s*[:=]\s*[\"]([^\"\']+)[\"]', content):
print(f'Config: {m}')
for u in re.findall(r'https?://[a-zA-Z0-9._-]+\.(?:firebaseio|firestore|googleapis|herokuapp)\.com[^\"\\s,]*', content):
print(f'URL: {u}')
for s in re.findall(r'(?:secret|jwt[_-]?secret|token)[\"\']?\s*[:=]\s*[\"]([a-zA-Z0-9_\-]{16,})[\"]', content):
print(f'Potential secret: {s}')
" 2>/dev/null
main_js=$(curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET:8080/" 2>/dev/null | grep -Eo 'src="([^"]+\.js)"' | sed 's/src="//;s/"//' | head -1)
[ -n "$main_js" ] && curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET:8080/$main_js" | python3 -c "
import sys, re
content = sys.stdin.read()
for m in re.findall(r'https?://[^\"\\s,;\\)]+', content):
if '://' in m and not 'fonts.' in m:
print(f'URL: {m}')
for m in re.findall(r'apiUrl[\"\\']?\s*[:=]\s*[\"\\']([^\"\\']+)[\"\\']', content):
print(f'apiUrl: {m}')
" 2>/dev/null
This technique can reveal API backends on non-standard ports that are absent
from navigation and DNS naming conventions.
GITHUB DORKING FOR TARGET
TARGET_ORG="TargetOrgName"
gh search code "api_key" --owner "$TARGET_ORG" --json path,repository 2>/dev/null | jq '.'
gh search code "password" --owner "$TARGET_ORG" --json path,repository 2>/dev/null | head -20
python3 ~/tools/GitDorker/GitDorker.py -t GITHUB_TOKEN -d ~/tools/GitDorker/Dorks/alldorksv3 -q "$TARGET" -org
30-MINUTE RECON PROTOCOL
Dual-Track Parallel Recon (Proven Effective for Multi-Target Batches)
When testing 15-20+ targets across multiple sectors, use a dual-track approach:
Track 1 — Manual Fast Probe (you do this while Track 2 runs)
- Probe each domain with
curl --max-time 10 --connect-timeout 5 -sI for alive check + server headers (takes ~30s per 5 domains)
- Immediately check promising signals: WordPress link headers, CORS origins, interesting cookies
- Run subfinder on interesting domains while they're fresh in mind
- Chase live subdomains immediately (app., dashboard., staging., etc.)
Track 2 — Automated Scanner (background)
- Launch a Python scanner with bounded delays and five parallel tasks.
- The scanner does the systematic work: WP detection, CORS test, sensitive file check, subdomain enum for every domain
- Check progress periodically; by the time it finishes you already have the high-signal findings
Why this works:
- Manual probes reveal high-value targets (CRITICAL CORS, WP users, exposed staging) in the first 5 minutes
- The automated scanner validates the rest without burning your attention on dead targets
- Subdomains found manually can be probed immediately while the scanner is still running
- In Wave 5 (20 targets, 4 sectors), this approach revealed 3 CRITICAL CORS + 11 exposed WP users + 95 subdomains within 15 minutes of manual work
Minutes 0-5: Read Program Page
Note:
- ALL in-scope assets (every domain listed)
- Out-of-scope list (read carefully — common trap)
- Safe harbor statement
- Impact types accepted (some exclude "low")
- Average bounty amount (signals program generosity)
Minutes 5-15: Asset Discovery
Run the standard pipeline above. Focus on live.txt output.
Minutes 15-25: Surface Map
Run gf patterns and the interesting-params grep above.
Minutes 25-30: Manual Exploration
Open Burp Suite. Browse the app with proxy on:
- Register an account
- Perform main user actions (create/read/update/delete resources)
- Note all API calls in Burp history
- Look for endpoints not in your URL list
After 30 min: Prioritize
Priority 1: API endpoints with ID parameters → IDOR candidates
Priority 2: File upload features → XSS/RCE candidates
Priority 3: OAuth/SSO flows → auth bypass candidates
Priority 4: Search/filter with user input → SQLi/SSRF/SSTI candidates
Priority 5: Admin/debug endpoints → auth bypass candidates
Toolchain fallback (when dnsx / httpx crash)
The projectdiscovery Go binaries (dnsx, httpx, naabu) occasionally SIGSEGV on macOS arm64 due to a cgo / system-resolver interaction. The crash signature is identical regardless of install method — both brew install and go install github.com/projectdiscovery/<tool>@latest produce binaries that segfault at the same address. Smoke-test once before relying on them in a real engagement:
dnsx -version
httpx -version
dnsx (preferred) or dig fallback
dnsx -silent -l subs.txt -a -resp-only -o resolved.txt
while read s; do
ips=$(dig +short +tries=1 +time=3 "$s" \
| grep -E '^[0-9.]+$' \
| paste -sd, -)
[ -n "$ips" ] && echo "$s|$ips"
done < subs.txt
httpx → curl fallback
while read s; do
resp=$(curl --max-time 30 --connect-timeout 10 -s -L -m 5 -o /tmp/body \
-w "%{http_code}|%{url_effective}|%{header_server}" \
"https://$s")
code=$(echo "$resp" | cut -d'|' -f1)
if [ "$code" != "000" ]; then
title=$(grep -oE '<title[^>]*>[^<]*</title>' /tmp/body | head -1 | sed 's/<[^>]*>//g')
echo "$s|$resp|$title"
fi
done < subs.txt
Trade-off: Serial vs. concurrent. The fallback handles ~24 subdomains in 14 seconds; the same workload on httpx with default 50 threads finishes in 2-3 seconds. For VDP-scale recon (< 100 subdomains) the fallback is fine. For mass recon (1000+) fix the toolchain first.
Verified against HackerOne's own VDP in docs/verification/recon-hackerone-vdp.md.
API Spec / Swagger / OpenAPI Discovery (2024-2026 surface)
API spec endpoints are the single highest-leverage recon target on any modern .NET / Node / Python / Java backend. The spec discloses every endpoint, HTTP methods, parameter names + types + formats, models, validation rules — a complete attack-map in JSON. Default routes are commonly left enabled in production. Add this wordlist to the directory-fuzzing phase (after the standard common.txt pass).
Default discovery path wordlist (paste into swagger-paths.txt)
# NSwag / Swashbuckle (ASP.NET Core)
/swagger
/swagger/
/swagger/index.html
/swagger/ui/index.html
/swagger/v1/swagger.json
/swagger/v2/swagger.json
/swagger/v3/swagger.json
/swagger/docs/v1
/swagger/docs/v2
/swagger-ui
/swagger-ui/
/swagger-ui.html
/swagger-resources
/swagger-resources/configuration/ui
/nswag
/nswag/index.html
/api/swagger
/api/swagger.json
/api/swagger/v1/swagger.json
/api/openapi
/api/openapi.json
/api/v1/swagger.json
/api/v2/swagger.json
/api-docs
/api-docs/swagger.json
# OpenAPI generic
/openapi
/openapi.json
/openapi.yaml
/openapi.yml
/openapi/v1.json
/openapi/v2.json
/openapi/v3.json
/.well-known/openapi.json
# Java / Spring (Springfox / springdoc)
/v2/api-docs
/v3/api-docs
/v3/api-docs.yaml
/v3/api-docs/swagger-config
/swagger-ui/index.html
# Python (FastAPI / Flask-RESTPlus / Connexion / DRF)
/docs
/docs/
/redoc
/redoc/
/openapi.json
/swagger.json
/swagger/?format=openapi
/swagger.yaml
# Express / Node / Hapi
/api-docs
/api-docs.json
/swagger.json
/swagger-stats
/graphql-docs
# GraphQL adjacent (often co-located)
/graphql
/graphiql
/playground
/altair
/voyager
/graphql/console
/graphql-explorer
# ReDoc / RapiDoc / Stoplight / alt UIs
/redoc
/redoc.html
/redoc-ui.html
/rapidoc
/rapidoc.html
/stoplight
/elements
# Misc / dev-leftover
/actuator
/actuator/openapi
/actuator/mappings
/q/openapi
/q/swagger-ui
/docs/swagger.json
/api/v1/docs
/api/v2/docs
/internal/swagger
/admin/swagger
/management/swagger
Integration with the standard pipeline
ffuf -w swagger-paths.txt -u "https://FUZZ.target.com" -mc 200,302 -fs 0 -t 50 -o swagger-hits.json
httpx -l live-hosts.txt -path swagger-paths.txt -mc 200 -mr "swagger|openapi" -json | tee swagger-hits.jsonl
jq '.paths | keys' swagger.json > endpoints.txt
jq '.components.schemas' swagger.json > schemas.json
Why this matters for recon-to-hunting handoff
- Spec → mass IDOR/BOLA —
jq '.paths | keys' swagger.json becomes the input list for Autorize/ffuf per-user testing.
- Spec → mass-assignment payload construction —
components.schemas.UserUpdateDto enumerates isAdmin, emailVerified, tenantId, role.
- Spec → hidden endpoint discovery —
/internal/*, /debug/*, /v0/*, /legacy/* routes documented but never auth-gated.
- Spec → injection-class seeding — every parameter's type + format + enum + max-length means payloads pass validation before reaching the sink. Especially valuable against ASP.NET Core where the model binder rejects malformed input before any controller logic.
Tools
kiterunner — natively ingests OpenAPI spec, generates requests against the API.
sj (Swagger Jacker) — purpose-built for Swagger spec exploitation.
apidetector (brinhosa) — Swagger-UI mass scanner.
XSSwagger (vavkamil) — detects vulnerable Swagger UI versions (CVE-2018-25031 family).
nuclei -t http/exposures/apis/ — built-in templates for default spec paths.
Anti-pattern reminder
A 404/403 on /swagger does NOT mean no spec is exposed. Many .NET projects route the spec under /api/swagger/v1/swagger.json rather than /swagger. Always test the full path list, not just the root.
Full attack-chain analysis is in hunt-api-misconfig → NSwag / Swagger / OpenAPI Spec Exposure.
Verification
- Core recon tools — confirm key tools are installed:
which subfinder httpx nuclei katana 2>/dev/null | wc -l | xargs -I{} echo "{} of 4 core tools found"
- HTTP probe — confirm httpx works:
echo "example.com" | httpx -silent -status-code 2>/dev/null && echo "PASS: httpx operational" || echo "NOTE: httpx not installed"
All tests verify web2 recon readiness.
Pitfalls
- Recon without triage — collecting 10,000 subdomains without prioritizing which to test is data collection, not recon.
- HTTP probe without HTTPS — many services only respond on HTTPS. Test both protocols.
- Screen shot collection without analysis — automated screenshots are useful only if you review them. Unique login pages, error messages, and CMS identifiers are the value.
- Port scanning without service identification — open port 8443 might be anything. Banner grab and identify the actual service.
- Stale recon data — subdomain data older than 30 days is increasingly unreliable. Refresh recon for each engagement.
Related Skills & Chains
offensive-osint — When recon needs concrete probes / wordlists / regexes beyond the basic pipeline. Workflow primitive: this skill produces the URL set; offensive-osint provides the secret regexes, GraphQL/Swagger paths, and identity-fabric probes you apply to that URL set.
osint-methodology — When you need a severity rubric for what you discovered. Workflow primitive: after recon outputs subdomains.txt / live-hosts.txt / urls.txt, score each asset against osint-methodology's findings rubric to decide what gets a finding versus what stays in the asset graph.
hunt-subdomain — When recon surfaces stale CNAMEs / dangling DNS. Workflow primitive: any subdomain in subdomains.txt whose CNAME points to S3 / GitHub Pages / Heroku / Shopify / Azure should auto-route to hunt-subdomain for takeover validation.
security-arsenal — When the URL set is classified by gf and ready for active testing. Workflow primitive: gf xss/ssrf/sqli/idor output names become payload-class queries against security-arsenal's payload library.
bb-methodology — When recon completes and Phase 1 transitions to Phase 2 (Mapping). Workflow primitive: hand the live host + URL set back to bb-methodology Phase 2 for endpoint mapping and Phase 3 vulnerability discovery routing.