Multiple updates:
- Added debug function (--debug) to print verbose DNS information - Fixed a bug where CNAME records could be incorrectly reported as A records
This commit is contained in:
parent
3e6d4e80df
commit
adfd5601f1
|
|
@ -25,6 +25,7 @@ Short Form | Long Form | Description
|
||||||
-o | --output | Save discovered domain names to specified text file
|
-o | --output | Save discovered domain names to specified text file
|
||||||
-h | --help | show the help message and exit
|
-h | --help | show the help message and exit
|
||||||
-a | --analysis | Do analysis of the results and save to specified text file
|
-a | --analysis | Do analysis of the results and save to specified text file
|
||||||
|
| --debug | Print debug information during the analysis module (-a)
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
|
|
@ -131,6 +132,3 @@ Respect legal restrictions and only conduct testing against infrastructure that
|
||||||
## Thanks
|
## Thanks
|
||||||
|
|
||||||
* Thank you to [aboul3la](https://github.com/aboul3la/) for releasing sublist3r, an incredible subdomain discovery tool!
|
* Thank you to [aboul3la](https://github.com/aboul3la/) for releasing sublist3r, an incredible subdomain discovery tool!
|
||||||
|
|
||||||
## Version
|
|
||||||
**1/15/18 Version 0.2**
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
# Turbolist3r v0.3
|
# Turbolist3r
|
||||||
# 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
|
||||||
#
|
#
|
||||||
# Major 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
|
||||||
#
|
#
|
||||||
|
|
@ -23,11 +23,10 @@ import random
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import threading
|
import threading
|
||||||
import socket
|
import socket
|
||||||
#import subbrute
|
|
||||||
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
|
||||||
|
|
@ -54,6 +53,9 @@ except:
|
||||||
# Check if we are running this on windows platform
|
# Check if we are running this on windows platform
|
||||||
is_windows = sys.platform.startswith('win')
|
is_windows = sys.platform.startswith('win')
|
||||||
|
|
||||||
|
global debug
|
||||||
|
|
||||||
|
|
||||||
# Console Colors
|
# Console Colors
|
||||||
if is_windows:
|
if is_windows:
|
||||||
# Windows deserve coloring too :D
|
# Windows deserve coloring too :D
|
||||||
|
|
@ -112,6 +114,7 @@ def parse_args():
|
||||||
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')
|
||||||
|
parser.add_argument('--debug', default=False, help='Enable verbose debug output', action="store_true")
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1031,16 +1034,19 @@ 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):
|
||||||
#print 'Trying ' + guess + ' at ' + name_server
|
if (debug):
|
||||||
|
print 'Trying ' + guess + ' at ' + name_server
|
||||||
use_tcp = False
|
use_tcp = False
|
||||||
response = None
|
response = None
|
||||||
failed = False
|
failed = False
|
||||||
record_type = ""
|
record_type = ""
|
||||||
record_value = ""
|
record_value = ""
|
||||||
query = dnslib.DNSRecord.question(guess, 'ANY')
|
query = dnslib.DNSRecord.question(guess)
|
||||||
try:
|
try:
|
||||||
response_q = query.send(name_server, 53, use_tcp, timeout = 3)
|
response_q = query.send(name_server, 53, use_tcp, timeout = 3)
|
||||||
if response_q:
|
if response_q:
|
||||||
|
if (debug):
|
||||||
|
print "response_q: " + response_q
|
||||||
response = dnslib.DNSRecord.parse(response_q)
|
response = dnslib.DNSRecord.parse(response_q)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print 'User exit'
|
print 'User exit'
|
||||||
|
|
@ -1050,11 +1056,17 @@ def lookup(guess, name_server):
|
||||||
print "ERROR - possible socket timeout when trying " + guess
|
print "ERROR - possible socket timeout when trying " + guess
|
||||||
pass
|
pass
|
||||||
if response:
|
if response:
|
||||||
#print response
|
if debug:
|
||||||
|
print "Decoded response:\n" + str(response) + "\n"
|
||||||
rcode = dnslib.RCODE[response.header.rcode]
|
rcode = dnslib.RCODE[response.header.rcode]
|
||||||
if rcode == 'NOERROR' or rcode == 'NXDOMAIN':
|
if rcode == 'NOERROR' or rcode == 'NXDOMAIN':
|
||||||
# success, this is a valid subdomain
|
# success, this is a valid subdomain
|
||||||
|
if debug:
|
||||||
|
print "response.rr:\n" + str(response.rr) + "\n"
|
||||||
for r in response.rr:
|
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
|
rtype = None
|
||||||
try:
|
try:
|
||||||
rtype = str(dnslib.QTYPE[r.rtype])
|
rtype = str(dnslib.QTYPE[r.rtype])
|
||||||
|
|
@ -1066,6 +1078,29 @@ def lookup(guess, name_server):
|
||||||
#print r.rdata
|
#print r.rdata
|
||||||
record_type = 'CNAME'
|
record_type = 'CNAME'
|
||||||
record_value = str(r.rdata)
|
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'):
|
elif (rtype == 'A' or rtype == 'AAAA'):
|
||||||
record_type = 'A'
|
record_type = 'A'
|
||||||
record_value = str(r.rdata)
|
record_value = str(r.rdata)
|
||||||
|
|
@ -1075,6 +1110,11 @@ def lookup(guess, name_server):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#####################
|
||||||
|
# Program entry point
|
||||||
|
#####################
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
domain = args.domain
|
domain = args.domain
|
||||||
|
|
@ -1086,6 +1126,9 @@ if __name__ == "__main__":
|
||||||
engines = args.engines
|
engines = args.engines
|
||||||
# Line added here
|
# Line added here
|
||||||
analysis = args.analysis
|
analysis = args.analysis
|
||||||
|
debug = args.debug
|
||||||
|
if (debug):
|
||||||
|
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()
|
||||||
|
|
@ -1140,3 +1183,4 @@ 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)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue