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.
This commit is contained in:
fleetcaptain 2018-01-16 21:24:14 -08:00 committed by GitHub
parent 99086ac1e0
commit 0271f4c63f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 49 additions and 45 deletions

View File

@ -1,13 +1,16 @@
#!/usr/bin/env python #!/usr/bin/env python
# coding: utf-8 # coding: utf-8
# Turbolist3r v1.0 # Turbolist3r v0.2
# By Carl Pearson - github.com/fleetcaptain # By Carl Pearson - github.com/fleetcaptain
# Based on Sublist3r code created by Ahmed Aboul-Ela - twitter.com/aboul3la # Based on Sublist3r code created by Ahmed Aboul-Ela - twitter.com/aboul3la
# Tested on Ubuntu Linux 16.10
# #
# Changes to Turbolist3r from Sublist3r: # 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) # - 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 # - 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 # modules in standard library
import re import re
@ -23,9 +26,13 @@ import socket
from collections import Counter from collections import Counter
# external modules # external modules
from subbrute import subbrute #from subbrute import subbrute
import dns.resolver import dns.resolver
import requests 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 # Python 2.x and 3.x compatiablity
if sys.version > '3': if sys.version > '3':
@ -1023,49 +1030,47 @@ def main(domain, threads, savefile, ports, silent, verbose, enable_bruteforce, e
cnames = ['== CNAME records =='] cnames = ['== CNAME records ==']
ahosts = ['== A records =='] ahosts = ['== A records ==']
def lookup(guess, name_server): def lookup(guess, name_server):
Resolver = dns.resolver.Resolver() #print 'Trying ' + guess + ' at ' + name_server
Resolver.timeout = 3 use_tcp = False
Resolver.lifetime = 3 response = None
Resolver.nameservers = [name_server] failed = False
answer = None record_type = ""
record_value = ""
query = dnslib.DNSRecord.question(guess, 'ANY')
try: try:
# obtain the DNS reply in DIG format, convert to string, and split newlines into an array response_q = query.send(name_server, 53, use_tcp, timeout = 3)
answer = str(Resolver.query(guess).response).split('\n') if response_q:
response = dnslib.DNSRecord.parse(response_q)
except KeyboardInterrupt:
print 'User exit'
exit()
except: except:
return "ERROR", "e" # probably socket timed out
print "ERROR - possible socket timeout when trying " + guess
''' pass
the answer in DIG format looks like this if response:
#print response
id 35423 rcode = dnslib.RCODE[response.header.rcode]
opcode QUERY if rcode == 'NOERROR' or rcode == 'NXDOMAIN':
rcode NOERROR # success, this is a valid subdomain
flags QR RD RA for r in response.rr:
;QUESTION rtype = None
myservice.example.com. IN A try:
;ANSWER rtype = str(dnslib.QTYPE[r.rtype])
myservice.example.com. 299 IN CNAME myservice.cloudservice.net. except:
myservice.cloudservice.net. 9 IN A 500.600.700.800 rtype = str(r.rtype)
;AUTHORITY #print rtype
;ADDITIONAL
if (rtype == 'CNAME'):
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) #print r.rdata
''' record_type = 'CNAME'
answerline = "" record_value = str(r.rdata)
for x in range(0, len(answer)): # for each line elif (rtype == 'A' or rtype == 'AAAA'):
if answer[x] == ";ANSWER": record_type = 'A'
answerline = answer[x + 1] # first answer record_value = str(r.rdata)
break else:
lineitems = answerline.split(' ') print "ERROR - returned stats " + rcode + " when trying " + guess
host = lineitems[len(lineitems) - 1] # host is the last line return record_type, record_value
# 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
@ -1130,4 +1135,3 @@ if __name__ == "__main__":
#print "" #print ""
# save the analysis to a file. Merge the arrays into one list for easier reading # save the analysis to a file. Merge the arrays into one list for easier reading
write_file(analysis, ahosts + ["\n"] + cnames) write_file(analysis, ahosts + ["\n"] + cnames)