Merge pull request #3 from Damian89/master

Major bugfixes
This commit is contained in:
fleetcaptain 2018-07-02 14:25:43 -07:00 committed by GitHub
commit 9101a974d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 198 additions and 202 deletions

View File

@ -163,7 +163,7 @@ class verify_nameservers(multiprocessing.Process):
#We found atleast one wildcard, look for more. #We found atleast one wildcard, look for more.
looking_for_wildcards = True looking_for_wildcards = True
except Exception as e: except Exception as e:
if type(e) == dns.resolver.NXDOMAIN or type(e) == dns.name.EmptyLabel: if type(e) == dns.resolver.NXDOMAIN or type(e) == dns.name.EmptyLabel or type(e) == dns.resolver.NoAnswer:
#not found #not found
return True return True
else: else:
@ -194,7 +194,7 @@ class lookup(multiprocessing.Process):
ret = [] ret = []
try: try:
ret = [self.resolver_q.get_nowait()] ret = [self.resolver_q.get_nowait()]
if ret == False: if ret == [False]:
#Queue is empty, inform the rest. #Queue is empty, inform the rest.
self.resolver_q.put(False) self.resolver_q.put(False)
ret = [] ret = []
@ -205,7 +205,7 @@ class lookup(multiprocessing.Process):
def get_ns_blocking(self): def get_ns_blocking(self):
ret = [] ret = []
ret = [self.resolver_q.get()] ret = [self.resolver_q.get()]
if ret == False: if ret == [False]:
trace("get_ns_blocking - Resolver list is empty.") trace("get_ns_blocking - Resolver list is empty.")
#Queue is empty, inform the rest. #Queue is empty, inform the rest.
self.resolver_q.put(False) self.resolver_q.put(False)

View File

@ -26,7 +26,7 @@ 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 # import dnslib, which provides better features compared to dns.resolver for finding subdomains
@ -46,6 +46,7 @@ else:
# there's also an option to disable the SSL warning: # there's also an option to disable the SSL warning:
try: try:
import requests.packages.urllib3 import requests.packages.urllib3
requests.packages.urllib3.disable_warnings() requests.packages.urllib3.disable_warnings()
except: except:
pass pass
@ -55,7 +56,6 @@ is_windows = sys.platform.startswith('win')
global debug global debug
# Console Colors # Console Colors
if is_windows: if is_windows:
# Windows deserve coloring too :D # Windows deserve coloring too :D
@ -63,12 +63,13 @@ if is_windows:
Y = '\033[93m' # yellow Y = '\033[93m' # yellow
B = '\033[94m' # blue B = '\033[94m' # blue
R = '\033[91m' # red R = '\033[91m' # red
W = '\033[0m' # white W = '\033[0m' # white
try: try:
import win_unicode_console , colorama import win_unicode_console, colorama
win_unicode_console.enable() win_unicode_console.enable()
colorama.init() colorama.init()
#Now the unicode will work ^_^ # Now the unicode will work ^_^
except: except:
print("[!] Error: Coloring libraries not installed ,no coloring will be used [Check the readme]") print("[!] Error: Coloring libraries not installed ,no coloring will be used [Check the readme]")
G = Y = B = R = W = G = Y = B = R = W = '' G = Y = B = R = W = G = Y = B = R = W = ''
@ -79,7 +80,7 @@ else:
Y = '\033[93m' # yellow Y = '\033[93m' # yellow
B = '\033[94m' # blue B = '\033[94m' # blue
R = '\033[91m' # red R = '\033[91m' # red
W = '\033[0m' # white W = '\033[0m' # white
def banner(): def banner():
@ -94,6 +95,7 @@ def banner():
# Forked by Carl Pearson - github.com/fleetcaptain # Forked by Carl Pearson - github.com/fleetcaptain
""" % (R, W, Y)) """ % (R, W, Y))
def parser_error(errmsg): def parser_error(errmsg):
banner() banner()
print("Usage: python " + sys.argv[0] + " [Options] use -h for help") print("Usage: python " + sys.argv[0] + " [Options] use -h for help")
@ -109,8 +111,10 @@ def parse_args():
parser.add_argument('-d', '--domain', help="Domain name to enumerate it's subdomains", required=True) parser.add_argument('-d', '--domain', help="Domain name to enumerate it's subdomains", required=True)
parser.add_argument('-b', '--bruteforce', help='Enable the subbrute bruteforce module', nargs='?', default=False) parser.add_argument('-b', '--bruteforce', help='Enable the subbrute bruteforce module', nargs='?', default=False)
parser.add_argument('-p', '--ports', help='Scan the found subdomains against specified tcp ports') parser.add_argument('-p', '--ports', help='Scan the found subdomains against specified tcp ports')
parser.add_argument('-v', '--verbose', help='Enable Verbosity and display results in realtime', nargs='?', default=False) parser.add_argument('-v', '--verbose', help='Enable Verbosity and display results in realtime', nargs='?',
parser.add_argument('-t', '--threads', help='Number of threads to use for subbrute bruteforce', type=int, default=30) default=False)
parser.add_argument('-t', '--threads', help='Number of threads to use for subbrute bruteforce', type=int,
default=30)
parser.add_argument('-e', '--engines', help='Specify a comma-separated list of search engines') parser.add_argument('-e', '--engines', help='Specify a comma-separated list of search engines')
parser.add_argument('-o', '--output', help='Save just domain names to specified text file') parser.add_argument('-o', '--output', help='Save just domain names to specified text file')
parser.add_argument('-a', '--analysis', help='Do analysis of the results and save to specified text file') parser.add_argument('-a', '--analysis', help='Do analysis of the results and save to specified text file')
@ -163,6 +167,12 @@ class enumratorBase(object):
self.engine_name = engine_name self.engine_name = engine_name
self.silent = silent self.silent = silent
self.verbose = verbose self.verbose = verbose
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.8',
'Accept-Encoding': 'gzip',
}
self.print_banner() self.print_banner()
def print_(self, text): def print_(self, text):
@ -176,17 +186,10 @@ class enumratorBase(object):
return return
def send_req(self, query, page_no=1): def send_req(self, query, page_no=1):
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:38.0) Gecko/20100101 Firefox/38.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en-GB,en;q=0.5',
'Accept-Encoding': 'gzip, deflate',
'Connection': 'keep-alive',
}
url = self.base_url.format(query=query, page_no=page_no) url = self.base_url.format(query=query, page_no=page_no)
try: try:
resp = self.session.get(url, headers=headers, timeout=self.timeout) resp = self.session.get(url, headers=self.headers, timeout=self.timeout)
except Exception: except Exception:
resp = None resp = None
return self.get_response(resp) return self.get_response(resp)
@ -259,7 +262,7 @@ class enumratorBase(object):
retries += 1 retries += 1
page_no = self.get_page(page_no) page_no = self.get_page(page_no)
# make another retry maybe it isn't the last page # make another retry maybe it isn't the last page
if retries >= 3: if retries >= 3:
return self.subdomains return self.subdomains
@ -270,7 +273,8 @@ class enumratorBase(object):
class enumratorBaseThreaded(multiprocessing.Process, enumratorBase): class enumratorBaseThreaded(multiprocessing.Process, enumratorBase):
def __init__(self, base_url, engine_name, domain, subdomains=None, q=None, lock=threading.Lock(), silent=False, verbose=True): def __init__(self, base_url, engine_name, domain, subdomains=None, q=None, lock=threading.Lock(), silent=False,
verbose=True):
subdomains = subdomains or [] subdomains = subdomains or []
enumratorBase.__init__(self, base_url, engine_name, domain, subdomains, silent=silent, verbose=verbose) enumratorBase.__init__(self, base_url, engine_name, domain, subdomains, silent=silent, verbose=verbose)
multiprocessing.Process.__init__(self) multiprocessing.Process.__init__(self)
@ -291,7 +295,8 @@ class GoogleEnum(enumratorBaseThreaded):
self.engine_name = "Google" self.engine_name = "Google"
self.MAX_DOMAINS = 11 self.MAX_DOMAINS = 11
self.MAX_PAGES = 200 self.MAX_PAGES = 200
super(GoogleEnum, self).__init__(base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose) super(GoogleEnum, self).__init__(base_url, self.engine_name, domain, subdomains, q=q, silent=silent,
verbose=verbose)
self.q = q self.q = q
return return
@ -313,7 +318,7 @@ class GoogleEnum(enumratorBaseThreaded):
return links_list return links_list
def check_response_errors(self, resp): def check_response_errors(self, resp):
if 'Our systems have detected unusual traffic' in resp: if (type(resp) is str or type(resp) is unicode) and 'Our systems have detected unusual traffic' in resp:
self.print_(R + "[!] Error: Google probably now is blocking our requests" + W) self.print_(R + "[!] Error: Google probably now is blocking our requests" + W)
self.print_(R + "[~] Finished now the Google Enumeration ..." + W) self.print_(R + "[~] Finished now the Google Enumeration ..." + W)
return False return False
@ -340,7 +345,8 @@ class YahooEnum(enumratorBaseThreaded):
self.engine_name = "Yahoo" self.engine_name = "Yahoo"
self.MAX_DOMAINS = 10 self.MAX_DOMAINS = 10
self.MAX_PAGES = 0 self.MAX_PAGES = 0
super(YahooEnum, self).__init__(base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose) super(YahooEnum, self).__init__(base_url, self.engine_name, domain, subdomains, q=q, silent=silent,
verbose=verbose)
self.q = q self.q = q
return return
@ -391,12 +397,13 @@ class AskEnum(enumratorBaseThreaded):
self.engine_name = "Ask" self.engine_name = "Ask"
self.MAX_DOMAINS = 11 self.MAX_DOMAINS = 11
self.MAX_PAGES = 0 self.MAX_PAGES = 0
enumratorBaseThreaded.__init__(self, base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose) enumratorBaseThreaded.__init__(self, base_url, self.engine_name, domain, subdomains, q=q, silent=silent,
verbose=verbose)
self.q = q self.q = q
return return
def extract_domains(self, resp): def extract_domains(self, resp):
link_regx = re.compile('<p class="web-result-url">(.*?)</p>') link_regx = re.compile('<td>(.*?)</td>', re.IGNORECASE)
try: try:
links_list = link_regx.findall(resp) links_list = link_regx.findall(resp)
for link in links_list: for link in links_list:
@ -477,7 +484,8 @@ class BaiduEnum(enumratorBaseThreaded):
self.engine_name = "Baidu" self.engine_name = "Baidu"
self.MAX_DOMAINS = 2 self.MAX_DOMAINS = 2
self.MAX_PAGES = 760 self.MAX_PAGES = 760
enumratorBaseThreaded.__init__(self, base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose) enumratorBaseThreaded.__init__(self, base_url, self.engine_name, domain, subdomains, q=q, silent=silent,
verbose=verbose)
self.querydomain = self.domain self.querydomain = self.domain
self.q = q self.q = q
return return
@ -535,21 +543,16 @@ class NetcraftEnum(enumratorBaseThreaded):
self.base_url = 'https://searchdns.netcraft.com/?restriction=site+ends+with&host={domain}' self.base_url = 'https://searchdns.netcraft.com/?restriction=site+ends+with&host={domain}'
self.engine_name = "Netcraft" self.engine_name = "Netcraft"
self.lock = threading.Lock() self.lock = threading.Lock()
super(NetcraftEnum, self).__init__(self.base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose) super(NetcraftEnum, self).__init__(self.base_url, self.engine_name, domain, subdomains, q=q, silent=silent,
verbose=verbose)
self.q = q self.q = q
return return
def req(self, url, cookies=None): def req(self, url, cookies=None):
cookies = cookies or {} cookies = cookies or {}
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:38.0) Gecko/20100101 Firefox/40.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en-GB,en;q=0.5',
'Accept-Encoding': 'gzip, deflate',
}
try: try:
resp = self.session.get(url, headers=headers, timeout=self.timeout, cookies=cookies) resp = self.session.get(url, headers=self.headers, timeout=self.timeout, cookies=cookies)
except Exception as e: except Exception as e:
self.print_(e) self.print_(e)
resp = None resp = None
@ -559,7 +562,7 @@ class NetcraftEnum(enumratorBaseThreaded):
link_regx = re.compile('<A href="(.*?)"><b>Next page</b></a>') link_regx = re.compile('<A href="(.*?)"><b>Next page</b></a>')
link = link_regx.findall(resp) link = link_regx.findall(resp)
link = re.sub('host=.*?%s' % self.domain, 'host=%s' % self.domain, link[0]) link = re.sub('host=.*?%s' % self.domain, 'host=%s' % self.domain, link[0])
url = 'http://searchdns.netcraft.com' + link url = 'https://searchdns.netcraft.com' + link
return url return url
def create_cookies(self, cookie): def create_cookies(self, cookie):
@ -615,13 +618,14 @@ class DNSdumpster(enumratorBaseThreaded):
self.threads = 70 self.threads = 70
self.lock = threading.BoundedSemaphore(value=self.threads) self.lock = threading.BoundedSemaphore(value=self.threads)
self.q = q self.q = q
super(DNSdumpster, self).__init__(base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose) super(DNSdumpster, self).__init__(base_url, self.engine_name, domain, subdomains, q=q, silent=silent,
verbose=verbose)
return return
def check_host(self, host): def check_host(self, host):
is_valid = False is_valid = False
Resolver = dns.resolver.Resolver() Resolver = dns.resolver.Resolver()
Resolver.nameservers = ['8.8.8.8', '8.8.4.4'] Resolver.nameservers = ['8.8.8.8', '8.8.4.4', '1.1.1.1', '1.0.0.1']
self.lock.acquire() self.lock.acquire()
try: try:
ip = Resolver.query(host, 'A')[0].to_text() ip = Resolver.query(host, 'A')[0].to_text()
@ -637,14 +641,8 @@ class DNSdumpster(enumratorBaseThreaded):
def req(self, req_method, url, params=None): def req(self, req_method, url, params=None):
params = params or {} params = params or {}
headers = { headers = dict(self.headers)
'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:38.0) Gecko/20100101 Firefox/40.0', headers['Referer'] = 'https://dnsdumpster.com'
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en-GB,en;q=0.5',
'Accept-Encoding': 'gzip, deflate',
'Referer': 'https://dnsdumpster.com'
}
try: try:
if req_method == 'GET': if req_method == 'GET':
resp = self.session.get(url, headers=headers, timeout=self.timeout) resp = self.session.get(url, headers=headers, timeout=self.timeout)
@ -698,20 +696,15 @@ class Virustotal(enumratorBaseThreaded):
self.engine_name = "Virustotal" self.engine_name = "Virustotal"
self.lock = threading.Lock() self.lock = threading.Lock()
self.q = q self.q = q
super(Virustotal, self).__init__(base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose) super(Virustotal, self).__init__(base_url, self.engine_name, domain, subdomains, q=q, silent=silent,
verbose=verbose)
return return
# the main send_req need to be rewritten # the main send_req need to be rewritten
def send_req(self, url): def send_req(self, url):
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:38.0) Gecko/20100101 Firefox/40.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en-GB,en;q=0.5',
'Accept-Encoding': 'gzip, deflate',
}
try: try:
resp = self.session.get(url, headers=headers, timeout=self.timeout) resp = self.session.get(url, headers=self.headers, timeout=self.timeout)
except Exception as e: except Exception as e:
self.print_(e) self.print_(e)
resp = None resp = None
@ -748,19 +741,14 @@ class ThreatCrowd(enumratorBaseThreaded):
self.engine_name = "ThreatCrowd" self.engine_name = "ThreatCrowd"
self.lock = threading.Lock() self.lock = threading.Lock()
self.q = q self.q = q
super(ThreatCrowd, self).__init__(base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose) super(ThreatCrowd, self).__init__(base_url, self.engine_name, domain, subdomains, q=q, silent=silent,
verbose=verbose)
return return
def req(self, url): def req(self, url):
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:38.0) Gecko/20100101 Firefox/40.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en-GB,en;q=0.5',
'Accept-Encoding': 'gzip, deflate',
}
try: try:
resp = self.session.get(url, headers=headers, timeout=self.timeout) resp = self.session.get(url, headers=self.headers, timeout=self.timeout)
except Exception: except Exception:
resp = None resp = None
@ -800,19 +788,14 @@ class CrtSearch(enumratorBaseThreaded):
self.engine_name = "SSL Certificates" self.engine_name = "SSL Certificates"
self.lock = threading.Lock() self.lock = threading.Lock()
self.q = q self.q = q
super(CrtSearch, self).__init__(base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose) super(CrtSearch, self).__init__(base_url, self.engine_name, domain, subdomains, q=q, silent=silent,
verbose=verbose)
return return
def req(self, url): def req(self, url):
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:38.0) Gecko/20100101 Firefox/40.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en-GB,en;q=0.5',
'Accept-Encoding': 'gzip, deflate',
}
try: try:
resp = self.session.get(url, headers=headers, timeout=self.timeout) resp = self.session.get(url, headers=self.headers, timeout=self.timeout)
except Exception: except Exception:
resp = None resp = None
@ -835,7 +818,7 @@ class CrtSearch(enumratorBaseThreaded):
continue continue
if '@' in subdomain: if '@' in subdomain:
subdomain = subdomain[subdomain.find('@')+1:] subdomain = subdomain[subdomain.find('@') + 1:]
if subdomain not in self.subdomains and subdomain != self.domain: if subdomain not in self.subdomains and subdomain != self.domain:
if self.verbose: if self.verbose:
@ -852,19 +835,32 @@ class PassiveDNS(enumratorBaseThreaded):
self.engine_name = "PassiveDNS" self.engine_name = "PassiveDNS"
self.lock = threading.Lock() self.lock = threading.Lock()
self.q = q self.q = q
super(PassiveDNS, self).__init__(base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose) super(PassiveDNS, self).__init__(base_url, self.engine_name, domain, subdomains, q=q, silent=silent,
verbose=verbose)
return return
def get_agent(self, ua=None):
agents_url = 'http://www.webuseragents.com/recent'
try:
resp = session.get(agents_url, headers=self.headers, timeout=self.timeout)
agents_list = self.get_response(resp)
agents_regex = re.compile('<a href="/ua/.*?>(.*)</a>')
agents = agents_regex.findall(agents_list)
ua = random.choice(agents)
except Exception as e:
pass
return ua
def req(self, url): def req(self, url):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en-GB,en;q=0.5',
'Accept-Encoding': 'gzip, deflate',
}
try: try:
resp = self.session.get(url, headers=headers, timeout=self.timeout) if self.get_agent():
self.headers['User-Agent'] = self.get_agent()
resp = self.session.get(url, headers=self.headers, timeout=self.timeout)
except Exception as e: except Exception as e:
self.print_(e) self.print_(e)
resp = None resp = None
@ -1002,7 +998,8 @@ def main(domain, threads, savefile, ports, silent, verbose, enable_bruteforce, e
process_count = threads process_count = threads
output = False output = False
json_output = False json_output = False
bruteforce_list = subbrute.print_target(parsed_domain.netloc, record_type, subs, resolvers, process_count, output, json_output, search_list, verbose) bruteforce_list = subbrute.print_target(parsed_domain.netloc, record_type, subs, resolvers, process_count,
output, json_output, search_list, verbose)
subdomains = search_list.union(bruteforce_list) subdomains = search_list.union(bruteforce_list)
@ -1025,91 +1022,91 @@ def main(domain, threads, savefile, ports, silent, verbose, enable_bruteforce, e
elif not silent: elif not silent:
for subdomain in subdomains: for subdomain in subdomains:
# Code modified - remove 'From http://PTRarchive.com: ' which shows up in some results # Code modified - remove 'From http://PTRarchive.com: ' which shows up in some results
subdomain = subdomain.replace("From http://PTRarchive.com: ", "") subdomain = subdomain.replace("From http://PTRarchive.com: ", "")
print(G + subdomain + W) print(G + subdomain + W)
return subdomains return subdomains
# Method code added # Method code added
cnames = ['== CNAME records =='] cnames = ['== CNAME records ==']
ahosts = ['== A records =='] ahosts = ['== A records ==']
def lookup(guess, name_server): def lookup(guess, name_server):
if (debug): if (debug):
print 'Trying ' + guess + ' at ' + name_server print('Trying ' + guess + ' at ' + name_server)
use_tcp = False
response = None
failed = False
record_type = ""
record_value = ""
query = dnslib.DNSRecord.question(guess)
try:
response_q = query.send(name_server, 53, use_tcp, timeout = 3)
if response_q:
if (debug):
print "response_q: " + response_q
response = dnslib.DNSRecord.parse(response_q)
except KeyboardInterrupt:
print 'User exit'
exit()
except:
# probably socket timed out
print "ERROR - possible socket timeout when trying " + guess
pass
if response:
if debug:
print "Decoded response:\n" + str(response) + "\n"
rcode = dnslib.RCODE[response.header.rcode]
if rcode == 'NOERROR' or rcode == 'NXDOMAIN':
# success, this is a valid subdomain
if debug:
print "response.rr:\n" + str(response.rr) + "\n"
for r in response.rr:
# note that we are looping through each piece of the answer but we only return from this method once... we must pick what we return verrry carefully, see explantion below
if debug:
print "r:\n" + str(r) + "\n"
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)
'''
*Why the following break keyword is here
So if we submit a query and get back a CNAME, the response contains the CNAME record and also the
CNAME records' record, and so on down to the A or AAAA record with the final IP address for the
query we submitted.
Example:
;; ANSWER SECTION:
support.indeed.com. 7200 IN CNAME indeed.zendesk.com.
indeed.zendesk.com. 900 IN A 52.34.200.91
indeed.zendesk.com. 900 IN A 35.167.245.158
indeed.zendesk.com. 900 IN A 34.216.174.56
That's fine, and we loop through each of these "answers" in the 'for r in response.rr' loop.
PROBLEM: we were overwriting the value of record_type and record_value each time around the loop.
So if the first 'r' was a CNAME record we wouldn't know about it since the subsequent A/AAAA record would update 'record_type
and record_value to reflect the A record not the first CNAME record!
The user would see the subdomain as a plain A record but that wasn't true... depending on if the CNAME was processed first.
Since this is structered as a method call that returns a single record type and value pair, and because
we care more about the CNAME record our query points to than the IP (at least for subdomain takeover recon), we will
halt analysis and return the CNAME data if we encounter a CNAME record. We care that a domain name points to an
AWS bucket, for example, not the particular Amazon IP it ultimately has to talk to.
'''
break
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
use_tcp = False
response = None
failed = False
record_type = ""
record_value = ""
query = dnslib.DNSRecord.question(guess)
try:
response_q = query.send(name_server, 53, use_tcp, timeout=3)
if response_q:
if (debug):
print("response_q: " + response_q)
response = dnslib.DNSRecord.parse(response_q)
except KeyboardInterrupt:
print('User exit')
exit()
except:
# probably socket timed out
print("ERROR - possible socket timeout when trying " + guess)
pass
if response:
if debug:
print("Decoded response:\n" + str(response) + "\n")
rcode = dnslib.RCODE[response.header.rcode]
if rcode == 'NOERROR' or rcode == 'NXDOMAIN':
# success, this is a valid subdomain
if debug:
print("response.rr:\n" + str(response.rr) + "\n")
for r in response.rr:
# note that we are looping through each piece of the answer but we only return from this method once... we must pick what we return verrry carefully, see explantion below
if debug:
print("r:\n" + str(r) + "\n")
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)
'''
*Why the following break keyword is here
So if we submit a query and get back a CNAME, the response contains the CNAME record and also the
CNAME records' record, and so on down to the A or AAAA record with the final IP address for the
query we submitted.
Example:
;; ANSWER SECTION:
support.indeed.com. 7200 IN CNAME indeed.zendesk.com.
indeed.zendesk.com. 900 IN A 52.34.200.91
indeed.zendesk.com. 900 IN A 35.167.245.158
indeed.zendesk.com. 900 IN A 34.216.174.56
That's fine, and we loop through each of these "answers" in the 'for r in response.rr' loop.
PROBLEM: we were overwriting the value of record_type and record_value each time around the loop.
So if the first 'r' was a CNAME record we wouldn't know about it since the subsequent A/AAAA record would update 'record_type
and record_value to reflect the A record not the first CNAME record!
The user would see the subdomain as a plain A record but that wasn't true... depending on if the CNAME was processed first.
Since this is structered as a method call that returns a single record type and value pair, and because
we care more about the CNAME record our query points to than the IP (at least for subdomain takeover recon), we will
halt analysis and return the CNAME data if we encounter a CNAME record. We care that a domain name points to an
AWS bucket, for example, not the particular Amazon IP it ultimately has to talk to.
'''
break
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
##################### #####################
@ -1128,59 +1125,58 @@ if __name__ == "__main__":
analysis = args.analysis analysis = args.analysis
debug = args.debug debug = args.debug
if (debug): if (debug):
print "Debugging output enabled for analysis module" print("Debugging output enabled for analysis module")
if verbose or verbose is None: if verbose or verbose is None:
verbose = True verbose = True
banner() banner()
res = main(domain, threads, savefile, ports, silent=False, verbose=verbose, enable_bruteforce=enable_bruteforce, engines=engines) res = main(domain, threads, savefile, ports, silent=False, verbose=verbose, enable_bruteforce=enable_bruteforce,
engines=engines)
# Code added here # Code added here
if (analysis): if (analysis):
# res is the list of subdomains e.g. www.example.com, mail.example.com, etc # res is the list of subdomains e.g. www.example.com, mail.example.com, etc
resolvers = ['8.8.8.8', '8.8.4.4', '9.9.9.9', '75.75.75.75'] resolvers = ['8.8.8.8', '8.8.4.4', '9.9.9.9', '75.75.75.75', '1.1.1.1', '1.0.0.1']
server = 0 server = 0
count = 0 count = 0
total = str(len(res)) total = str(len(res))
print "" print("")
print(B + "[-] Beginning analysis of " + total + " subdomains..." + W) print(B + "[-] Beginning analysis of " + total + " subdomains..." + W)
for subdomain in res: for subdomain in res:
try: try:
name = subdomain.replace('\n', '').replace('\r', '') name = subdomain.replace('\n', '').replace('\r', '')
(rtype, record) = lookup(name, resolvers[server]) (rtype, record) = lookup(name, resolvers[server])
# if the query did not return an error, then add result to appropriate array # if the query did not return an error, then add result to appropriate array
if rtype != "ERROR": if rtype != "ERROR":
if rtype == "CNAME": if rtype == "CNAME":
cnames.append(name + " -->-- " + record) cnames.append(name + " -->-- " + record)
elif rtype == "A": elif rtype == "A":
ahosts.append(name + " -->-- " + record) ahosts.append(name + " -->-- " + record)
# round robin the resolvers # round robin the resolvers
server = server + 1 server = server + 1
server = server % len(resolvers) server = server % len(resolvers)
# update user on our progress - every 30 hosts # update user on our progress - every 30 hosts
count = count + 1 count = count + 1
if (count % 30) == 0: if (count % 30) == 0:
print str(count) + '/' + total print(str(count) + '/' + total)
except KeyboardInterrupt: except KeyboardInterrupt:
print(R + '\n[-] User exit' + W) print(R + '\n[-] User exit' + W)
exit() exit()
except: except:
# Generally unknown error. Keep going # Generally unknown error. Keep going
# Known errors: subdomain sample starting with a dot, ex .domain.com # Known errors: subdomain sample starting with a dot, ex .domain.com
continue continue
ahosts.sort() ahosts.sort()
cnames.sort() cnames.sort()
# output analysis results to console # output analysis results to console
for x in range(0, len(ahosts)): for x in range(0, len(ahosts)):
print(G + ahosts[x] + W) print(G + ahosts[x] + W)
print "\n" print("\n")
for x in range(0, len(cnames)): for x in range(0, len(cnames)):
print(G + cnames[x] + W) print(G + cnames[x] + W)
#print ""
# save the analysis to a file. Merge the arrays into one list for easier reading
write_file(analysis, ahosts + ["\n"] + cnames)
# print ""
# save the analysis to a file. Merge the arrays into one list for easier reading
write_file(analysis, ahosts + ["\n"] + cnames)