Subdomain_recon.py: A SubDomain Reconnaissance Tool
Share this

Subdomain_recon.py: A SubDomain Reconnaissance Tool

Recently I participated in a hackathon building tools to help the blue team inventory our external attack surface. We are a large business with many global locations, datacenters, and devices, built up over time and via acquisition, so there is a lot to search for, and our internal inventories don't always keep up.

I decided to search for subdomain takeover and nameserver takeover risks across our infrastructure, and automate a way to maintain it going forward.

To accomplish this, I wrote a script to do the following:

  • Check for any unregistered nameservers in the domain chain to search for domain takeover attack opportunities.
  • Try to find all known subdomains of a given domain, using the excellent DNSDumpster.
  • Screenshot each subdomain for a quick visual inspection.
  • Collect shodan data for each subdomain infrastructure item found.
  • Write everything to an HTML report.

The subdomain_recon.py Tool

I recreated this script for general use and put it on my github.

Here is the script running against this website:

$ python subdomain_recon.py nullsweep.com
Checking nullsweep.com for subdomains and takeover opportunities...
Searching for unregistered name servers...
Checking name server f.gtld-servers.net. for com....
Checking name server j.gtld-servers.net. for com....
Checking name server i.gtld-servers.net. for com....
Checking name server a.gtld-servers.net. for com....
Checking name server h.gtld-servers.net. for com....
Checking name server l.gtld-servers.net. for com....
Checking name server g.gtld-servers.net. for com....
Checking name server b.gtld-servers.net. for com....
Checking name server e.gtld-servers.net. for com....
Checking name server m.gtld-servers.net. for com....
Checking name server c.gtld-servers.net. for com....
Checking name server k.gtld-servers.net. for com....
Checking name server d.gtld-servers.net. for com....
Checking name server josh.ns.cloudflare.com. for nullsweep.com....
Checking name server lara.ns.cloudflare.com. for nullsweep.com....
Searching for subdomains...
[verbose] Retrieved token: 4FhdLcwZDOwnpCqOXp5gzgVDzuz6Bv47v03z5eGUmc3J0L3yhEgSNMCRzxuIxZbE
list index out of range
	Found 3 subdomains
Wrote report to nullsweep.com.html

And a screenshot of the report. Shodan got my open ports wrong, probably because I am using CloudFlare:

subdomain_recon report against nullsweep

NameServer Takeover

For an excellent dive into nameserver takeover risks, I recommend reading the hackerblog entry Hijacking Broken Nameservers to Compromise Your Target

In this script, I iterate through each component of a domain name and list the nameservers for that component. For nullsweep.com, this breaks down into finding nameservers for the ".com" and "nullsweep.com" domains. Sites that have longer chains like mysite.hostingsite.region.com would have checks for ".com", ".region.com", "hostingsite.region.com" and "mysite.hostingsite.region.com", each of which may use different name servers.

For each name server found, we check it's registration status.

def list_ns(domain):
    ''' Return a list of name servers for the given domain'''
    q = dns.message.make_query(domain, dns.rdatatype.NS)
    r = dns.query.udp(q, name_server)
    if r.rcode() == dns.rcode.NOERROR and len(r.answer) > 0:
        return r.answer[0].items
    return []


def get_ns_registration_status(domain, depth=2):
    ''' Check registration status of all name servers.
    Depth of 2 will check TLD's such as .com or .info,
    3 or higher skips TLD
    '''
    domain = dns.name.from_text(domain)
    done = False
    nameservers = {}
    while not done:
        s = domain.split(depth)

        done = s[0].to_unicode() == u'@'
        subdomain = s[1]

        nss = list_ns(subdomain)
        for ns in nss:
            print(f"Checking name server {ns} for {subdomain}...")
            nameservers[ns.to_text()] = "registered"
            if can_register(ns):
                nameservers[ns.to_text()] = "UNREGISTERED"
        depth += 1
    return nameservers

Find all Subdomains

There are tons of great tools out there for doing DNS busting (brute forcing DNS and attempting zone transfers), but I wanted to use a service. Out of all the subdomain search tools I sampled, DNSDumpster had by far the best results, even if sometimes they were out of date. Even better, they have an (unofficial) python package.

Finding all subdomains is as simple as quering the service:

def find_subdomains(domain):
    results = DNSDumpsterAPI({'verbose': True}).search(domain)
    subdomains = [domain_details(domain)]
    if len(results) > 0:
        subdomains.extend(results['dns_records']['host'])
    return subdomains

There are other good tools out there that check for subdomain takeover opportunities (when a subdomain points to a third party service that is no longer owned by the domain owner, such as a released S3 bucket, or closed github account).

You can find a good list of services that may allow takeover on github, and a tool called subjack, which has some overlap with the one I wrote. Seeing a screenshot of any of those services likely means a takeover could be possible.

Integrating Shodan

Finally, I wanted to see what, if anything, shodan had picked up about the services found. Shodan charges for larger result sets, but by quering a specific IP address, we can leverage the API with a free account just fine.

def shodan_data(ip):
    if api is None:
        return ("", False)
    try:
        host = api.host(ip)
        return (host, True)
    except shodan.APIError as e:
        return (str(e), False)

In the above function, I handle the case that the user has not passed in an API key, in which case shodan report data will be blank, and the case where shodan errors. The most common cause of this was shodan having no information about the provided IP address.

Final Thoughts

The script turned out to be quite useful! It yields a fair amount of data that is quick to visually process and follow up on.

I would love to hear from the community on other techniques or tools I missed!

Comments

This comment system is self hosted using the Mozilla Coral talk platform, and not connected with any third parties who might collect data. At any time, you can completely delete all comments and data stored, including your email address. I will never send you emails aside from password reset emails you request.