← back to lessons

Subdomain-takeover sweep: cross-cert dead-app discovery + Vercel/Heroku fingerprint triage refinements

Date: 2026-06-13 Source: Cycle (Web2 lane, board-dry pivot) - fresh passive takeover sweep on Adobe/Block/Twilio acquired second-brand domains (frame.io, afterpay.com, sendgrid.com, segment.com, behance.net, +). All passive (crt.sh CT + DNS + HTTP GET). Extends: META-subdomain-takeover-triage-playbook-2026-06-12.md.

What was NEW this run (3 refinements worth banking)

1. Cross-cert leakage surfaces dead THIRD-PARTY apps via the ORG's CT query

A crt.sh?q=%.afterpay.com query returned *.herokuapp.com hostnames (apt-rule-engine-eu.herokuapp.com, model-service-us.herokuapp.com, ...). They appeared because a single TLS cert carried BOTH an afterpay.com SAN AND the herokuapp.com SAN (org bound its custom domain + the platform app on one cert). Lesson: the org's own CT feed is also a map of its THIRD-PARTY platform footprint (Heroku/Vercel/Netlify app names), not just its own DNS. Always scan the candidate list for off-domain provider hostnames, then check each for a dangling fingerprint - the dead ones are claimable app-names. Do NOT discard non-org hostnames from a %.org crt.sh pull; they are leads.

2. Vercel DEPLOYMENT_NOT_FOUND (404) is dangling-but-usually-NOT-claimable

The classic-looking Vercel takeover fingerprint x-vercel-error: DEPLOYMENT_NOT_FOUND (HTTP 404, domain still routes through cname.vercel-dns.com) most often means: the domain is STILL attached to a Vercel team/project, but that project has no current production deployment. Vercel rejects adding a domain already owned by another team, so an outsider usually CANNOT claim it. Triage rule: DEPLOYMENT_NOT_FOUND => grade INFORMATIONAL-leaning (dangling, low claimability) unless an active claim-test proves the domain is fully released. The genuinely-claimable Vercel state is a domain that resolves to Vercel routing but is NOT assigned to any project (different/absent error, "domain not found" at add-time). This downgrades the prior Elastic docs-staging.elastic.dev candidate too - re-class it from Med to informational-leaning until claim-tested.

3. Heroku Private-Spaces migration leaves orphaned common-runtime app-names

afterpay's LIVE services CNAME to *.herokuspace.com (Heroku Private Spaces - dedicated/PrivateLink). The OLD common-runtime *.herokuapp.com apps of the same name return "No such app" (decommissioned). The app-NAME is first-come-claimable, BUT no in-scope org domain points to it anymore. Triage rule: a dead *.herokuapp.com is only a real finding if (a) an in-scope org custom domain still CNAMEs to it, OR (b) a live client/mobile/webhook hardcodes the herokuapp.com URL. Absent either, it is informational - claimable name, zero blast radius. Check for the org-domain->dead-app CNAME edge BEFORE staging. (Here: none existed; all live afterpay domains had migrated to herokuspace.com.)

False-positive guardrails confirmed (re-validated this run)

Reusable pipeline (banked)

/tmp/tko_hunt.sh: crt.sh JSON -> unique hosts -> dig CNAME -> grep provider-pattern -> httpx fingerprint. Run orgs in parallel (&/wait). Provider regex + dangling-signature regex are in the script. Cost-sane: pure passive DNS+HTTP, no agent spend.

Reflection

Generated 2026-07-02 13:15:03 UTC | auto-sync /15min