From 0271f4c63f52a7ff10414f834c86bd295a3caa59 Mon Sep 17 00:00:00 2001 From: fleetcaptain Date: Tue, 16 Jan 2018 21:24:14 -0800 Subject: [PATCH] Update turbolist3r.py Changed the analysis DNS requests to use dnslib for better subdomain takeover detection. The dns.resolver library could miss out on some subdomains pointing to vulnerable cloud services that returned NXDOMAIN status since the cloud service did not exist but the target domain's pointer record still existed and pointed to it. --- turbolist3r.py | 94 ++++++++++++++++++++++++++------------------------ 1 file changed, 49 insertions(+), 45 deletions(-) diff --git a/turbolist3r.py b/turbolist3r.py index 32a2df8..d1c0590 100644 --- a/turbolist3r.py +++ b/turbolist3r.py @@ -1,13 +1,16 @@ #!/usr/bin/env python # coding: utf-8 -# Turbolist3r v1.0 +# Turbolist3r v0.2 # By Carl Pearson - github.com/fleetcaptain # Based on Sublist3r code created by Ahmed Aboul-Ela - twitter.com/aboul3la -# Tested on Ubuntu Linux 16.10 # # Changes to Turbolist3r from Sublist3r: # - check subdomain for text "From http://PTRarchive.com: " and remove it (otherwise it ends up in the output and can impede automated analysis with other tools) # - added functionality to query found subdomains, record answer, and catagorize as A or CNAME record. Speeds up subdomain takeover analysis as CNAME records and the services they point to are collected and displayed +# +# TODO - merge Sublist3r dns requests with dnslib to avoid duplication of dns libraries +# + # modules in standard library import re @@ -23,9 +26,13 @@ import socket from collections import Counter # external modules -from subbrute import subbrute +#from subbrute import subbrute import dns.resolver import requests +# import dnslib, which provides better features compared to dns.resolver for finding subdomains +# for example, a return status of NXDOMAIN causes an exception with dns.resolver, but dnslib allows +# us to easily capture the reply, which could indicate the precsence of subdomain takeover +import dnslib # Python 2.x and 3.x compatiablity if sys.version > '3': @@ -1023,49 +1030,47 @@ def main(domain, threads, savefile, ports, silent, verbose, enable_bruteforce, e cnames = ['== CNAME records =='] ahosts = ['== A records =='] def lookup(guess, name_server): - Resolver = dns.resolver.Resolver() - Resolver.timeout = 3 - Resolver.lifetime = 3 - Resolver.nameservers = [name_server] - answer = None + #print 'Trying ' + guess + ' at ' + name_server + use_tcp = False + response = None + failed = False + record_type = "" + record_value = "" + query = dnslib.DNSRecord.question(guess, 'ANY') try: - # obtain the DNS reply in DIG format, convert to string, and split newlines into an array - answer = str(Resolver.query(guess).response).split('\n') + response_q = query.send(name_server, 53, use_tcp, timeout = 3) + if response_q: + response = dnslib.DNSRecord.parse(response_q) + except KeyboardInterrupt: + print 'User exit' + exit() except: - return "ERROR", "e" - - ''' - the answer in DIG format looks like this - - id 35423 - opcode QUERY - rcode NOERROR - flags QR RD RA - ;QUESTION - myservice.example.com. IN A - ;ANSWER - myservice.example.com. 299 IN CNAME myservice.cloudservice.net. - myservice.cloudservice.net. 9 IN A 500.600.700.800 - ;AUTHORITY - ;ADDITIONAL - - we grab the first line after ";ANSWER" - it's the first answer and the one we care about. May be the only answer depending on the specific host (like if it's an A record only 1 IP may be returned) - ''' - answerline = "" - for x in range(0, len(answer)): # for each line - if answer[x] == ";ANSWER": - answerline = answer[x + 1] # first answer - break - lineitems = answerline.split(' ') - host = lineitems[len(lineitems) - 1] # host is the last line - # determine if this is a CNAME or A record. A records can be interesting to find vulnerable hosts and CNAME - # records can be interesting for subdomain takeover - for item in lineitems: - if item == 'CNAME': - host = host[:-1] # remove the trailing period - return "CNAME", host - elif item == 'A': - return "A", host + # probably socket timed out + print "ERROR - possible socket timeout when trying " + guess + pass + if response: + #print response + rcode = dnslib.RCODE[response.header.rcode] + if rcode == 'NOERROR' or rcode == 'NXDOMAIN': + # success, this is a valid subdomain + for r in response.rr: + rtype = None + try: + rtype = str(dnslib.QTYPE[r.rtype]) + except: + rtype = str(r.rtype) + #print rtype + + if (rtype == 'CNAME'): + #print r.rdata + record_type = 'CNAME' + record_value = str(r.rdata) + elif (rtype == 'A' or rtype == 'AAAA'): + record_type = 'A' + record_value = str(r.rdata) + else: + print "ERROR - returned stats " + rcode + " when trying " + guess + return record_type, record_value @@ -1130,4 +1135,3 @@ if __name__ == "__main__": #print "" # save the analysis to a file. Merge the arrays into one list for easier reading write_file(analysis, ahosts + ["\n"] + cnames) -