Back to Articles

tko-subs: Automating Subdomain Takeovers from Detection to Exploitation

[ View on GitHub ]

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

Exploitation

Fingerprinting

DNS Resolution

Input

batch domains

execute per domain

CNAME/A records

load signatures

HTTP response

vulnerable match

vulnerable match

all results

claim success

claim success

Subdomain List

Provider Signatures CSV

Goroutine Pool

dig Command Executor

HTTP Client

Response Matcher

GitHub API Client

Heroku API Client

Vulnerability Report

System architecture — auto-generated

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.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/developer-tools/anshumanbh-tko-subs.svg)](https://starlog.is/api/badge-click/developer-tools/anshumanbh-tko-subs)