|
@@ -1,8 +1,8 @@
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
## Title: domainhunter.py
|
|
|
-## Author: Joe Vest and Andrew Chiles
|
|
|
-## Description: Checks expired domains, bluecoat categorization, and Archive.org history to determine
|
|
|
+## Author: @joevest and @andrewchiles
|
|
|
+## Description: Checks expired domains, reputation/categorization, and Archive.org history to determine
|
|
|
## good candidates for phishing and C2 domain names
|
|
|
|
|
|
# To-do:
|
|
@@ -15,31 +15,32 @@ import random
|
|
|
import argparse
|
|
|
import json
|
|
|
|
|
|
+__version__ = "20180407"
|
|
|
+
|
|
|
## Functions
|
|
|
|
|
|
def checkBluecoat(domain):
|
|
|
try:
|
|
|
- url = 'https://sitereview.bluecoat.com/rest/categorization'
|
|
|
- postData = {"url":domain} # HTTP POST Parameters
|
|
|
+ url = 'https://sitereview.bluecoat.com/resource/lookup'
|
|
|
+ postData = {'url':domain,'captcha':''} # HTTP POST Parameters
|
|
|
headers = {'User-Agent':useragent,
|
|
|
- 'X-Requested-With':'XMLHttpRequest',
|
|
|
- 'Referer':'https://sitereview.bluecoat.com/sitereview.jsp'}
|
|
|
+ 'Content-Type':'application/json; charset=UTF-8',
|
|
|
+ 'Referer':'https://sitereview.bluecoat.com/lookup'}
|
|
|
|
|
|
print('[*] BlueCoat Check: {}'.format(domain))
|
|
|
- response = s.post(url,headers=headers,data=postData,verify=False)
|
|
|
+ response = s.post(url,headers=headers,json=postData,verify=False)
|
|
|
|
|
|
- responseJson = json.loads(response.text)
|
|
|
+ responseJSON = json.loads(response.text)
|
|
|
|
|
|
- if 'errorType' in responseJson:
|
|
|
- a = responseJson['errorType']
|
|
|
+ if 'errorType' in responseJSON:
|
|
|
+ a = responseJSON['errorType']
|
|
|
else:
|
|
|
- soupA = BeautifulSoup(responseJson['categorization'], 'lxml')
|
|
|
- a = soupA.find("a").text
|
|
|
+ a = responseJSON['categorization'][0]['name']
|
|
|
|
|
|
- # Print notice if CAPTCHAs are blocking accurate results
|
|
|
- if a == 'captcha':
|
|
|
- print('[-] Error: Blue Coat CAPTCHA received. Change your IP or manually solve a CAPTCHA at "https://sitereview.bluecoat.com/sitereview.jsp"')
|
|
|
- #raw_input('[*] Press Enter to continue...')
|
|
|
+ # # Print notice if CAPTCHAs are blocking accurate results
|
|
|
+ # if a == 'captcha':
|
|
|
+ # print('[-] Error: Blue Coat CAPTCHA received. Change your IP or manually solve a CAPTCHA at "https://sitereview.bluecoat.com/sitereview.jsp"')
|
|
|
+ # #raw_input('[*] Press Enter to continue...')
|
|
|
|
|
|
return a
|
|
|
except:
|
|
@@ -60,12 +61,12 @@ def checkIBMxForce(domain):
|
|
|
url = 'https://api.xforce.ibmcloud.com/url/{}'.format(domain)
|
|
|
response = s.get(url,headers=headers,verify=False)
|
|
|
|
|
|
- responseJson = json.loads(response.text)
|
|
|
+ responseJSON = json.loads(response.text)
|
|
|
|
|
|
- if 'error' in responseJson:
|
|
|
- a = responseJson['error']
|
|
|
+ if 'error' in responseJSON:
|
|
|
+ a = responseJSON['error']
|
|
|
else:
|
|
|
- a = responseJson["result"]['cats']
|
|
|
+ a = str(responseJSON["result"]['cats'])
|
|
|
|
|
|
return a
|
|
|
|
|
@@ -73,6 +74,28 @@ def checkIBMxForce(domain):
|
|
|
print('[-] Error retrieving IBM x-Force reputation!')
|
|
|
return "-"
|
|
|
|
|
|
+def checkTalos(domain):
|
|
|
+ try:
|
|
|
+ url = "https://www.talosintelligence.com/sb_api/query_lookup?query=%2Fapi%2Fv2%2Fdetails%2Fdomain%2F&query_entry={0}&offset=0&order=ip+asc".format(domain)
|
|
|
+ headers = {'User-Agent':useragent,
|
|
|
+ 'Referer':url}
|
|
|
+
|
|
|
+ print('[*] Cisco Talos Check: {}'.format(domain))
|
|
|
+ response = s.get(url,headers=headers,verify=False)
|
|
|
+
|
|
|
+ responseJSON = json.loads(response.text)
|
|
|
+ if 'error' in responseJSON:
|
|
|
+ a = str(responseJSON['error'])
|
|
|
+ else:
|
|
|
+ a = '{0} (Score: {1})'.format(str(responseJSON['category']['description']), str(responseJSON['web_score_name']))
|
|
|
+
|
|
|
+ return a
|
|
|
+
|
|
|
+ except:
|
|
|
+ print('[-] Error retrieving Talos reputation!')
|
|
|
+ return "-"
|
|
|
+
|
|
|
+
|
|
|
def downloadMalwareDomains():
|
|
|
url = malwaredomains
|
|
|
response = s.get(url,headers=headers,verify=False)
|
|
@@ -96,33 +119,30 @@ if __name__ == "__main__":
|
|
|
print("[*] Install required dependencies by running `pip install -r requirements.txt`")
|
|
|
quit(0)
|
|
|
|
|
|
- parser = argparse.ArgumentParser(description='Checks expired domains, bluecoat categorization, and Archive.org history to determine good candidates for C2 and phishing domains')
|
|
|
- parser.add_argument('-q','--query', help='Optional keyword used to refine search results', required=False, type=str)
|
|
|
- parser.add_argument('-c','--check', help='Perform slow reputation checks', required=False, default=False, action='store_true')
|
|
|
- parser.add_argument('-r','--maxresults', help='Number of results to return when querying latest expired/deleted domains (min. 100)', required=False, type=int, default=100)
|
|
|
- parser.add_argument('-w','--maxwidth', help='Width of text table', required=False, type=int, default=400)
|
|
|
- #parser.add_argument('-f','--file', help='Input file containing potential domain names to check (1 per line)', required=False, type=str)
|
|
|
+ parser = argparse.ArgumentParser(description='Finds expired domains, domain categorization, and Archive.org history to determine good candidates for C2 and phishing domains')
|
|
|
+ parser.add_argument('-q','--query', help='Optional keyword used to refine search results', required=False, default=False, type=str, dest='query')
|
|
|
+ parser.add_argument('-c','--check', help='Perform slow reputation checks', required=False, default=False, action='store_true', dest='check')
|
|
|
+ parser.add_argument('-r','--maxresults', help='Number of results to return when querying latest expired/deleted domains (min. 100)', required=False, default=100, type=int, dest='maxresults')
|
|
|
+ parser.add_argument('-s','--single', help='Performs reputation checks against a single domain name.', required=False, default=False, dest='single')
|
|
|
+ parser.add_argument('-w','--maxwidth', help='Width of text table', required=False, default=400, type=int, dest='maxwidth')
|
|
|
+ parser.add_argument('-v','--version', action='version',version='%(prog)s {version}'.format(version=__version__))
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
## Variables
|
|
|
- query = False
|
|
|
- if args.query:
|
|
|
- query = args.query
|
|
|
+
|
|
|
+ query = args.query
|
|
|
|
|
|
check = args.check
|
|
|
+
|
|
|
maxresults = args.maxresults
|
|
|
|
|
|
if maxresults < 100:
|
|
|
maxresults = 100
|
|
|
-
|
|
|
- maxwidth=args.maxwidth
|
|
|
|
|
|
- # TODO: Add Input file support
|
|
|
- #inputfile = False
|
|
|
- #if args.file:
|
|
|
- # inputfile = args.file
|
|
|
+ single = args.single
|
|
|
|
|
|
- t = Texttable(max_width=maxwidth)
|
|
|
+ maxwidth = args.maxwidth
|
|
|
+
|
|
|
malwaredomains = 'http://mirror1.malwaredomains.com/files/justdomains'
|
|
|
expireddomainsqueryurl = 'https://www.expireddomains.net/domain-name-search'
|
|
|
|
|
@@ -148,21 +168,35 @@ if __name__ == "__main__":
|
|
|
print(title)
|
|
|
print("")
|
|
|
print("Expired Domains Reputation Checker")
|
|
|
- print("")
|
|
|
- print("DISCLAIMER:")
|
|
|
- print("This is for educational purposes only!")
|
|
|
+ print("Authors: @joevest and @andrewchiles\n")
|
|
|
+ print("DISCLAIMER: This is for educational purposes only!")
|
|
|
disclaimer = '''It is designed to promote education and the improvement of computer/cyber security.
|
|
|
The authors or employers are not liable for any illegal act or misuse performed by any user of this tool.
|
|
|
If you plan to use this content for illegal purpose, don't. Have a nice day :)'''
|
|
|
print(disclaimer)
|
|
|
print("")
|
|
|
- print("********************************************")
|
|
|
- print("Start Time: {}".format(timestamp))
|
|
|
- print("TextTable Column Width: {}".format(str(maxwidth)))
|
|
|
- print("Checking Reputation: {}".format(str(check)))
|
|
|
- print("Number Domains Checked: {}".format(maxresults))
|
|
|
- print("********************************************")
|
|
|
|
|
|
+ # Retrieve reputation for a single choosen domain (Quick Mode)
|
|
|
+ if single:
|
|
|
+ domain = single
|
|
|
+ print('[*] Fetching domain reputation for: {}'.format(domain))
|
|
|
+
|
|
|
+ bluecoat = ''
|
|
|
+ ibmxforce = ''
|
|
|
+ ciscotalos = ''
|
|
|
+
|
|
|
+ bluecoat = checkBluecoat(domain)
|
|
|
+ print("[+] {}: {}".format(domain, bluecoat))
|
|
|
+
|
|
|
+ ibmxforce = checkIBMxForce(domain)
|
|
|
+ print("[+] {}: {}".format(domain, ibmxforce))
|
|
|
+
|
|
|
+ ciscotalos = checkTalos(domain)
|
|
|
+ print("[+] {}: {}".format(domain, ciscotalos))
|
|
|
+
|
|
|
+ quit()
|
|
|
+
|
|
|
+ # Calculate estimated runtime based on sleep variable
|
|
|
runtime = 0
|
|
|
if check:
|
|
|
runtime = (maxresults * 20) / 60
|
|
@@ -170,15 +204,14 @@ If you plan to use this content for illegal purpose, don't. Have a nice day :)'
|
|
|
else:
|
|
|
runtime = maxresults * .15 / 60
|
|
|
|
|
|
- print("Estimated Max Run Time: {} minutes".format(int(runtime)))
|
|
|
- print("")
|
|
|
-
|
|
|
+ print("Estimated Max Run Time: {} minutes\n".format(int(runtime)))
|
|
|
+
|
|
|
+ # Download known malware domains
|
|
|
print('[*] Downloading malware domain list from {}'.format(malwaredomains))
|
|
|
maldomains = downloadMalwareDomains()
|
|
|
|
|
|
maldomains_list = maldomains.split("\n")
|
|
|
- # Create an initial session
|
|
|
-
|
|
|
+
|
|
|
# Generic Proxy support
|
|
|
# TODO: add as a parameter
|
|
|
proxies = {
|
|
@@ -186,7 +219,7 @@ If you plan to use this content for illegal purpose, don't. Have a nice day :)'
|
|
|
'https': 'http://127.0.0.1:8080',
|
|
|
}
|
|
|
|
|
|
-
|
|
|
+ # Create an initial session
|
|
|
domainrequest = s.get("https://www.expireddomains.net",headers=headers,verify=False)
|
|
|
#domainrequest = s.get("https://www.expireddomains.net",headers=headers,verify=False,proxies=proxies)
|
|
|
|
|
@@ -204,6 +237,8 @@ If you plan to use this content for illegal purpose, don't. Have a nice day :)'
|
|
|
else:
|
|
|
urls.append("{}/?start={}&q={}".format(expireddomainsqueryurl,i,query))
|
|
|
headers['Referer'] ='https://www.expireddomains.net/domain-name-search/?start={}&q={}'.format((i-25),query)
|
|
|
+
|
|
|
+ # If no keyword provided, retrieve list of recently expired domains
|
|
|
else:
|
|
|
|
|
|
print('[*] Fetching expired or deleted domains...')
|
|
@@ -212,8 +247,7 @@ If you plan to use this content for illegal purpose, don't. Have a nice day :)'
|
|
|
urls.append('https://www.expireddomains.net/deleted-com-domains/?start={}&o=changed&r=a'.format(i))
|
|
|
urls.append('https://www.expireddomains.net/deleted-net-domains/?start={}&o=changed&r=a'.format(i))
|
|
|
urls.append('https://www.expireddomains.net/deleted-org-domains/?start={}&o=changed&r=a'.format(i))
|
|
|
-
|
|
|
-
|
|
|
+
|
|
|
for url in urls:
|
|
|
|
|
|
print("[*] {}".format(url))
|
|
@@ -230,7 +264,6 @@ If you plan to use this content for illegal purpose, don't. Have a nice day :)'
|
|
|
pk_str = '5abbbc772cbacfb1' + '.1496' + str(r1) + '.2.1496' + str(r1) + '.1496' + str(r1)
|
|
|
|
|
|
jar = requests.cookies.RequestsCookieJar()
|
|
|
- #jar.set('_pk_id.10.dd0a', '843f8d071e27aa52.1496597944.2.1496602069.1496601572.', domain='expireddomains.net', path='/')
|
|
|
jar.set('_pk_ses.10.dd0a', '*', domain='expireddomains.net', path='/')
|
|
|
jar.set('_pk_id.10.dd0a', pk_str, domain='expireddomains.net', path='/')
|
|
|
|
|
@@ -243,7 +276,6 @@ If you plan to use this content for illegal purpose, don't. Have a nice day :)'
|
|
|
soup = BeautifulSoup(domains, 'lxml')
|
|
|
table = soup.find("table")
|
|
|
|
|
|
-
|
|
|
try:
|
|
|
for row in table.findAll('tr')[1:]:
|
|
|
|
|
@@ -252,8 +284,6 @@ If you plan to use this content for illegal purpose, don't. Have a nice day :)'
|
|
|
|
|
|
cells = row.findAll("td")
|
|
|
|
|
|
-
|
|
|
-
|
|
|
if len(cells) >= 1:
|
|
|
output = ""
|
|
|
|
|
@@ -328,9 +358,9 @@ If you plan to use this content for illegal purpose, don't. Have a nice day :)'
|
|
|
ibmxforce = 'ignored'
|
|
|
elif check == True:
|
|
|
bluecoat = checkBluecoat(c0)
|
|
|
- print("[+] {} is categorized as: {}".format(c0, bluecoat))
|
|
|
+ print("[+] {}: {}".format(c0, bluecoat))
|
|
|
ibmxforce = checkIBMxForce(c0)
|
|
|
- print("[+] {} is categorized as: {}".format(c0, ibmxforce))
|
|
|
+ print("[+] {}: {}".format(c0, ibmxforce))
|
|
|
# Sleep to avoid captchas
|
|
|
time.sleep(random.randrange(10,20))
|
|
|
else:
|
|
@@ -338,44 +368,12 @@ If you plan to use this content for illegal purpose, don't. Have a nice day :)'
|
|
|
ibmxforce = "skipped"
|
|
|
# Append parsed domain data to list
|
|
|
data.append([c0,c3,c4,available,status,bluecoat,ibmxforce])
|
|
|
- except Exception as e: print(e)
|
|
|
- #print("[-] Error: No results found on this page!")
|
|
|
-
|
|
|
- # TODO: Add support of input file
|
|
|
- # Retrieve the most recent expired/deleted domain results
|
|
|
- # elif inputfile:
|
|
|
-
|
|
|
- # print('[*] Fetching domain reputation from file: {}').format(inputfile)
|
|
|
- # # read in file contents to list
|
|
|
- # try:
|
|
|
- # domains = [line.rstrip('\r\n') for line in open(inputfile, "r")]
|
|
|
+ except Exception as e:
|
|
|
+ print(e)
|
|
|
|
|
|
- # except IOError:
|
|
|
- # print '[-] Error: "{}" does not appear to exist.'.format(inputfile)
|
|
|
- # exit()
|
|
|
-
|
|
|
- # print('[*] Domains loaded: {}').format(len(domains))
|
|
|
- # for domain in domains:
|
|
|
- # if domain in maldomains_list:
|
|
|
- # print("[-] Skipping {} - Identified as known malware domain").format(domain)
|
|
|
- # else:
|
|
|
- # bluecoat = ''
|
|
|
- # ibmxforce = ''
|
|
|
- # bluecoat = checkBluecoat(domain)
|
|
|
- # print "[+] {} is categorized as: {}".format(domain, bluecoat)
|
|
|
- # ibmxforce = checkIBMxForce(domain)
|
|
|
- # print "[+] {} is categorized as: {}".format(domain, ibmxforce)
|
|
|
- # # Sleep to avoid captchas
|
|
|
- # time.sleep(random.randrange(10,20))
|
|
|
- # data.append([domain,'-','-','-',bluecoat,ibmxforce])
|
|
|
-
|
|
|
# Sort domain list by column 2 (Birth Year)
|
|
|
sortedData = sorted(data, key=lambda x: x[1], reverse=True)
|
|
|
|
|
|
- t.add_rows(sortedData)
|
|
|
- header = ['Domain', 'Birth', '#', 'TLDs', 'Status', 'BC', 'IBM']
|
|
|
- t.header(header)
|
|
|
-
|
|
|
# Build HTML Table
|
|
|
html = ''
|
|
|
htmlHeader = '<html><head><title>Expired Domain List</title></head>'
|
|
@@ -388,7 +386,7 @@ If you plan to use this content for illegal purpose, don't. Have a nice day :)'
|
|
|
<th>Entries</th>
|
|
|
<th>TLDs Available</th>
|
|
|
<th>Status</th>
|
|
|
- <th>Bluecoat</th>
|
|
|
+ <th>Symantec</th>
|
|
|
<th>Categorization</th>
|
|
|
<th>IBM-xForce</th>
|
|
|
<th>Categorization</th>
|
|
@@ -410,7 +408,7 @@ If you plan to use this content for illegal purpose, don't. Have a nice day :)'
|
|
|
htmlTableBody += '<td>{}</td>'.format(i[3]) # TLDs
|
|
|
htmlTableBody += '<td>{}</td>'.format(i[4]) # Status
|
|
|
|
|
|
- htmlTableBody += '<td><a href="https://sitereview.bluecoat.com/sitereview.jsp#/?search={}" target="_blank">Bluecoat</a></td>'.format(i[0]) # Bluecoat
|
|
|
+ htmlTableBody += '<td><a href="https://sitereview.bluecoat.com/sitereview#/?search={}" target="_blank">Bluecoat</a></td>'.format(i[0]) # Bluecoat
|
|
|
htmlTableBody += '<td>{}</td>'.format(i[5]) # Bluecoat Categorization
|
|
|
htmlTableBody += '<td><a href="https://exchange.xforce.ibmcloud.com/url/{}" target="_blank">IBM-xForce</a></td>'.format(i[0]) # IBM xForce
|
|
|
htmlTableBody += '<td>{}</td>'.format(i[6]) # IBM x-Force Categorization
|
|
@@ -428,7 +426,12 @@ If you plan to use this content for illegal purpose, don't. Have a nice day :)'
|
|
|
|
|
|
print("\n[*] Search complete")
|
|
|
print("[*] Log written to {}\n".format(logfilename))
|
|
|
-
|
|
|
+
|
|
|
+ # Print Text Table
|
|
|
+ t = Texttable(max_width=maxwidth)
|
|
|
+ t.add_rows(sortedData)
|
|
|
+ header = ['Domain', 'Birth', '#', 'TLDs', 'Status', 'Symantec', 'IBM']
|
|
|
+ t.header(header)
|
|
|
print(t.draw())
|
|
|
|
|
|
|