Featuring: insomnia, a tool named tracr, and DNS servers that finally learned how to say “no.”

TL;DR: scan subdomains for dangling nameservers (REFUSED/SERVFAIL or unresolvable NS) and report clearly with reproducible evidence.

assetfinder -subs-only target.com | tracr -c 50 -v

Use it: https://github.com/cybercdh/tracr

Meet Derek. Derek traces DNS by hand. Derek is a good person with a bad hobby. He reads RFCs like bedtime stories and wakes up wondering if an SOA’s minimum TTL is spiritual or literal. On the night our story begins, Derek had a thousand subdomains, a mug named Gerald, and the sinking feeling that if he ran one more dig +trace he would start hearing colors.

Somewhere around support-preprod-analytics.dev.example.com the enlightenment arrived that always precedes truly questionable coding: “What if I just teach the computer to hate tedium as much as I do?” tracr was born at 02:47, alongside a promise to do laundry “tomorrow.”

Think of DNS as museums handing off a package across rooms. +trace walks you room-to-room until the last-but-one room says, “These are the official guides.” If those guides are cardboard cutouts (REFUSED, SERVFAIL, or unresolvable), the last room is at the mercy of whoever animates the cutouts first.

tracr reads the museum map at speed and only taps you on the shoulder for the cardboard.

The glow-up from manual to civilized looks like this:

# Old way
dig sub.example.com +trace
dig @ns1.abandoned.com example.com
# repeat until personality dissolves

# New way
assetfinder -subs-only target.com | tracr -c 50 -v | tee vuln.txt

Under the hood it’s simple, just… fast. Pseudocode in Go‑ish:

type Target struct { domain string; servers []string }

func authorities(domain string) []string {
    rsps, _ := dig.Trace(domain)
    var ns []string
    for i, r := range rsps {
        if i != len(rsps)-2 { continue }
        for _, rr := range r.Msg.Ns {
            if rr.Header().Rrtype == dns.TypeNS {
                ns = append(ns, strings.TrimSuffix(rr.(*dns.NS).Ns, "."))
            }
        }
    }
    return ns
}

func looksDangling(domain, server string) bool {
    dig.SetDNS(server)
    m, err := dig.GetMsg(dns.TypeA, domain)
    if err != nil { return true } // network wobble; tracer retries elsewhere
    rc := dns.RcodeToString[m.MsgHdr.Rcode]
    return rc == "REFUSED" || rc == "SERVFAIL"
}

Worker pools keep it snappy without heating the planet:

jobs := make(chan string)
wg := sync.WaitGroup{}
for w := 0; w < 40; w++ {
  wg.Add(1)
  go func() {
    defer wg.Done()
    for d := range jobs {
      ns := authorities(d)
      for _, s := range ns {
        if looksDangling(d, s) { fmt.Printf("%s -> %s\n", d, s) }
      }
    }
  }()
}
// feed jobs from stdin

False‑positive control so you don’t report “my Wi‑Fi blinked” as a vuln:

  • Retry on transient NXDOMAIN/timeout; only flag consistent REFUSED/SERVFAIL.
  • Cache NS lookups per run; avoid hammering the same dead server.
  • Randomize resolver choice; some public resolvers paper over weirdness.

Derek’s first adult run saved six hours and paid £3,200. The next found another at £5,800. After a few months the total was £25k and a personality that no longer smelled like dig output. More importantly, the reports stopped sounding like prophecies and started looking like evidence.

Practical pipeline you can copy without thinking too hard:

subfinder -d target.com -silent > subs.txt
cat subs.txt | tracr -c 40 -v | tee vulns.txt

# sanity-check a single finding
ns=ns1.abandoned-hosting.com
dig @"$ns" target.com A; echo $?

Install it:

go install github.com/cybercdh/tracr@latest

Copy me:

# Minimal scan + sanity-check
assetfinder -subs-only example.com > subs.txt
cat subs.txt | tracr -c 40 -v | tee vulns.txt

# verify one finding
ns=ns1.abandoned-hosting.com; dig @"$ns" example.com A +norecurse

Rules of engagement so we all keep getting invited back:

  • Only test where you have permission.
  • Report clearly: show trace, show servers, show consistent codes.
  • Don’t register infrastructure you don’t need to prove; explain the impact instead.

The point isn’t just DNS. It’s noticing when your brain is doing what a loop could do, and then writing the loop so your brain can go outside occasionally.

Be like tracr: lazy on purpose, fast by design, polite to DNS. Gerald approves.

Report snippet (bug bounty/responsible disclosure):

Title: Dangling nameserver enables subdomain takeover

Target: example.com (affects: api.example.com, cdn.example.com)
Evidence:
  - dig +trace api.example.com → authority NS: ns1.abandoned-hosting.com
  - dig @ns1.abandoned-hosting.com example.com A → REFUSED (consistent across retries)
Impact: Attacker registering/operating the nameserver could answer authoritatively, enabling takeover and phishing.
Actions taken: No registration or exploitation; discovery only.
Recommended fix: Update NS to active servers or remove stale delegations; verify authoritative answers.