11#! /usr/bin/env bash
22#
3- # chaos-takeover.sh — bulk subdomain-takeover hunt over the ProjectDiscovery
4- # Chaos dataset, fully streamed (resolve → CNAME match → verify → Discord).
3+ # chaos-takeover.sh — bulk subdomain CNAME-fingerprint hunt over the
4+ # ProjectDiscovery Chaos dataset, fully streamed (resolve → match → Discord).
55#
66# Pipeline (every stage runs concurrently via pipes — no save-then-wait):
77# download all program ZIPs → unzip -p → dnsx (CNAME) → grep fingerprints
8- # → nuclei takeover templates → notify (Discord, per confirmed hit )
8+ # → notify (Discord, per matched host )
99#
10- # A CNAME match is only a CANDIDATE. nuclei verifies fingerprint + unclaimed
11- # target before it's alerted, so Discord only gets real, reportable takeovers.
10+ # NO VERIFICATION STEP. A CNAME match only means the host points at one of the
11+ # fingerprinted services — most are CLAIMED (not exploitable). This is a raw
12+ # candidate feed, not a confirmed-findings feed: expect false positives, and
13+ # manually confirm (unclaimed target / claimable) before treating anything here
14+ # as a reportable bug.
1215#
1316# Usage:
1417# ./chaos-takeover.sh [options]
1821# --par N parallel ZIP downloads (default: 4)
1922# --threads N dnsx threads (default: 300)
2023# --no-download skip download, reuse zips/ already on disk
21- # --candidates-too also send raw CNAME candidates to Discord (batched; noisy)
2224# -h | --help
2325#
2426# Note: the bulk dataset (index.json + program ZIPs) is a PUBLIC endpoint and is
2527# fetched WITHOUT auth — sending CHAOS_API_KEY to it returns HTTP 400. The key is
2628# only used by the per-domain DNS API (the AutoAR Chaos integration / chaos-client).
2729#
28- # Deps: curl jq unzip dnsx nuclei notify
30+ # Deps: curl jq unzip dnsx notify
2931set -euo pipefail
3032
3133# ── defaults ──────────────────────────────────────────────────────────────────
@@ -36,11 +38,11 @@ DNSX_T=300
3638RESOLVERS=" "
3739WEBHOOK=" "
3840DO_DOWNLOAD=1
39- CANDIDATES_TOO=0
4041NOTIFY_ID=" takeover"
4142
42- # Takeover-able service fingerprints matched against the CNAME target. A match is
43- # a candidate only; nuclei confirms. Reference: github.com/EdOverflow/can-i-take-over-xyz
43+ # Service fingerprints matched against the CNAME target. UNVERIFIED — a match
44+ # means "points at this service", not "takeover confirmed". Most are claimed.
45+ # Reference: github.com/EdOverflow/can-i-take-over-xyz
4446FP=' vercel-dns\.com|github\.io|netlify\.app|azurewebsites\.net|elasticbeanstalk\.com|webflow\.io|gitbook\.io|readme\.io|railway\.app|herokudns\.com|herokuapp\.com|fastly\.net|ghost\.io|helpscoutdocs\.com|surge\.sh|bitbucket\.io|wpengine\.com|pantheonsite\.io|zendesk\.com|statuspage\.io'
4547
4648# ── args ──────────────────────────────────────────────────────────────────────
@@ -52,16 +54,14 @@ while [ $# -gt 0 ]; do
5254 --par) DL_PAR=" $2 " ; shift 2 ;;
5355 --threads) DNSX_T=" $2 " ; shift 2 ;;
5456 --no-download) DO_DOWNLOAD=0; shift ;;
55- --candidates-too) CANDIDATES_TOO=1; shift ;;
5657 -h|--help) sed -n ' 2,40p' " $0 " ; exit 0 ;;
5758 * ) echo " unknown option: $1 " >&2 ; exit 2 ;;
5859 esac
5960done
6061
6162# ── deps ──────────────────────────────────────────────────────────────────────
62- # Note: no httpx — nuclei probes hosts itself, and many systems have the Python
63- # httpx CLI shadowing ProjectDiscovery's, which breaks the pipeline.
64- for t in curl jq unzip dnsx nuclei notify; do
63+ # Note: no httpx/nuclei — this is a raw grep-match pipeline, unverified by design.
64+ for t in curl jq unzip dnsx notify; do
6565 command -v " $t " > /dev/null || { echo " [!] missing dependency: $t " >&2 ; exit 1; }
6666done
6767
@@ -122,45 +122,21 @@ else
122122 echo " [*] --no-download: reusing $( ls zips/* .zip 2> /dev/null | wc -l | tr -d ' ' ) existing ZIPs"
123123fi
124124
125- # Ensure the takeover templates are actually installed. This is NOT best-effort —
126- # `nuclei -update-templates` was previously swallowed by `|| true`, so a first-run
127- # machine with an empty ~/nuclei-templates dir would silently proceed straight to
128- # "no templates provided for scan" with zero hits ever alerted. Fail loud instead.
129- echo " [*] Ensuring nuclei templates are installed…"
130- nuclei -update-templates 2>&1 | tail -5
131- TAKEOVER_TPL_COUNT=$( nuclei -tags takeover -tl 2> /dev/null | grep -c ' \.yaml$' || true)
132- if [ " ${TAKEOVER_TPL_COUNT:- 0} " -eq 0 ]; then
133- echo " [!] 0 takeover templates found after update — nuclei can't verify anything." >&2
134- echo " Check 'nuclei -update-templates' output above for the real error." >&2
135- exit 1
136- fi
137- echo " ${TAKEOVER_TPL_COUNT} takeover templates available"
138-
139- # ── 3. streaming resolve → match → verify → notify ──────────────────────────
125+ # ── 3. streaming resolve → match → notify (no verification step) ────────────
140126DNSX_ARGS=(-cname -resp -silent -t " $DNSX_T " )
141127[ -n " $RESOLVERS " ] && DNSX_ARGS+=(-r " $RESOLVERS " )
142128
143- echo " [*] Streaming: unzip → dnsx → grep → nuclei(takeover) → notify "
144- : > candidates.txt; : > confirmed.txt
129+ echo " [*] Streaming: unzip → dnsx → grep → notify (every match, unverified) "
130+ : > candidates.txt
145131
146- # No httpx stage: nuclei probes each host itself, and -tags takeover selects the
147- # takeover templates by tag (path-independent across nuclei versions).
148132for z in zips/* .zip; do unzip -p " $z " 2> /dev/null || true ; done \
149133 | dnsx " ${DNSX_ARGS[@]} " \
150134 | stdbuf -oL grep -iE " $FP " \
151135 | tee -a candidates.txt \
152- | awk ' {print $1}' \
153- | nuclei -tags takeover -silent \
154- | tee -a confirmed.txt \
155- | while IFS= read -r hit; do
156- printf ' 🚨 Subdomain takeover confirmed: %s\n' " $hit " | notify -silent -id " $NOTIFY_ID "
136+ | while IFS= read -r line; do
137+ printf ' 🎯 CNAME match: %s\n' " $line " | notify -silent -id " $NOTIFY_ID "
157138 done
158139
159- # ── 4. optional: batch the raw candidates to Discord too (noisy) ────────────
160- if [ " $CANDIDATES_TOO " -eq 1 ] && [ -s candidates.txt ]; then
161- echo " [*] Sending $( wc -l < candidates.txt | tr -d ' ' ) raw candidates (batched)…"
162- notify -silent -id " $NOTIFY_ID " -bulk < candidates.txt || true
163- fi
164-
165- echo " [✓] Done — candidates=$( wc -l < candidates.txt | tr -d ' ' ) confirmed=$( wc -l < confirmed.txt | tr -d ' ' ) "
166- echo " candidates.txt (all CNAME matches) · confirmed.txt (nuclei-verified)"
140+ echo " [✓] Done — candidates=$( wc -l < candidates.txt | tr -d ' ' ) "
141+ echo " candidates.txt has every CNAME match, sent to Discord live as found."
142+ echo " UNVERIFIED — confirm each is actually unclaimed before reporting."
0 commit comments