domainhunter.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. #!/usr/bin/env python
  2. ## Title: domainhunter.py
  3. ## Author: @joevest and @andrewchiles
  4. ## Description: Checks expired domains, reputation/categorization, and Archive.org history to determine
  5. ## good candidates for phishing and C2 domain names
  6. # Add OCR support for BlueCoat/SiteReview CAPTCHA using tesseract
  7. # Add support for input file list of potential domains
  8. # Add additional error checking for ExpiredDomains.net parsing
  9. # Changed -q/--query switch to -k/--keyword to better match its purpose
  10. import time
  11. import random
  12. import argparse
  13. import json
  14. import base64
  15. __version__ = "20180411"
  16. ## Functions
  17. def doSleep(timing):
  18. if timing == 0:
  19. time.sleep(random.randrange(90,120))
  20. elif timing == 1:
  21. time.sleep(random.randrange(60,90))
  22. elif timing == 2:
  23. time.sleep(random.randrange(30,60))
  24. elif timing == 3:
  25. time.sleep(random.randrange(10,20))
  26. elif timing == 4:
  27. time.sleep(random.randrange(5,10))
  28. # There's no elif timing == 5 here because we don't want to sleep for -t 5
  29. def checkBluecoat(domain):
  30. try:
  31. url = 'https://sitereview.bluecoat.com/resource/lookup'
  32. postData = {'url':domain,'captcha':''} # HTTP POST Parameters
  33. headers = {'User-Agent':useragent,
  34. 'Content-Type':'application/json; charset=UTF-8',
  35. 'Referer':'https://sitereview.bluecoat.com/lookup'}
  36. print('[*] BlueCoat: {}'.format(domain))
  37. response = s.post(url,headers=headers,json=postData,verify=False)
  38. responseJSON = json.loads(response.text)
  39. if 'errorType' in responseJSON:
  40. a = responseJSON['errorType']
  41. else:
  42. a = responseJSON['categorization'][0]['name']
  43. # Print notice if CAPTCHAs are blocking accurate results and attempt to solve if --ocr
  44. if a == 'captcha':
  45. if ocr:
  46. # This request is performed in a browser, but is not needed for our purposes
  47. #captcharequestURL = 'https://sitereview.bluecoat.com/resource/captcha-request'
  48. #print('[*] Requesting CAPTCHA')
  49. #response = s.get(url=captcharequestURL,headers=headers,cookies=cookies,verify=False)
  50. print('[*] Received CAPTCHA challenge!')
  51. captcha = solveCaptcha('https://sitereview.bluecoat.com/resource/captcha.jpg',s)
  52. if captcha:
  53. b64captcha = base64.b64encode(captcha.encode('utf-8')).decode('utf-8')
  54. # Send CAPTCHA solution via GET since inclusion with the domain categorization request doens't work anymore
  55. captchasolutionURL = 'https://sitereview.bluecoat.com/resource/captcha-request/{0}'.format(b64captcha)
  56. print('[*] Submiting CAPTCHA at {0}'.format(captchasolutionURL))
  57. response = s.get(url=captchasolutionURL,headers=headers,verify=False)
  58. # Try the categorization request again
  59. response = s.post(url,headers=headers,json=postData,verify=False)
  60. responseJSON = json.loads(response.text)
  61. if 'errorType' in responseJSON:
  62. a = responseJSON['errorType']
  63. else:
  64. a = responseJSON['categorization'][0]['name']
  65. else:
  66. print('[-] Error: Failed to solve BlueCoat CAPTCHA with OCR! Manually solve at "https://sitereview.bluecoat.com/sitereview.jsp"')
  67. else:
  68. print('[-] Error: BlueCoat CAPTCHA received. Try --ocr flag or manually solve a CAPTCHA at "https://sitereview.bluecoat.com/sitereview.jsp"')
  69. return a
  70. except Exception as e:
  71. print('[-] Error retrieving Bluecoat reputation! {0}'.format(e))
  72. return "-"
  73. def checkIBMXForce(domain):
  74. try:
  75. url = 'https://exchange.xforce.ibmcloud.com/url/{}'.format(domain)
  76. headers = {'User-Agent':useragent,
  77. 'Accept':'application/json, text/plain, */*',
  78. 'x-ui':'XFE',
  79. 'Origin':url,
  80. 'Referer':url}
  81. print('[*] IBM xForce: {}'.format(domain))
  82. url = 'https://api.xforce.ibmcloud.com/url/{}'.format(domain)
  83. response = s.get(url,headers=headers,verify=False)
  84. responseJSON = json.loads(response.text)
  85. if 'error' in responseJSON:
  86. a = responseJSON['error']
  87. elif not responseJSON['result']['cats']:
  88. a = 'Uncategorized'
  89. else:
  90. categories = ''
  91. # Parse all dictionary keys and append to single string to get Category names
  92. for key in responseJSON["result"]['cats']:
  93. categories += '{0}, '.format(str(key))
  94. a = '{0}(Score: {1})'.format(categories,str(responseJSON['result']['score']))
  95. return a
  96. except:
  97. print('[-] Error retrieving IBM x-Force reputation!')
  98. return "-"
  99. def checkTalos(domain):
  100. 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)
  101. headers = {'User-Agent':useragent,
  102. 'Referer':url}
  103. print('[*] Cisco Talos: {}'.format(domain))
  104. try:
  105. response = s.get(url,headers=headers,verify=False)
  106. responseJSON = json.loads(response.text)
  107. if 'error' in responseJSON:
  108. a = str(responseJSON['error'])
  109. elif responseJSON['category'] is None:
  110. a = 'Uncategorized'
  111. else:
  112. a = '{0} (Score: {1})'.format(str(responseJSON['category']['description']), str(responseJSON['web_score_name']))
  113. return a
  114. except:
  115. print('[-] Error retrieving Talos reputation!')
  116. return "-"
  117. def checkMXToolbox(domain):
  118. url = 'https://mxtoolbox.com/Public/Tools/BrandReputation.aspx'
  119. headers = {'User-Agent':useragent,
  120. 'Origin':url,
  121. 'Referer':url}
  122. print('[*] Google SafeBrowsing and PhishTank: {}'.format(domain))
  123. try:
  124. response = s.get(url=url, headers=headers)
  125. soup = BeautifulSoup(response.content,'lxml')
  126. viewstate = soup.select('input[name=__VIEWSTATE]')[0]['value']
  127. viewstategenerator = soup.select('input[name=__VIEWSTATEGENERATOR]')[0]['value']
  128. eventvalidation = soup.select('input[name=__EVENTVALIDATION]')[0]['value']
  129. data = {
  130. "__EVENTTARGET": "",
  131. "__EVENTARGUMENT": "",
  132. "__VIEWSTATE": viewstate,
  133. "__VIEWSTATEGENERATOR": viewstategenerator,
  134. "__EVENTVALIDATION": eventvalidation,
  135. "ctl00$ContentPlaceHolder1$brandReputationUrl": domain,
  136. "ctl00$ContentPlaceHolder1$brandReputationDoLookup": "Brand Reputation Lookup",
  137. "ctl00$ucSignIn$hfRegCode": 'missing',
  138. "ctl00$ucSignIn$hfRedirectSignUp": '/Public/Tools/BrandReputation.aspx',
  139. "ctl00$ucSignIn$hfRedirectLogin": '',
  140. "ctl00$ucSignIn$txtEmailAddress": '',
  141. "ctl00$ucSignIn$cbNewAccount": 'cbNewAccount',
  142. "ctl00$ucSignIn$txtFullName": '',
  143. "ctl00$ucSignIn$txtModalNewPassword": '',
  144. "ctl00$ucSignIn$txtPhone": '',
  145. "ctl00$ucSignIn$txtCompanyName": '',
  146. "ctl00$ucSignIn$drpTitle": '',
  147. "ctl00$ucSignIn$txtTitleName": '',
  148. "ctl00$ucSignIn$txtModalPassword": ''
  149. }
  150. response = s.post(url=url, headers=headers, data=data)
  151. soup = BeautifulSoup(response.content,'lxml')
  152. a = ''
  153. if soup.select('div[id=ctl00_ContentPlaceHolder1_noIssuesFound]'):
  154. a = 'No issues found'
  155. return a
  156. else:
  157. if soup.select('div[id=ctl00_ContentPlaceHolder1_googleSafeBrowsingIssuesFound]'):
  158. a = 'Google SafeBrowsing Issues Found. '
  159. if soup.select('div[id=ctl00_ContentPlaceHolder1_phishTankIssuesFound]'):
  160. a += 'PhishTank Issues Found'
  161. return a
  162. except Exception as e:
  163. print('[-] Error retrieving Google SafeBrowsing and PhishTank reputation!')
  164. return "-"
  165. def downloadMalwareDomains(malwaredomainsURL):
  166. url = malwaredomainsURL
  167. response = s.get(url=url,headers=headers,verify=False)
  168. responseText = response.text
  169. if response.status_code == 200:
  170. return responseText
  171. else:
  172. print("[-] Error reaching:{} Status: {}").format(url, response.status_code)
  173. def checkDomain(domain):
  174. print('[*] Fetching domain reputation for: {}'.format(domain))
  175. if domain in maldomainsList:
  176. print("[!] {}: Identified as known malware domain (malwaredomains.com)".format(domain))
  177. mxtoolbox = checkMXToolbox(domain)
  178. print("[+] {}: {}".format(domain, mxtoolbox))
  179. bluecoat = checkBluecoat(domain)
  180. print("[+] {}: {}".format(domain, bluecoat))
  181. ibmxforce = checkIBMXForce(domain)
  182. print("[+] {}: {}".format(domain, ibmxforce))
  183. ciscotalos = checkTalos(domain)
  184. print("[+] {}: {}".format(domain, ciscotalos))
  185. print("")
  186. return
  187. def solveCaptcha(url,session):
  188. # Downloads CAPTCHA image and saves to current directory for OCR with tesseract
  189. # Returns CAPTCHA string or False if error occured
  190. jpeg = 'captcha.jpg'
  191. try:
  192. response = session.get(url=url,headers=headers,verify=False, stream=True)
  193. if response.status_code == 200:
  194. with open(jpeg, 'wb') as f:
  195. response.raw.decode_content = True
  196. shutil.copyfileobj(response.raw, f)
  197. else:
  198. print('[-] Error downloading CAPTCHA file!')
  199. return False
  200. text = pytesseract.image_to_string(Image.open(jpeg))
  201. text = text.replace(" ", "")
  202. return text
  203. except Exception as e:
  204. print("[-] Error solving CAPTCHA - {0}".format(e))
  205. return False
  206. ## MAIN
  207. if __name__ == "__main__":
  208. parser = argparse.ArgumentParser(description='Finds expired domains, domain categorization, and Archive.org history to determine good candidates for C2 and phishing domains')
  209. parser.add_argument('-k','--keyword', help='Keyword used to refine search results', required=False, default=False, type=str, dest='keyword')
  210. parser.add_argument('-c','--check', help='Perform domain reputation checks', required=False, default=False, action='store_true', dest='check')
  211. parser.add_argument('-f','--filename', help='Specify input file of line delimited domain names to check', required=False, default=False, type=str, dest='filename')
  212. parser.add_argument('--ocr', help='Perform OCR on CAPTCHAs when present', required=False, default=False, action='store_true')
  213. parser.add_argument('-r','--maxresults', help='Number of results to return when querying latest expired/deleted domains', required=False, default=100, type=int, dest='maxresults')
  214. parser.add_argument('-s','--single', help='Performs detailed reputation checks against a single domain name/IP.', required=False, default=False, dest='single')
  215. parser.add_argument('-t','--timing', help='Modifies request timing to avoid CAPTCHAs. Slowest(0) = 90-120 seconds, Default(3) = 10-20 seconds, Fastest(5) = no delay', required=False, default=3, type=int, choices=range(0,6), dest='timing')
  216. parser.add_argument('-w','--maxwidth', help='Width of text table', required=False, default=400, type=int, dest='maxwidth')
  217. parser.add_argument('-V','--version', action='version',version='%(prog)s {version}'.format(version=__version__))
  218. args = parser.parse_args()
  219. # Load dependent modules
  220. try:
  221. import requests
  222. from bs4 import BeautifulSoup
  223. from texttable import Texttable
  224. except Exception as e:
  225. print("Expired Domains Reputation Check")
  226. print("[-] Missing basic dependencies: {}".format(str(e)))
  227. print("[*] Install required dependencies by running `pip3 install -r requirements.txt`")
  228. quit(0)
  229. # Load OCR related modules if --ocr flag is set since these can be difficult to get working
  230. if args.ocr:
  231. try:
  232. import pytesseract
  233. from PIL import Image
  234. import shutil
  235. except Exception as e:
  236. print("Expired Domains Reputation Check")
  237. print("[-] Missing OCR dependencies: {}".format(str(e)))
  238. print("[*] Install required Python dependencies by running `pip3 install -r requirements.txt`")
  239. print("[*] Ubuntu\Debian - Install tesseract by running `apt-get install tesseract-ocr python3-imaging`")
  240. print("[*] MAC OSX - Install tesseract with homebrew by running `brew install tesseract`")
  241. quit(0)
  242. ## Variables
  243. keyword = args.keyword
  244. check = args.check
  245. filename = args.filename
  246. maxresults = args.maxresults
  247. single = args.single
  248. timing = args.timing
  249. maxwidth = args.maxwidth
  250. malwaredomainsURL = 'http://mirror1.malwaredomains.com/files/justdomains'
  251. expireddomainsqueryURL = 'https://www.expireddomains.net/domain-name-search'
  252. ocr = args.ocr
  253. timestamp = time.strftime("%Y%m%d_%H%M%S")
  254. useragent = 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)'
  255. headers = {'User-Agent':useragent}
  256. requests.packages.urllib3.disable_warnings()
  257. # HTTP Session container, used to manage cookies, session tokens and other session information
  258. s = requests.Session()
  259. data = []
  260. title = '''
  261. ____ ___ __ __ _ ___ _ _ _ _ _ _ _ _ _____ _____ ____
  262. | _ \ / _ \| \/ | / \ |_ _| \ | | | | | | | | | \ | |_ _| ____| _ \
  263. | | | | | | | |\/| | / _ \ | || \| | | |_| | | | | \| | | | | _| | |_) |
  264. | |_| | |_| | | | |/ ___ \ | || |\ | | _ | |_| | |\ | | | | |___| _ <
  265. |____/ \___/|_| |_/_/ \_\___|_| \_| |_| |_|\___/|_| \_| |_| |_____|_| \_\ '''
  266. print(title)
  267. print("")
  268. print("Expired Domains Reputation Checker")
  269. print("Authors: @joevest and @andrewchiles\n")
  270. print("DISCLAIMER: This is for educational purposes only!")
  271. disclaimer = '''It is designed to promote education and the improvement of computer/cyber security.
  272. The authors or employers are not liable for any illegal act or misuse performed by any user of this tool.
  273. If you plan to use this content for illegal purpose, don't. Have a nice day :)'''
  274. print(disclaimer)
  275. print("")
  276. # Download known malware domains
  277. print('[*] Downloading malware domain list from {}\n'.format(malwaredomainsURL))
  278. maldomains = downloadMalwareDomains(malwaredomainsURL)
  279. maldomainsList = maldomains.split("\n")
  280. # Retrieve reputation for a single choosen domain (Quick Mode)
  281. if single:
  282. checkDomain(single)
  283. quit(0)
  284. # Perform detailed domain reputation checks against input file
  285. if filename:
  286. try:
  287. with open(filename, 'r') as domainsList:
  288. for line in domainsList.read().splitlines():
  289. checkDomain(line)
  290. doSleep(timing)
  291. except KeyboardInterrupt:
  292. print('Caught keyboard interrupt. Exiting!')
  293. quit(0)
  294. except Exception as e:
  295. print('[-] {}'.format(e))
  296. quit(1)
  297. quit(0)
  298. # Generic Proxy support
  299. # TODO: add as a parameter
  300. proxies = {
  301. 'http': 'http://127.0.0.1:8080',
  302. 'https': 'http://127.0.0.1:8080',
  303. }
  304. # Create an initial session
  305. domainrequest = s.get("https://www.expireddomains.net",headers=headers,verify=False)
  306. # Use proxy like Burp for debugging request/parsing errors
  307. #domainrequest = s.get("https://www.expireddomains.net",headers=headers,verify=False,proxies=proxies)
  308. # Generate list of URLs to query for expired/deleted domains
  309. urls = []
  310. # Use the keyword string to narrow domain search if provided
  311. if keyword:
  312. print('[*] Fetching expired or deleted domains containing "{}"'.format(keyword))
  313. for i in range (0,maxresults,25):
  314. if i == 0:
  315. urls.append("{}/?q={}".format(expireddomainsqueryURL,keyword))
  316. headers['Referer'] ='https://www.expireddomains.net/domain-name-search/?q={}&start=1'.format(keyword)
  317. else:
  318. urls.append("{}/?start={}&q={}".format(expireddomainsqueryURL,i,keyword))
  319. headers['Referer'] ='https://www.expireddomains.net/domain-name-search/?start={}&q={}'.format((i-25),keyword)
  320. # If no keyword provided, retrieve list of recently expired domains in batches of 25 results.
  321. else:
  322. print('[*] Fetching expired or deleted domains...')
  323. # Caculate number of URLs to request since we're performing a request for four different resources instead of one
  324. numresults = int(maxresults / 4)
  325. for i in range (0,(numresults),25):
  326. urls.append('https://www.expireddomains.net/backorder-expired-domains?start={}&o=changed&r=a'.format(i))
  327. urls.append('https://www.expireddomains.net/deleted-com-domains/?start={}&o=changed&r=a'.format(i))
  328. urls.append('https://www.expireddomains.net/deleted-net-domains/?start={}&o=changed&r=a'.format(i))
  329. urls.append('https://www.expireddomains.net/deleted-org-domains/?start={}&o=changed&r=a'.format(i))
  330. for url in urls:
  331. print("[*] {}".format(url))
  332. # Annoyingly when querying specific keywords the expireddomains.net site requires additional cookies which
  333. # are set in JavaScript and not recognized by Requests so we add them here manually.
  334. # May not be needed, but the _pk_id.10.dd0a cookie only requires a single . to be successful
  335. # In order to somewhat match a real cookie, but still be different, random integers are introduced
  336. r1 = random.randint(100000,999999)
  337. # Known good example _pk_id.10.dd0a cookie: 5abbbc772cbacfb1.1496760705.2.1496760705.1496760705
  338. pk_str = '5abbbc772cbacfb1' + '.1496' + str(r1) + '.2.1496' + str(r1) + '.1496' + str(r1)
  339. jar = requests.cookies.RequestsCookieJar()
  340. jar.set('_pk_ses.10.dd0a', '*', domain='expireddomains.net', path='/')
  341. jar.set('_pk_id.10.dd0a', pk_str, domain='expireddomains.net', path='/')
  342. domainrequest = s.get(url,headers=headers,verify=False,cookies=jar)
  343. #domainrequest = s.get(url,headers=headers,verify=False,cookies=jar,proxies=proxies)
  344. domains = domainrequest.text
  345. # Turn the HTML into a Beautiful Soup object
  346. soup = BeautifulSoup(domains, 'lxml')
  347. try:
  348. table = soup.find("table")
  349. for row in table.findAll('tr')[1:]:
  350. # Alternative way to extract domain name
  351. # domain = row.find('td').find('a').text
  352. cells = row.findAll("td")
  353. if len(cells) >= 1:
  354. output = ""
  355. if keyword:
  356. c0 = row.find('td').find('a').text # domain
  357. c1 = cells[1].find(text=True) # bl
  358. c2 = cells[2].find(text=True) # domainpop
  359. c3 = cells[3].find(text=True) # birth
  360. c4 = cells[4].find(text=True) # Archive.org entries
  361. c5 = cells[5].find(text=True) # similarweb
  362. c6 = cells[6].find(text=True) # similarweb country code
  363. c7 = cells[7].find(text=True) # Dmoz.org
  364. c8 = cells[8].find(text=True) # status com
  365. c9 = cells[9].find(text=True) # status net
  366. c10 = cells[10].find(text=True) # status org
  367. c11 = cells[11].find(text=True) # status de
  368. c12 = cells[12].find(text=True) # tld registered
  369. c13 = cells[13].find(text=True) # Related Domains
  370. c14 = cells[14].find(text=True) # Domain list
  371. c15 = cells[15].find(text=True) # status
  372. c16 = cells[16].find(text=True) # related links
  373. else:
  374. c0 = cells[0].find(text=True) # domain
  375. c1 = cells[1].find(text=True) # bl
  376. c2 = cells[2].find(text=True) # domainpop
  377. c3 = cells[3].find(text=True) # birth
  378. c4 = cells[4].find(text=True) # Archive.org entries
  379. c5 = cells[5].find(text=True) # similarweb
  380. c6 = cells[6].find(text=True) # similarweb country code
  381. c7 = cells[7].find(text=True) # Dmoz.org
  382. c8 = cells[8].find(text=True) # status com
  383. c9 = cells[9].find(text=True) # status net
  384. c10 = cells[10].find(text=True) # status org
  385. c11 = cells[11].find(text=True) # status de
  386. c12 = cells[12].find(text=True) # tld registered
  387. c13 = cells[13].find(text=True) # changes
  388. c14 = cells[14].find(text=True) # whois
  389. c15 = "" # not used
  390. c16 = "" # not used
  391. c17 = "" # not used
  392. # Expired Domains results have an additional 'Availability' column that breaks parsing "deleted" domains
  393. #c15 = cells[15].find(text=True) # related links
  394. available = ''
  395. if c8 == "available":
  396. available += ".com "
  397. if c9 == "available":
  398. available += ".net "
  399. if c10 == "available":
  400. available += ".org "
  401. if c11 == "available":
  402. available += ".de "
  403. status = ""
  404. if c15:
  405. status = c15
  406. # Skip additional reputation checks if this domain is already categorized as malicious
  407. if c0 in maldomainsList:
  408. print("[-] Skipping {} - Identified as known malware domain").format(c0)
  409. else:
  410. bluecoat = ''
  411. ibmxforce = ''
  412. if c3 == '-':
  413. bluecoat = 'ignored'
  414. ibmxforce = 'ignored'
  415. elif check == True:
  416. bluecoat = checkBluecoat(c0)
  417. print("[+] {}: {}".format(c0, bluecoat))
  418. ibmxforce = checkIBMXForce(c0)
  419. print("[+] {}: {}".format(c0, ibmxforce))
  420. # Sleep to avoid captchas
  421. doSleep(timing)
  422. else:
  423. bluecoat = "skipped"
  424. ibmxforce = "skipped"
  425. # Append parsed domain data to list
  426. data.append([c0,c3,c4,available,status,bluecoat,ibmxforce])
  427. except Exception as e:
  428. #print(e)
  429. pass
  430. # Check for valid results before continuing
  431. if not(data):
  432. print("[-] No results found for keyword: {0}".format(keyword))
  433. quit(0)
  434. # Sort domain list by column 2 (Birth Year)
  435. sortedData = sorted(data, key=lambda x: x[1], reverse=True)
  436. # Build HTML Table
  437. html = ''
  438. htmlHeader = '<html><head><title>Expired Domain List</title></head>'
  439. htmlBody = '<body><p>The following available domains report was generated at {}</p>'.format(timestamp)
  440. htmlTableHeader = '''
  441. <table border="1" align="center">
  442. <th>Domain</th>
  443. <th>Birth</th>
  444. <th>Entries</th>
  445. <th>TLDs Available</th>
  446. <th>Status</th>
  447. <th>Symantec</th>
  448. <th>Categorization</th>
  449. <th>IBM-xForce</th>
  450. <th>Categorization</th>
  451. <th>WatchGuard</th>
  452. <th>Namecheap</th>
  453. <th>Archive.org</th>
  454. '''
  455. htmlTableBody = ''
  456. htmlTableFooter = '</table>'
  457. htmlFooter = '</body></html>'
  458. # Build HTML table contents
  459. for i in sortedData:
  460. htmlTableBody += '<tr>'
  461. htmlTableBody += '<td>{}</td>'.format(i[0]) # Domain
  462. htmlTableBody += '<td>{}</td>'.format(i[1]) # Birth
  463. htmlTableBody += '<td>{}</td>'.format(i[2]) # Entries
  464. htmlTableBody += '<td>{}</td>'.format(i[3]) # TLDs
  465. htmlTableBody += '<td>{}</td>'.format(i[4]) # Status
  466. htmlTableBody += '<td><a href="https://sitereview.bluecoat.com/sitereview#/?search={}" target="_blank">Bluecoat</a></td>'.format(i[0]) # Bluecoat
  467. htmlTableBody += '<td>{}</td>'.format(i[5]) # Bluecoat Categorization
  468. htmlTableBody += '<td><a href="https://exchange.xforce.ibmcloud.com/url/{}" target="_blank">IBM-xForce</a></td>'.format(i[0]) # IBM xForce
  469. htmlTableBody += '<td>{}</td>'.format(i[6]) # IBM x-Force Categorization
  470. htmlTableBody += '<td><a href="http://www.borderware.com/domain_lookup.php?ip={}" target="_blank">WatchGuard</a></td>'.format(i[0]) # Borderware WatchGuard
  471. htmlTableBody += '<td><a href="https://www.namecheap.com/domains/registration/results.aspx?domain={}" target="_blank">Namecheap</a></td>'.format(i[0]) # Namecheap
  472. htmlTableBody += '<td><a href="http://web.archive.org/web/*/{}" target="_blank">Archive.org</a></td>'.format(i[0]) # Archive.org
  473. htmlTableBody += '</tr>'
  474. html = htmlHeader + htmlBody + htmlTableHeader + htmlTableBody + htmlTableFooter + htmlFooter
  475. logfilename = "{}_domainreport.html".format(timestamp)
  476. log = open(logfilename,'w')
  477. log.write(html)
  478. log.close
  479. print("\n[*] Search complete")
  480. print("[*] Log written to {}\n".format(logfilename))
  481. # Print Text Table
  482. t = Texttable(max_width=maxwidth)
  483. t.add_rows(sortedData)
  484. header = ['Domain', 'Birth', '#', 'TLDs', 'Status', 'Symantec', 'IBM']
  485. t.header(header)
  486. print(t.draw())