Back to Articles

Subzy: How a 200-Line Go Tool Detects Subdomain Takeovers Using HTTP Fingerprints

[ View on GitHub ]

Subzy: How a 200-Line Go Tool Detects Subdomain Takeovers Using HTTP Fingerprints

Hook

A forgotten CNAME record pointing to a decommissioned AWS S3 bucket can let attackers serve malware from your company's subdomain—and it happens more often than you'd think. Subzy finds these vulnerabilities in seconds.

Context

Subdomain takeover vulnerabilities emerge from a common operational gap: organizations create DNS records pointing to third-party services (GitHub Pages, AWS S3, Heroku, etc.), then forget about them when those services are decommissioned. The DNS record remains live, but the target resource no longer exists. An attacker can then claim that abandoned resource and effectively control content served from your domain. This isn't theoretical—subdomain takeovers have enabled session hijacking, credential theft, and reputation damage for organizations ranging from startups to Fortune 500 companies.

Traditional vulnerability scanners often miss these issues because they're not looking at the relationship between DNS records and service availability. Manual verification requires security researchers to maintain knowledge of dozens of service-specific error messages and registration patterns. Subzy addresses this by automating the detection process: it performs HTTP fingerprinting against a curated database of known vulnerable service patterns. Built in Go for speed and portability, it transforms what could be hours of manual verification into a rapid, parallelized scan that integrates seamlessly into bug bounty workflows and security assessment pipelines.

Technical Insight

Subzy's architecture revolves around three core components: target ingestion, concurrent HTTP fingerprinting, and pattern matching against a vulnerability database. The tool reads subdomain targets either from command-line arguments or from files, then spins up a configurable number of goroutines (default 10) to probe each target concurrently. This design choice reflects Go's strength in handling I/O-bound operations where network latency dominates execution time.

The fingerprinting engine makes HTTP requests to each subdomain and captures multiple response characteristics: status codes, response headers, and body content. It then compares these against fingerprints sourced from the can-i-take-over-xyz repository, a community-maintained database of known vulnerable service patterns. Here's a simplified version of how the matching logic works:

func checkTakeover(target string, fingerprints []Fingerprint) (*Vulnerability, error) {
    resp, err := httpClient.Get("https://" + target)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    body, _ := ioutil.ReadAll(resp.Body)
    bodyString := string(body)

    for _, fp := range fingerprints {
        if matchesFingerprint(resp, bodyString, fp) {
            return &Vulnerability{
                Subdomain: target,
                Service:   fp.Service,
                Vulnerable: true,
            }, nil
        }
    }
    return nil, nil
}

func matchesFingerprint(resp *http.Response, body string, fp Fingerprint) bool {
    // Check status code if specified
    if fp.StatusCode != 0 && resp.StatusCode != fp.StatusCode {
        return false
    }
    
    // Check for presence of specific strings in response body
    for _, pattern := range fp.ContentPatterns {
        if strings.Contains(body, pattern) {
            return true
        }
    }
    
    return false
}

The concurrency model deserves attention. Subzy implements a worker pool pattern using a buffered channel to limit simultaneous HTTP requests. This prevents overwhelming target servers or local network resources while maintaining speed:

semaphore := make(chan struct{}, concurrencyLimit)
var wg sync.WaitGroup

for _, target := range targets {
    wg.Add(1)
    go func(domain string) {
        defer wg.Done()
        semaphore <- struct{}{}        // Acquire slot
        defer func() { <-semaphore }() // Release slot
        
        if vuln := checkTakeover(domain, fingerprints); vuln != nil {
            results <- vuln
        }
    }(target)
}
wg.Wait()

One architectural decision that significantly reduces maintenance burden is the reliance on the can-i-take-over-xyz fingerprint database. Rather than hardcoding service patterns into Subzy's codebase, the tool fetches updated fingerprints from this community resource. This means when new services become vulnerable or existing patterns change, Subzy benefits from community updates without requiring code changes. The fingerprint format typically includes service names, vulnerable status codes (404, 403, etc.), and specific error message strings that indicate an unclaimed resource.

The tool also includes practical configuration options: customizable timeouts prevent hanging on unresponsive targets, SSL verification can be disabled for internal testing environments, and output can be formatted for both human consumption and programmatic parsing. The --verify-ssl=false flag, for instance, proves valuable when testing internal subdomains with self-signed certificates, though this should obviously never be used against untrusted targets.

Subzy's value proposition lies in its focused scope. It doesn't attempt subdomain enumeration, DNS analysis, or comprehensive vulnerability assessment. Instead, it excels at a single task: given a list of subdomains, quickly determine which ones exhibit fingerprints matching known takeover vulnerabilities. This Unix philosophy approach—do one thing well—makes it an ideal component in larger security workflows where other tools handle enumeration (subfinder, amass) and validation (manual verification, nuclei).

Gotcha

The fingerprint matching approach that makes Subzy fast and maintainable also introduces reliability concerns. False positives occur when legitimate services happen to return error messages that match vulnerable patterns. For example, a properly configured service might display a generic "404 Not Found" page that coincidentally includes strings from Subzy's fingerprint database. False negatives happen when vulnerable services customize their error pages or when cloud providers update their default responses. I've encountered situations where an S3 bucket was genuinely vulnerable but returned a slightly modified error page that didn't match the fingerprint, requiring manual verification to confirm the takeover possibility.

Another practical limitation is Subzy's dependence on external tools for target discovery. It expects you to arrive with a list of subdomains already enumerated. In real-world bug bounty workflows, this means running passive enumeration tools like subfinder or Amass first, then feeding their output to Subzy. This isn't necessarily a weakness—separation of concerns is good architecture—but new security researchers might expect an all-in-one solution. Additionally, Subzy only performs HTTP-based checks. Certain takeover vectors that are primarily DNS-based, such as dangling NS records or certain types of CNAME misconfigurations, won't be detected. Tools like DNSTwist or manual DNS record analysis remain necessary for comprehensive coverage.

Verdict

Use if: You're conducting bug bounty research or security assessments and already have enumerated subdomains that need rapid takeover checking. Subzy excels when speed matters and you're scanning hundreds or thousands of subdomains—the concurrent Go architecture and minimal dependencies make it perfect for integration into automated security pipelines. It's particularly valuable if you're working across multiple targets and need a consistent methodology backed by community-maintained fingerprints. Skip if: You need comprehensive subdomain discovery as part of your workflow (chain it with enumeration tools instead), require definitive proof of exploitability beyond fingerprint matching (use it for triage, then manually verify), or are assessing non-HTTP takeover vectors like dangling NS records. Also skip if you're scanning very small subdomain lists where the overhead of installing and configuring a dedicated tool outweighs manually checking the handful of targets.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/cybersecurity/pentestpad-subzy.svg)](https://starlog.is/api/badge-click/cybersecurity/pentestpad-subzy)