web2-recon — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited web2-recon (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Full asset discovery from nothing to a prioritized URL list ready for hunting.
# 1. Set your Chaos API key (get free key at chaos.projectdiscovery.io)
export CHAOS_API_KEY="your-key-here"
# Add to ~/.zshrc or ~/.bashrc for persistence:
echo 'export CHAOS_API_KEY="your-key-here"' >> ~/.zshrc
# 2. Update nuclei templates (run weekly)
nuclei -update-templates
# 3. Configure subfinder with API keys for more sources
mkdir -p ~/.config/subfinder
cat > ~/.config/subfinder/config.yaml << 'EOF'
# Get free keys at: virustotal.com, securitytrails.com, censys.io, shodan.io
virustotal: [YOUR_VT_KEY]
securitytrails: [YOUR_ST_KEY]
censys_apiid: YOUR_CENSYS_ID
censys_secret: YOUR_CENSYS_SECRET
shodan: [YOUR_SHODAN_KEY]
EOF
# 4. Verify all tools installed
which subfinder httpx dnsx nuclei katana waybackurls gau dalfox ffuf anew gf interactsh-clientIf a target shows nothing interesting after 5 minutes of recon, move on. Don't burn hours on dead surface.
5-minute kill signals:
TARGET="target.com"
# Step 0: Passive — crt.sh certificate transparency (no API key needed)
curl -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"
# Step 1: Chaos API (ProjectDiscovery — most comprehensive source)
curl -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"
# Step 2: subfinder (passive multi-source)
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)"
# Step 3: DNS resolution + live host check
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)"
# Step 4: URL crawl
cat /tmp/live.txt | awk '{print $1}' | katana -d 3 -jc -kf all -silent | anew /tmp/urls.txt
# Step 5: Historical URLs
echo $TARGET | waybackurls | anew /tmp/urls.txt
gau $TARGET --subs | anew /tmp/urls.txt
echo "[+] Total URLs: $(wc -l < /tmp/urls.txt)"
# Step 6: Nuclei scan
nuclei -l /tmp/live.txt -t ~/nuclei-templates/ -severity critical,high,medium -o /tmp/nuclei.txtTARGET="target.com"
RECON_DIR="recon/$TARGET"
mkdir -p $RECON_DIR
# All outputs go here:
/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# Parameters worth testing
cat /tmp/urls.txt | grep -E "[?&](id|user|file|path|url|redirect|next|src|token|key|api_key)=" | tee /tmp/interesting-params.txt
# API endpoints
cat /tmp/urls.txt | grep -E "/api/|/v1/|/v2/|/v3/|/graphql|/rest/|/gql" | tee /tmp/api-endpoints.txt
# File upload endpoints
cat /tmp/urls.txt | grep -E "upload|file|attachment|document|image|avatar|photo|media" | tee /tmp/uploads.txt
# Admin/internal paths
cat /tmp/urls.txt | grep -E "/admin|/internal|/debug|/test|/staging|/dev|/management|/console" | tee /tmp/admin-paths.txt
# Authentication endpoints
cat /tmp/urls.txt | grep -E "/oauth|/login|/auth|/sso|/saml|/oidc|/callback|/token" | tee /tmp/auth-paths.txt# Install gf patterns: https://github.com/tomnomnom/gf
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# Activate venv
source ~/tools/SecretFinder/.venv/bin/activate
# Scan a single JS file
python3 ~/tools/SecretFinder/SecretFinder.py -i "https://target.com/static/js/main.js" -o cli
# Scan all JS URLs found in recon
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
deactivatesource ~/tools/LinkFinder/.venv/bin/activate
# Single JS file
python3 ~/tools/LinkFinder/linkfinder.py -i "https://target.com/app.js" -o cli
# All pages (crawls JS from HTML)
python3 ~/tools/LinkFinder/linkfinder.py -i "https://target.com" -d -o cli
deactivate# Directory discovery on a live host
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
# API endpoint discovery
ffuf -u "https://target.com/api/FUZZ" \
-w ~/wordlists/api-endpoints.txt \
-mc 200,201,204,301,302 \
-ac \
-t 20
# IDOR fuzzing with authenticated request
# Create req.txt with Authorization: Bearer TOKEN
ffuf -request /tmp/req.txt \
-request-proto https \
-w <(seq 1 10000) \
-fc 404 \
-ac \
-t 10Score 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
# Response headers reveal backend
curl -sI https://target.com | grep -iE "server|x-powered-by|x-aspnet|x-runtime|x-generator"
# Common signals:
# Server: nginx + X-Powered-By: PHP/7.4 → PHP backend
# Server: gunicorn OR X-Powered-By: Express → Python/Node.js
# X-Powered-By: ASP.NET → .NET
# Server: Apache Tomcat → Java
# X-Runtime: Ruby → Ruby on Rails
# Framework from JS bundle paths:
# /_next/static/ → Next.js
# /static/js/main.chunk.js → CRA (React)
# /packs/ → Ruby on Rails + Webpacker
# /__nuxt/ → Nuxt.js (Vue)| 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 |
Set up once per target. Alerts you before other hunters.
#!/bin/bash
TARGET="target.com"
KNOWN="/tmp/$TARGET-subs-known.txt"
subfinder -d $TARGET -silent > /tmp/$TARGET-subs-fresh.txt
curl -s "https://dns.projectdiscovery.io/dns/$TARGET/subdomains" \
-H "Authorization: $CHAOS_API_KEY" \
| jq -r '.[]' >> /tmp/$TARGET-subs-fresh.txt
# Diff against known
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
# Schedule: crontab -e → 0 8 * * * /bin/bash ~/monitors/subs-watch.sh#!/bin/bash
REPO="TargetOrg/target-app"
LAST_SHA="/tmp/$REPO-last-sha.txt"
CURRENT=$(curl -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
# Get changed files
curl -s "https://api.github.com/repos/$REPO/commits/$CURRENT" \
| jq -r '.files[].filename' | grep -E "auth|middleware|route|permission|role|admin"
fi
# Schedule: */30 * * * * /bin/bash ~/monitors/github-watch.sh# naabu — fast port scanner from ProjectDiscovery
# Finds non-standard ports: 8080, 8443, 3000, 8888, 9000, etc.
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
# Why this matters: admin panels, debug services, internal APIs often run on alt ports
# Example wins: :8080/actuator/env (Spring Boot), :9200/_cat/indices (Elasticsearch), :6379 (Redis)# trufflehog — high-signal secret detection with entropy analysis
# Scans JS files and git repos
pip install trufflehog3 2>/dev/null || true
trufflehog filesystem --only-verified recon/$TARGET/ 2>/dev/null
# SecretFinder — manual JS bundle scan (already in tools/)
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
# Quick grep for common patterns in downloaded JS
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# Search GitHub for hardcoded secrets before hunting the app
TARGET_ORG="TargetOrgName" # Check their GitHub org
# Useful dorks (search on github.com):
# org:TARGET_ORG password
# org:TARGET_ORG api_key
# org:TARGET_ORG "Authorization: Bearer"
# org:TARGET_ORG .env
# org:TARGET_ORG "BEGIN RSA PRIVATE KEY"
# CLI with gh (GitHub CLI):
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
# GitDorker (if installed):
python3 ~/tools/GitDorker/GitDorker.py -t GITHUB_TOKEN -d ~/tools/GitDorker/Dorks/alldorksv3 -q "$TARGET" -orgNote:
- 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)Run the standard pipeline above. Focus on live.txt output.
Run gf patterns and the interesting-params grep above.
Open Burp Suite. Browse the app with proxy on:
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 candidatesdnsx / 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 # if SIGSEGV: use the dig fallback below
httpx -version # if SIGSEGV: use the curl fallback belowdnsx → dig fallback# Replaces: dnsx -l subs.txt -a -resp -silent
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.txthttpx → curl fallback# Replaces: httpx -l subs.txt -silent -status-code -title -tech-detect
while read s; do
resp=$(curl -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.txtTrade-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.
offensive-osint provides the secret regexes, GraphQL/Swagger paths, and identity-fabric probes you apply to that URL set.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.subdomains.txt whose CNAME points to S3 / GitHub Pages / Heroku / Shopify / Azure should auto-route to hunt-subdomain for takeover validation.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 Phase 2 for endpoint mapping and Phase 3 vulnerability discovery routing.Engagement-derived + 2026-specific additions to the vendored foundation. Wisdom from real authorized engagements + Phase 2 verification across this repo's 31+ skill-area live tests. The upstream pipeline covers the WHAT; this layer covers the WHEN-IT-WORKS-vs-WHEN-IT-DOESN'T.
Phase 2C's HackerOne VDP recon walked from hackerone.com (24 subdomains) into a sister TLD hacker.one (12 more subdomains found in JS bundle references). Operators who only enumerate *.target.com miss attack surface that the target legitimately operates on a different domain.
Always grep JS bundles for plausible sibling TLDs:
# pull all JS, grep for sibling-TLD candidates
for url in $(cat live-hosts.txt); do
curl -s "$url" | grep -oE 'src="[^"]+\.js"' | sed 's/src="//;s/"//'
done | sort -u > js-urls.txt
# then on each JS file
for j in $(cat js-urls.txt); do
curl -s "$j" | grep -oE '[a-z0-9.-]+\.(io|app|one|dev|test|cloud|ai|co)' | sort -u
done | sort -u > sibling-tld-candidates.txtCommon sibling-TLD patterns: target.com → target.io / target.app / target.one / target.dev / target.test / target-corp.com / target-cdn.net. Always validate via WHOIS or by checking if the cert chain trusts the same internal CA before treating the sister TLD as in-scope.
Top discovery prefixes by hit rate against enterprise VDPs in our 2024-2026 corpus:
mta-sts.* api.* docs.*
dev-* staging-* *-qa
*-stage *-uat events.*
portal.* customer.* partner.*
vendor.* internal-* admin-*
employee-* hr.* jobs.*
sso.* auth.* id.*Internal-looking subdomains often expose more surface than the marketing site — partner.target.com and vendor-portal.target.com frequently have weaker auth than the main app because they're scoped for "trusted" external users. Always send a probe to the long-tail wordlist after the standard subfinder run completes.
curl -sI <host> headers are 80% of the fingerprint:
Server: — apache / nginx / cloudflare / kestrel (= .NET Core) / openresty / envoyX-Powered-By: — PHP version, ASP.NET version, Express.jsX-Drupal-Cache, X-Generator: Drupal 9 — DrupalX-Generator: WordPress — WordPressVia: — CDN chain (1.1 varnish, 1.1 cloudfront)Set-Cookie: names — JSESSIONID (Java), PHPSESSID (PHP), ASP.NET_SessionId (.NET), connect.sid (Express), laravel_session (Laravel)JS bundle filename patterns:
/_next/static/ = Next.js/_nuxt/ = Nuxt/assets/static/ with hash filenames = Vite/static/js/main.*.chunk.js = Create React Appruntime.*.js + polyfills.*.js + main.*.js = Angular CLIThe first 10s of recon should yield a stack guess; the rest is targeting. If your fingerprint contradicts itself (Server says nginx, Set-Cookie says ASP.NET) you've found a reverse proxy front-end — note the origin app for later smuggling/cache attacks.
Critical distinction operators get wrong:
Same distinction for CloudFront:
Server: CloudFront = distribution exists, origin returned 404 — NOT a takeover.X-Cache: Error from cloudfront = origin missing entirely — potential takeover.Phase 2C verified both patterns live. Always check the EXACT response body string before filing a takeover finding — the takeover-scanner tools (subzy, subjack) match on multiple fingerprints and frequently false-positive on the "still owned, just empty" case.
Already covered in this file's Phase 2C addition. Quick reminder: dnsx/httpx may segfault on macOS arm64; the dig+curl fallback works for < 100-host runs in ~14 seconds. Don't burn an hour debugging Go binary panics when the fallback gets you to the same URL set.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.