tko-subs: Automating Subdomain Takeovers from Detection to Exploitation
Hook
What if your scanner didn't just find vulnerable subdomains, but actually claimed them for you? tko-subs crosses the line from reconnaissance to exploitation, automating the entire subdomain takeover chain in a single Go binary.
Context
Subdomain takeover vulnerabilities have plagued organizations since the early days of cloud adoption. The attack vector is deceptively simple: a company points blog.example.com to their-blog.herokuapp.com via CNAME, later deletes the Heroku app but forgets to remove the DNS record. An attacker can then register 'their-blog' on Heroku and effectively control blog.example.com, serving malicious content under the victim's domain.
Traditionally, bug bounty hunters and red teamers manually checked for these issues by resolving CNAMEs, visiting subdomains, recognizing error pages from various cloud providers, then attempting to claim the resources. This process was tedious when scanning hundreds or thousands of subdomains. Tools emerged to automate detection, but tko-subs went further: it automated the actual takeover for supported platforms, compressing hours of manual work into seconds of API calls.
Technical Insight
tko-subs implements a three-stage pipeline: DNS resolution, HTTP fingerprinting, and optional exploitation. The architecture leverages Go's concurrency model to parallelize subdomain checking across multiple goroutines, with configurable thread counts to balance speed against rate limiting.
The tool begins by reading a list of subdomains and performing DNS lookups. Unlike earlier versions that used Go's net.LookupCNAME, the current implementation shells out to the 'dig' command directly. This design decision came from discovering that Go's built-in resolver couldn't catch certain edge cases, particularly dead DNS records where nameservers are configured but don't respond properly:
// Simplified example of the DNS checking approach
func checkDNS(subdomain string) (*DNSResult, error) {
// Using dig instead of net.LookupCNAME to catch more edge cases
cmd := exec.Command("dig", "+short", subdomain, "CNAME")
output, err := cmd.Output()
if err != nil {
return nil, err
}
cname := strings.TrimSpace(string(output))
if cname == "" {
// No CNAME, check for A records or dead nameservers
return checkForDeadDNS(subdomain)
}
return &DNSResult{
Subdomain: subdomain,
CNAME: cname,
}, nil
}
Once DNS records are extracted, the tool performs HTTP requests to each subdomain and matches response bodies against provider signatures. These signatures are defined in a CSV file (providers-data.csv) containing regex patterns and identifying strings for services like GitHub Pages ("There isn't a GitHub Pages site here"), Amazon S3 ("NoSuchBucket"), and Heroku ("No such app"). This CSV-based approach makes the tool extensible—security researchers can add new cloud providers without modifying Go code.
The fingerprinting engine uses goroutines to check multiple subdomains concurrently:
func scanSubdomains(domains []string, threads int) []Vulnerability {
jobs := make(chan string, len(domains))
results := make(chan Vulnerability, len(domains))
// Spawn worker goroutines
for i := 0; i < threads; i++ {
go worker(jobs, results)
}
// Feed domains to workers
for _, domain := range domains {
jobs <- domain
}
close(jobs)
// Collect results
var vulnerabilities []Vulnerability
for i := 0; i < len(domains); i++ {
vuln := <-results
if vuln.Vulnerable {
vulnerabilities = append(vulnerabilities, vuln)
}
}
return vulnerabilities
}
What sets tko-subs apart is the exploitation layer. When a vulnerable GitHub Pages or Heroku subdomain is detected, the tool can automatically claim it using provider APIs. For GitHub, it creates a new repository with the CNAME file pointing to the vulnerable subdomain. For Heroku, it adds the custom domain to an existing app you control. This functionality requires API credentials (GitHub tokens, Heroku app names) passed as command-line flags.
The tool also detects nameserver misconfigurations by checking whether authoritative nameservers for a subdomain are registered but the domain itself doesn't resolve. This catches cases where companies use external DNS providers (like ns1.example-dns-provider.com) but let their account expire, allowing an attacker to register that provider domain and control DNS for all subdomains pointing to it.
Error handling throughout the codebase is pragmatic rather than exhaustive—failed DNS lookups are logged but don't halt execution, allowing the scan to continue even when individual subdomains timeout or return unexpected responses. This trade-off favors completeness over perfection, appropriate for reconnaissance tooling where false negatives are more costly than occasional errors.
Gotcha
The biggest limitation is maintenance stagnation. The repository hasn't seen significant updates since 2018-2019, meaning the provider fingerprints are likely outdated. Cloud platforms frequently change their error messages—what returned "NoSuchBucket" in 2018 might return different HTML in 2024, causing false negatives. The CSV file contains about 20 providers, but dozens of new cloud platforms have emerged since then (Vercel, Netlify, Render, Railway), none of which are included.
Automated takeover only works for GitHub Pages and Heroku. If you find a vulnerable subdomain pointing to Azure, AWS Elastic Beanstalk, or any of the other 18 detected providers, you'll still need to manually claim them. This asymmetry is jarring—the tool proudly detects 20+ platforms but only exploits 2. The implementation also requires you to already have a Heroku app deployed and pass its name via flag, adding setup friction. For GitHub, it creates repositories in your account, which can get messy during large-scale scans. Finally, shelling out to 'dig' means the tool won't work on systems without bind-utils installed, reducing portability compared to pure-Go DNS implementations. The reliance on string matching against HTTP responses is inherently brittle and makes the tool feel dated compared to modern vulnerability scanners using more robust detection logic.
Verdict
Use if: You're conducting bug bounty or red team engagements where you need to quickly triage hundreds of subdomains for takeover vulnerabilities, you're comfortable updating the providers-data.csv yourself to keep fingerprints current, or you specifically need automated GitHub/Heroku takeover capabilities and want to compress the detection-to-exploitation timeline. Skip if: You need a production-ready, actively maintained tool with current cloud provider fingerprints (consider subjack or nuclei templates instead), you're uncomfortable with the ethical/legal implications of automated exploitation features, you require support for claiming subdomains beyond GitHub/Heroku, or you need robust error handling and portability across systems without external dependencies like dig.