How to Programmatically List All CNAME Records for a Domain When dig/nslookup Fail


3 views

When working with DNS configurations, you might encounter situations where standard command-line tools like dig, nslookup, or host don't reveal all CNAME records. This often happens when dealing with:

  • Complex DNS architectures with multiple layers of redirection
  • Private or internal DNS zones
  • DNS providers that implement rate limiting
  • Domains using modern DNS features like CNAME flattening

Here are several reliable methods to enumerate CNAME records programmatically:

1. Using DNS Zone Transfer (AXFR)

If the nameserver allows zone transfers (rare in production environments), you can try:

dig @ns1.example.com example.com AXFR | grep CNAME

2. Cloud Provider APIs

For domains hosted on services like AWS Route 53:

import boto3

client = boto3.client('route53')
response = client.list_resource_record_sets(
    HostedZoneId='Z23ABC4XYZL05B',
    StartRecordName='subdomain.example.com',
    StartRecordType='CNAME'
)

for record in response['ResourceRecordSets']:
    if record['Type'] == 'CNAME':
        print(record['Name'], '->', record['ResourceRecords'][0]['Value'])

3. DNS Query Brute-forcing

For situations where you need to discover subdomains:

import dns.resolver

base_domain = "example.com"
common_subdomains = ["www", "mail", "web", "api", "dev"]

for sub in common_subdomains:
    try:
        answers = dns.resolver.resolve(f"{sub}.{base_domain}", 'CNAME')
        for rdata in answers:
            print(f"{sub}.{base_domain} -> {rdata.target}")
    except dns.resolver.NoAnswer:
        continue

4. Using Specialized DNS Tools

Consider tools like:

  • dnsrecon for comprehensive DNS enumeration
  • fierce for brute-force subdomain discovery
  • sublist3r for OSINT-based subdomain discovery

When querying DNS programmatically, always implement:

import time
import random

def query_with_backoff(domain, record_type):
    max_retries = 3
    for attempt in range(max_retries):
        try:
            return dns.resolver.resolve(domain, record_type)
        except dns.resolver.NXDOMAIN:
            return None
        except (dns.resolver.Timeout, dns.resolver.NoNameservers):
            if attempt < max_retries - 1:
                sleep_time = random.uniform(1, 3) * (attempt + 1)
                time.sleep(sleep_time)
                continue
            raise

For domains with CNAME chains:

def resolve_cname_chain(domain, max_hops=10):
    chain = []
    current_domain = domain
    for _ in range(max_hops):
        try:
            answer = dns.resolver.resolve(current_domain, 'CNAME')
            target = str(answer[0].target).rstrip('.')
            chain.append((current_domain, target))
            current_domain = target
        except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN):
            break
    return chain

Remember that DNS records can be cached at multiple levels (local resolver, ISP, etc.), so your program should account for possible stale data during development and testing.


When working with DNS configurations, developers often need to enumerate all CNAME records for troubleshooting or infrastructure management. Standard tools like dig, nslookup, and host don't directly provide this capability because:

  • CNAMEs are alias records that point to other domain names
  • Most tools only show CNAMEs when querying specific subdomains
  • DNS servers typically don't support wildcard queries for record types

1. Using dig with AXFR (Zone Transfer)

If you have proper permissions, perform a zone transfer:

dig example.com AXFR

Note: This requires zone transfer to be allowed on the DNS server (rare in production environments).

2. DNS Enumeration with dnsrecon

Security tools can help discover records:

dnsrecon -d example.com -t std

This will list various record types including CNAMEs.

3. Programmatic Approach Using Python

Here's a script using the dnspython library:

import dns.resolver

def find_cnames(domain):
    try:
        answers = dns.resolver.resolve(domain, 'CNAME')
        for rdata in answers:
            print(f"{domain} CNAME {rdata.target}")
    except dns.resolver.NoAnswer:
        pass
    except dns.resolver.NXDOMAIN:
        print(f"{domain} does not exist")

# Example usage
domains = ['www.example.com', 'mail.example.com', 'api.example.com']
for domain in domains:
    find_cnames(domain)
  • For cloud providers (AWS Route 53, Cloudflare), use their APIs for complete record listings
  • Remember that CNAME chains can exist (CNAME pointing to another CNAME)
  • Some DNS providers limit the number of queries per second

Consider these specialized utilities:

  • fierce - DNS reconnaissance tool
  • dnsenum - Perl script for DNS enumeration
  • massdns - High-performance bulk resolver