├── .gitignore ├── README.md ├── harbinger.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Harbinger Threat Intelligence 2 | Domain/IP/Hash threat feeds checker. Will check http://ipvoid.com, http://urlvoid.com, https://cymon.io and https://virustotal.com 3 | 4 | Install required packages with 5 | ``` 6 | pip install -r requirements.txt 7 | ``` 8 | 9 | You can put API keys inside a script. 10 | 11 | Results will be saved as json files for file analysis mode or printed on screen for single item. 12 | 13 | # Usage 14 | ``` 15 | _ _ _ _ 16 | | | | | | | (_) 17 | | |__| | __ _ _ __| |__ _ _ __ __ _ ___ _ __ 18 | | __ |/ _` | '__| '_ \| | '_ \ / _` |/ _ \ '__| 19 | | | | | (_| | | | |_) | | | | | (_| | __/ | 20 | |_| |_|\__,_|_| |_.__/|_|_| |_|\__, |\___|_| 21 | __/ | 22 | |___/ 23 | Threat Intelligence 24 | 25 | 26 | usage: harbinger.py [-h] [-i IP] [-d DOMAIN] [-a HASH] [-fd FILE_DOMAIN] 27 | [-fi FILE_IP] [-fh FILE_HASH] [--api API] [--vtapi VTAPI] 28 | 29 | Domain/IP/Hash threat feeds checker. Will check ipvoid, urlvoid, virustotal 30 | and cymon. 31 | 32 | optional arguments: 33 | -h, --help show this help message and exit 34 | -i IP, --ip IP ip address to check 35 | -d DOMAIN, --domain DOMAIN 36 | domain to check 37 | -a HASH, --hash HASH hash to check 38 | -fd FILE_DOMAIN, --file-domain FILE_DOMAIN 39 | file with domain list to check. One per line. 40 | -fi FILE_IP, --file-ip FILE_IP 41 | file with ip list to check. One per line. 42 | -fh FILE_HASH, --file-hash FILE_HASH 43 | file with hashes(MD5,SHA1,SHA256) to check. One per 44 | line. 45 | --api API Optional api key for Cymon 46 | --vtapi VTAPI Virustotal api key. 47 | ``` 48 | 49 | -------------------------------------------------------------------------------- /harbinger.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import argparse 3 | import json 4 | from time import sleep 5 | from operator import itemgetter 6 | try: 7 | import requests 8 | from cymon import Cymon 9 | from bs4 import BeautifulSoup 10 | except ImportError: 11 | print('''Some required modules is not found. 12 | Please install them with: 13 | pip install requests 14 | pip install cymon 15 | pip install beautifulsoup4''') 16 | sys.exit(1) 17 | 18 | VERSION = "1.0" 19 | CYMON_API = "" 20 | VT_API = "" 21 | UA = {"User-Agent": "Harbinger Threat Intelligence/%s" % VERSION} 22 | 23 | 24 | def read_file(filename): 25 | with open(filename) as f: 26 | for line in f: 27 | yield line.strip("\n") 28 | 29 | 30 | def get_data(url, data): 31 | try: 32 | r = requests.get(url + data, headers=UA) 33 | if r.status_code == 200: 34 | html_page = r.text 35 | return html_page 36 | else: 37 | print("Request was unsuccessful. Status code: %s" % r.status_code) 38 | return 39 | except requests.exceptions.HTTPError as e: 40 | print("Error: %s" % e) 41 | return 42 | 43 | 44 | def ipvoid(html_page): 45 | try: 46 | soup = BeautifulSoup(html_page, "html.parser") 47 | data = soup.find("table") 48 | if data: 49 | blacklist_data = data.find("td", text="Blacklist Status") 50 | blacklist_results = blacklist_data.findNext("td").text 51 | last_analysis_data = data.find("td", text="Analysis Date") 52 | last_analysis_results = last_analysis_data.findNext("td").text 53 | ip_addr_data = data.find("td", text="IP Address") 54 | ip_addr_results = ip_addr_data.findNext("td").strong.text 55 | rdns_data = data.find("td", text="Reverse DNS") 56 | rdns_results = rdns_data.findNext("td").text 57 | asn_data = data.find("td", text="ASN") 58 | asn_results = asn_data.findNext("td").text 59 | asn_owner_data = data.find("td", text="ASN Owner") 60 | asn_owner_results = asn_owner_data.findNext("td").text.replace(""", "") 61 | country_data = data.find("td", text="Country Code") 62 | country_results = country_data.findNext("td").text 63 | return blacklist_results, last_analysis_results, ip_addr_results, rdns_results, asn_results, asn_owner_results, country_results 64 | else: 65 | print("Not found on ipvoid") 66 | except AttributeError as e: 67 | print("Error parsing ipvoid: %s" % e) 68 | return 69 | 70 | 71 | def cymon(api=None, ip=None, domain=None): 72 | try: 73 | if api: 74 | cymon = Cymon(api) 75 | else: 76 | cymon = Cymon() 77 | if ip: 78 | ip_results = cymon.ip_lookup(ip) 79 | return ip_results 80 | elif domain: 81 | domain_results = cymon.domain_lookup(domain) 82 | return domain_results 83 | except requests.exceptions.HTTPError as e: 84 | print("Error or not found, Cymon says: %s" % e) 85 | return 86 | 87 | 88 | def urlvoid(html_page): 89 | try: 90 | soup = BeautifulSoup(html_page, "html.parser") 91 | data = soup.find("table") 92 | if data: 93 | safety_data = data.find("td", text="Safety Reputation") 94 | safety_results = safety_data.findNext("td").text 95 | last_analysis_data = data.find("td", text="Analysis Date") 96 | last_analysis_results = last_analysis_data.findNext("td").text 97 | domain_age_data = data.find("td", text="Domain 1st Registered") 98 | domain_age_results = domain_age_data.findNext("td").text 99 | country_data = data.find("td", text="Server Location") 100 | country_results = country_data.findNext("td").text 101 | alexa_data = data.find("td", text="Alexa Traffic Rank") 102 | alexa_results = alexa_data.findNext("td").text 103 | data_for_ip = soup.find("div", id="ipaddress") 104 | ipdata = data_for_ip.find("table") 105 | ip_addr_data = ipdata.find("td", text="IP Address") 106 | ip_addr_results = ip_addr_data.findNext("td").strong.text 107 | host_data = ipdata.find("td", text="Hostname") 108 | host_results = host_data.findNext("td").text 109 | asn_data = ipdata.find("td", text="ASN") 110 | asn_results = asn_data.findNext("td").text 111 | asn_owner_data = ipdata.find("td", text="ASN Owner") 112 | asn_owner_results = asn_owner_data.findNext("td").text.replace(""", "") 113 | ip_country_data = ipdata.find("td", text="Country Code") 114 | ip_country_results = ip_country_data.findNext("td").text 115 | return safety_results, last_analysis_results, domain_age_results, country_results, alexa_results, ip_addr_results, host_results, asn_results, asn_owner_results, ip_country_results 116 | else: 117 | print("Not found on urlvoid") 118 | except AttributeError as e: 119 | print("Error parsing urlvoid: %s" % e) 120 | return 121 | 122 | 123 | def vt_ip(ip, api): 124 | url = "https://www.virustotal.com/vtapi/v2/ip-address/report" 125 | params = {"ip": ip, "apikey": api} 126 | r = requests.get(url, params, headers=UA) 127 | if r.status_code == 204: 128 | return 204 129 | if r.status_code == 200: 130 | data = r.json() 131 | if data['response_code'] == 1: 132 | as_owner = data.get('as_owner') 133 | asn = data.get('asn') 134 | country = data.get('country') 135 | if data.get('detected_communicating_samples'): 136 | detected_comm_samples = len(data.get('detected_communicating_samples')) 137 | else: 138 | detected_comm_samples = None 139 | if data.get('detected_downloaded_samples'): 140 | detected_down_samples = len(data.get('detected_downloaded_samples')) 141 | else: 142 | detected_down_samples = None 143 | if data.get('detected_referrer_samples'): 144 | detected_ref_samples = len(data.get('detected_referrer_samples')) 145 | else: 146 | detected_ref_samples = None 147 | if data.get('detected_urls'): 148 | detected_urls = len(data.get('detected_urls')) 149 | else: 150 | detected_urls = None 151 | if data.get('resolutions'): 152 | resolutions = data.get('resolutions') 153 | else: 154 | resolutions = None 155 | if data.get('undetected_communicating_samples'): 156 | undetected_comm_samples = len(data.get('undetected_communicating_samples')) 157 | else: 158 | undetected_comm_samples = None 159 | if data.get('undetected_downloaded_samples'): 160 | undetected_down_samples = len(data.get('undetected_downloaded_samples')) 161 | else: 162 | undetected_down_samples = None 163 | if data.get('undetected_referrer_samples'): 164 | undetected_ref_samples = len(data.get('undetected_referrer_samples')) 165 | else: 166 | undetected_ref_samples = None 167 | return as_owner, asn, country, detected_comm_samples, detected_down_samples, detected_ref_samples, detected_urls,\ 168 | resolutions, undetected_comm_samples, undetected_down_samples, undetected_ref_samples 169 | else: 170 | print("Request was unsuccessful. Virustotal report: %s" % data['verbose_msg']) 171 | else: 172 | print("Something went wrong. VT status code: %s" % r.status_code) 173 | 174 | 175 | def vt_domain(domain, api): 176 | url = "https://www.virustotal.com/vtapi/v2/domain/report" 177 | params = {"domain": domain, "apikey": api} 178 | r = requests.get(url, params, headers=UA) 179 | if r.status_code == 204: 180 | return 204 181 | if r.status_code == 200: 182 | try: 183 | data = r.json() 184 | except json.JSONDecodeError as e: 185 | print("Json decoding error: %s" % e) 186 | return 187 | if data['response_code'] == 1: 188 | resolutions = data.get('resolutions') 189 | webutation = data.get('Webutation domain info') 190 | opera = data.get('Opera domain info') 191 | if data.get('undetected_referrer_samples'): 192 | undetected_ref_samples = len(data.get('undetected_referrer_samples')) 193 | else: 194 | undetected_ref_samples = None 195 | wot = data.get('WOT domain info') 196 | if data.get('detected_downloaded_samples'): 197 | detected_down_samples = len(data.get('detected_downloaded_samples')) 198 | else: 199 | detected_down_samples = None 200 | if data.get('detected_referrer_samples'): 201 | detected_ref_samples = len(data.get('detected_referrer_samples')) 202 | else: 203 | detected_ref_samples = None 204 | if data.get('detected_urls'): 205 | detected_urls = len(data.get('detected_urls')) 206 | else: 207 | detected_urls = None 208 | bitdefender = data.get('BitDefender domain info') 209 | if data.get('detected_communicating_samples'): 210 | detected_comm_samples = len(data.get('detected_communicating_samples')) 211 | else: 212 | detected_comm_samples = None 213 | if data.get('undetected_communicating_samples'): 214 | undetected_comm_samples = len(data.get('undetected_communicating_samples')) 215 | else: 216 | undetected_comm_samples = None 217 | if data.get('undetected_downloaded_samples'): 218 | undetected_down_samples = len(data.get('undetected_downloaded_samples')) 219 | else: 220 | undetected_down_samples = None 221 | alexa = data.get('Alexa rank') 222 | whois = data.get('whois') 223 | subdomains = data.get('subdomains') 224 | return resolutions, webutation, opera, undetected_ref_samples, wot, detected_down_samples, detected_ref_samples,\ 225 | detected_urls, bitdefender, detected_comm_samples, undetected_comm_samples, undetected_down_samples, alexa, whois, subdomains 226 | else: 227 | print("Request was unsuccessful. Virustotal report: %s" % data['verbose_msg']) 228 | else: 229 | print("Something went wrong, VT status code: %s" % r.status_code) 230 | 231 | 232 | def vt_hash(h, api): 233 | url = "https://www.virustotal.com/vtapi/v2/file/report" 234 | params = {"resource": h, "apikey": api} 235 | r = requests.get(url, params, headers=UA) 236 | if r.status_code == 204: 237 | return 204 238 | if r.status_code == 200: 239 | data = r.json() 240 | if data['response_code'] == 1: 241 | scan_date = data['scan_date'] 242 | results = str(data['positives']) + "/" + str(data['total']) 243 | details = data['scans'] 244 | return scan_date, results, details 245 | else: 246 | print("Request was unsuccessful. Virustotal report: %s" % data['verbose_msg']) 247 | else: 248 | print("Something went wrong, VT status code: %s" % r.status_code) 249 | 250 | 251 | def vt_url(domain, api): 252 | if domain: 253 | url = "https://www.virustotal.com/vtapi/v2/url/report" 254 | params = {"resource": "http://" + domain, "apikey": api} 255 | r = requests.get(url, params, headers=UA) 256 | if r.status_code == 204: 257 | return 204 258 | if r.status_code == 200: 259 | try: 260 | data = r.json() 261 | except json.JSONDecodeError as e: 262 | print("Json decoding error %s" % e) 263 | return 264 | if data['response_code'] == 1: 265 | scan_date = data['scan_date'] 266 | results = str(data['positives']) + "/" + str(data['total']) 267 | return scan_date, results 268 | else: 269 | print("Request was unsuccesful. Virustotal report: %s" % data['verbose_msg']) 270 | else: 271 | print("Something went wrong, VT status code: %s" % r.status_code) 272 | 273 | 274 | def main(): 275 | parser = argparse.ArgumentParser(description="Domain/IP/Hash threat feeds checker. " 276 | "Will check ipvoid, urlvoid, virustotal and cymon.") 277 | parser.add_argument("-i", "--ip", help="ip address to check") 278 | parser.add_argument("-d", "--domain", help="domain to check") 279 | parser.add_argument("-a", "--hash", help="hash to check") 280 | parser.add_argument("-fd", "--file-domain", help="file with domain list to check. One per line.") 281 | parser.add_argument("-fi", "--file-ip", help="file with ip list to check. One per line.") 282 | parser.add_argument("-fh", "--file-hash", help="file with hashes(MD5,SHA1,SHA256) to check. One per line.") 283 | parser.add_argument("--api", help="Optional api key for Cymon") 284 | parser.add_argument("--vtapi", help="Virustotal api key.") 285 | args = parser.parse_args() 286 | if CYMON_API: 287 | args.api = CYMON_API 288 | if VT_API: 289 | args.vtapi = VT_API 290 | if args.ip: 291 | if args.api: 292 | cymon_data = cymon(api=args.api, ip=args.ip) 293 | else: 294 | cymon_data = cymon(ip=args.ip) 295 | data = get_data("http://ipvoid.com/scan/", args.ip) 296 | ipvoid_results = ipvoid(data) 297 | if args.vtapi: 298 | vt_data = vt_ip(args.ip, args.vtapi) 299 | counter = 0 300 | while vt_data == 204 and counter < 3: 301 | print("Virustotal API limit reached. Public API limit is 4 req/per minute." 302 | "Sleeping for 30 seconds. Will try 3 attempts.") 303 | sleep(30) 304 | vt_data = vt_ip(args.ip, args.vtapi) 305 | counter += 1 306 | if vt_data and counter < 3: 307 | print(''' 308 | Virustotal report for IP %s 309 | ======================= 310 | AS owner: %s 311 | ASN: %s 312 | Location: %s 313 | How many malicious samples communicated with this IP: %s 314 | How many malicious samples were downloaded from this IP: %s 315 | How many malicious samples embed this IP in their strings: %s 316 | How many malicious URLs hosted by this IP: %s 317 | How many undetected samples communicated with this IP: %s 318 | How many undetected samples were downloaded from this IP: %s 319 | How many undetected samples embed this IP in their strings: %s 320 | ''' % (args.ip, vt_data[0], vt_data[1], vt_data[2], vt_data[3], vt_data[4], vt_data[5], vt_data[6], 321 | vt_data[8], vt_data[9], vt_data[10])) 322 | 323 | try: 324 | print(''' 325 | The following domains resolve to this IP(last 10): ''') 326 | sorted_ips = sorted(vt_data[7], key=itemgetter("last_resolved"), reverse=True) 327 | for i in sorted_ips[:10]: 328 | print(''' 329 | Domain: %s ''' % i.get("hostname"), 330 | ''' 331 | Last resolved: %s''' % i.get("last_resolved")) 332 | except TypeError: 333 | pass 334 | if cymon_data: 335 | print(''' 336 | Cymon report for IP %s 337 | ======================= 338 | Record created: %s 339 | Last updated: %s 340 | Blacklisted by: %s 341 | ''' % (args.ip, cymon_data.get('created'), cymon_data.get('updated'), cymon_data.get('sources'))) 342 | if ipvoid_results: 343 | print(''' 344 | ipvoid report for IP %s 345 | ======================= 346 | Blacklist: %s 347 | Last time analysed: %s 348 | Reverse DNS: %s 349 | ASN: %s 350 | ASN Owner: %s 351 | Location: %s 352 | ''' % (args.ip, ipvoid_results[0], ipvoid_results[1], ipvoid_results[3], ipvoid_results[4], ipvoid_results[5], 353 | ipvoid_results[6])) 354 | elif args.domain: 355 | if args.api: 356 | cymon_data = cymon(api=args.api, domain=args.domain) 357 | else: 358 | cymon_data = cymon(domain=args.domain) 359 | data = get_data("http://urlvoid.com/scan/", args.domain) 360 | if args.vtapi: 361 | vt_data1 = vt_domain(args.domain, args.vtapi) 362 | vt_data2 = vt_url(args.domain, args.vtapi) 363 | counter = 0 364 | while vt_data1 == 204 or vt_data2 == 204 and counter < 3: 365 | print("Virustotal API limit reached. Public API limit is 4 req/per minute." 366 | "Sleeping for 30 seconds. Will try 3 attempts.") 367 | sleep(30) 368 | vt_data1 = vt_domain(args.domain, args.vtapi) 369 | vt_data2 = vt_url(args.domain, args.vtapi) 370 | counter += 1 371 | if vt_data1 and vt_data2 and counter < 3: 372 | print(''' 373 | Virustotal report for domain %s 374 | ======================= 375 | Blacklist: %s 376 | Last time analysed: %s 377 | Webutation report: %s 378 | Opera report: %s 379 | WOT report: %s 380 | BitDefender report: %s 381 | Alexa rank: %s 382 | How many malicious samples communicated with this domain: %s 383 | How many malicious samples were downloaded from this domain: %s 384 | How many malicious samples embed this domain in their strings: %s 385 | How many malicious URLs hosted by this domain: %s 386 | How many undetected samples communicated with this domain: %s 387 | How many undetected samples were downloaded from this domain: %s 388 | How many undetected samples embed this domain in their strings: %s 389 | Subdomains on this domain: 390 | %s 391 | WHOIS: 392 | ====== 393 | %s 394 | ''' % (args.domain, vt_data2[1], vt_data2[0], vt_data1[1], vt_data1[2], vt_data1[4], vt_data1[8], 395 | vt_data1[12], vt_data1[9], vt_data1[5], vt_data1[6], vt_data1[7], vt_data1[10], vt_data1[11], 396 | vt_data1[3], vt_data1[14], vt_data1[13])) 397 | 398 | try: 399 | print(''' 400 | This domain resolves to following IPs(last 10): ''') 401 | sorted_ips = sorted(vt_data1[0], key=itemgetter('last_resolved'), reverse=True) 402 | for i in sorted_ips[:10]: 403 | print(''' 404 | IP: %s''' % i.get("ip_address"), 405 | ''' 406 | Last resolved: %s''' % i.get("last_resolved")) 407 | except TypeError: 408 | pass 409 | if data: 410 | urlvoid_results = urlvoid(data) 411 | if urlvoid_results: 412 | print(''' 413 | urlvoid report for domain %s 414 | ======================= 415 | Blacklist: %s 416 | Last time analysed: %s 417 | Domain 1st Registered: %s 418 | Location: %s 419 | Alexa Rank: %s 420 | IP: %s 421 | Hostname: %s 422 | ASN: %s 423 | ASN Owner: %s 424 | IP Location: %s 425 | ''' % (args.domain, urlvoid_results[0], urlvoid_results[1], urlvoid_results[2], urlvoid_results[3], urlvoid_results[4], 426 | urlvoid_results[5], urlvoid_results[6], urlvoid_results[7], urlvoid_results[8], urlvoid_results[9])) 427 | if cymon_data: 428 | print(''' 429 | Cymon report for domain %s 430 | ======================= 431 | Record created: %s 432 | Last updated: %s 433 | Blacklisted by: %s 434 | ''' % (args.domain, cymon_data.get('created'), cymon_data.get('updated'), cymon_data.get('sources'))) 435 | elif args.hash: 436 | if args.vtapi: 437 | vt_data = vt_hash(args.hash, args.vtapi) 438 | counter = 0 439 | while vt_data == 204 and counter < 3: 440 | print("Virustotal API limit reached. Public API limit is 4 req/per minute." 441 | "Sleeping for 30 seconds. Will try 3 attempts.") 442 | sleep(30) 443 | vt_data = vt_hash(args.hash, args.vtapi) 444 | counter += 1 445 | if vt_data and counter < 3: 446 | print(''' 447 | Virustotal report for hash %s 448 | ======================= 449 | Detection: %s 450 | Last time analysed: %s 451 | ''' % (args.hash, vt_data[1], vt_data[0])) 452 | print("Analysis details: ") 453 | for k, v in vt_data[2].items(): 454 | print(''' 455 | Antivirus %s version %s 456 | ============ 457 | Result: %s 458 | Last Updated: %s 459 | Detected: %s 460 | ''' % (k, v.get('version'), v.get('result'), v.get('update'), v.get('detected'))) 461 | elif args.file_ip: 462 | if CYMON_API: 463 | args.api = CYMON_API 464 | if VT_API: 465 | args.vtapi = VT_API 466 | data = read_file(args.file_ip) 467 | with open("ips_report.json", 'w') as f: 468 | results = {"ip": "", "cymon_record_created": "", "cymon_last_updated": "", "cymon_blacklists": "", "cymon_url": "", "ipvoid_blacklists": "", 469 | "ipvoid_last_time_analysed": "", "ipvoid_reverse_dns": "", "ipvoid_asn": "", "ipvoid_asn_owner": "", 470 | "ipvoid_location": "", "ipvoid_url": "", "vt_asn": "", "vt_asn_owner": "", "vt_location": "", 471 | "vt_count_samples_malicious_communicated_with": "", "vt_count_samples_malicious_downloaded_from": "", 472 | "vt_count_samples_malicious_embed_this_address": "", "vt_count_malicious_urls_hosted_by": "", 473 | "vt_count_samples_undetected_communicated_with": "", "vt_count_samples_undetected_downloaded_from": "", 474 | "vt_count_samples_undetected_embed_this_address": "", "vt_last10_dns_resolutions": "", "vt_url": ""} 475 | for ip in data: 476 | print("Working on %s" % ip) 477 | results["ip"] = ip 478 | if args.api: 479 | cymon_data = cymon(api=args.api, ip=ip) 480 | else: 481 | cymon_data = cymon(ip=ip) 482 | if cymon_data: 483 | results["cymon_record_created"] = cymon_data.get('created') 484 | results["cymon_last_updated"] = cymon_data.get('updated') 485 | results["cymon_blacklists"] = cymon_data.get('sources') 486 | results["cymon_url"] = "https://cymon.io/" + ip 487 | ipvoid_data = get_data("http://ipvoid.com/scan/", ip) 488 | if ipvoid_data: 489 | ipvoid_results = ipvoid(ipvoid_data) 490 | if ipvoid_results: 491 | results["ipvoid_blacklists"] = ipvoid_results[0].strip("POSSIBLY SAFE").strip("BLACKLISTED") 492 | results["ipvoid_last_time_analysed"] = ipvoid_results[1] 493 | results["ipvoid_reverse_dns"] = ipvoid_results[3] 494 | results["ipvoid_asn"] = ipvoid_results[4] 495 | results["ipvoid_asn_owner"] = ipvoid_results[5] 496 | results["ipvoid_location"] = ipvoid_results[6] 497 | results["ipvoid_url"] = "http://ipvoid.com/scan/" + ip 498 | if args.vtapi: 499 | vt_data = vt_ip(ip, args.vtapi) 500 | counter = 0 501 | while vt_data == 204 and counter < 3: 502 | print("Virustotal API limit reached. Public API limit is 4 req/per minute." 503 | "Sleeping for 30 seconds. Will try 3 attempts.") 504 | sleep(30) 505 | vt_data = vt_ip(ip, args.vtapi) 506 | counter += 1 507 | if vt_data and counter < 3: 508 | last10_ip_resolutions = [] 509 | if vt_data[7]: 510 | sorted_ips = sorted(vt_data[7], key=itemgetter("last_resolved"), reverse=True) 511 | for i in sorted_ips[:10]: 512 | last10_ip_resolutions.append({"ip_address": i.get("ip_address"), "last_resolved": i.get("last_resolved")}) 513 | results["vt_asn"] = vt_data[1] 514 | results["vt_asn_owner"] = vt_data[0] 515 | results["vt_location"] = vt_data[2] 516 | results["vt_count_samples_malicious_communicated_with"] = vt_data[3] 517 | results["vt_count_samples_malicious_downloaded_from"] = vt_data[4] 518 | results["vt_count_samples_malicious_embed_this_address"] = vt_data[5] 519 | results["vt_count_malicious_urls_hosted_by"] = vt_data[6] 520 | results["vt_count_samples_undetected_communicated_with"] = vt_data[8] 521 | results["vt_count_samples_undetected_downloaded_from"] = vt_data[9] 522 | results["vt_count_samples_undetected_embed_this_address"] = vt_data[10] 523 | if last10_ip_resolutions: 524 | results["vt_last10_dns_resolutions"] = last10_ip_resolutions 525 | results["vt_url"] = "https://www.virustotal.com/en/ip-address/%s/information/" % ip 526 | json.dump(results, f, indent=4) 527 | print("File ips_report.json created") 528 | elif args.file_domain: 529 | data = read_file(args.file_domain) 530 | if CYMON_API: 531 | args.api = CYMON_API 532 | if VT_API: 533 | args.vtapi = VT_API 534 | with open("domains_report.json", 'w') as f: 535 | results = {"domain": "", "cymon_record_created": "", "cymon_last_updated": "", "cymon_blacklists": "", "cymon_url": "", "urlvoid_blacklists": "", 536 | "urlvoid_last_time_analysed": "", "urlvoid_domain_1st_registration": "", "urlvoid_location": "", "urlvoid_alexa": "", 537 | "urlvoid_ip": "", "urlvoid_hostname": "", "urlvoid_asn": "", "urlvoid_asn_owner": "", "urlvoid_ip_location": "", "urlvoid_url": "", 538 | "vt_blacklist": "", "vt_last_time_analysed": "", "vt_webutation": "", "vt_opera": "", "vt_wot": "", 539 | "vt_bitdefender": "", "vt_alexa": "", "vt_count_samples_malicious_communicated_with": "", "vt_count_samples_malicious_downloaded_from": "", 540 | "vt_count_samples_malicious_embed_this_domain": "", "vt_count_malicious_urls_hosted_by": "", 541 | "vt_count_samples_undetected_communicated_with": "", "vt_count_samples_undetected_downloaded_from": "", 542 | "vt_count_samples_undetected_embed_this_domain": "", "vt_subdomains": "", "vt_whois": "", 543 | "vt_last10_ip_resolutions": "", "vt_url": ""} 544 | for domain in data: 545 | print("Working on %s" % domain) 546 | results["domain"] = domain 547 | if args.api: 548 | cymon_data = cymon(api=args.api, domain=domain) 549 | else: 550 | cymon_data = cymon(domain=domain) 551 | if cymon_data: 552 | results["cymon_record_created"] = cymon_data.get('created') 553 | results["cymon_last_updated"] = cymon_data.get('updated') 554 | results["cymon_blacklists"] = cymon_data.get('sources') 555 | results["cymon_url"] = "https://cymon.io/domain/" + domain 556 | urlvoid_data = get_data("http://urlvoid.com/scan/", domain) 557 | if urlvoid_data: 558 | urlvoid_results = urlvoid(urlvoid_data) 559 | if urlvoid_results: 560 | results["urlvoid_blacklists"] = urlvoid_results[0] 561 | results["urlvoid_last_time_analysed"] = urlvoid_results[1] 562 | results["urlvoid_domain_1st_registration"] = urlvoid_results[2] 563 | results["urlvoid_location"] = urlvoid_results[3] 564 | results["urlvoid_alexa"] = urlvoid_results[4] 565 | results["urlvoid_ip"] = urlvoid_results[5] 566 | results["urlvoid_hostname"] = urlvoid_results[6] 567 | results["urlvoid_asn"] = urlvoid_results[7] 568 | results["urlvoid_asn_owner"] = urlvoid_results[8] 569 | results["urlvoid_ip_location"] = urlvoid_results[9] 570 | results["urlvoid_url"] = "http://urlvoid.com/scan/" + domain 571 | if args.vtapi: 572 | vt_data1 = vt_domain(domain, args.vtapi) 573 | vt_data2 = vt_url(domain, args.vtapi) 574 | counter = 0 575 | while vt_data1 == 204 or vt_data2 == 204 and counter < 3: 576 | print("Virustotal API limit reached. Public API limit is 4 req/per minute." 577 | "Sleeping for 30 second. Will try 3 attempts.") 578 | sleep(30) 579 | vt_data1 = vt_domain(domain, args.vtapi) 580 | vt_data2 = vt_url(domain, args.vtapi) 581 | counter += 1 582 | if vt_data1 and vt_data2 and counter < 3: 583 | last10_ip_resolutions = [] 584 | if vt_data1[0]: 585 | sorted_ips = sorted(vt_data1[0], key=itemgetter("last_resolved"), reverse=True) 586 | for i in sorted_ips[:10]: 587 | last10_ip_resolutions.append({"ip_address": i.get("ip_address"), "last_resolved": i.get("last_resolved")}) 588 | results["vt_blacklist"] = vt_data2[1] 589 | results["vt_last_time_analysed"] = vt_data2[0] 590 | results["vt_webutation"] = vt_data1[1] 591 | results["vt_opera"] = vt_data1[2] 592 | results["vt_wot"] = vt_data1[4] 593 | results["vt_bitdefender"] = vt_data1[8] 594 | results["vt_alexa"] = vt_data1[12] 595 | results["vt_count_samples_malicious_communicated_with"] = vt_data1[9] 596 | results["vt_count_samples_malicious_downloaded_from"] = vt_data1[5] 597 | results["vt_count_samples_malicious_embed_this_address"] = vt_data1[6] 598 | results["vt_count_malicious_urls_hosted_by"] = vt_data1[7] 599 | results["vt_count_samples_undetected_communicated_with"] = vt_data1[10] 600 | results["vt_count_samples_undetected_downloaded_from"] = vt_data1[11] 601 | results["vt_count_samples_undetected_embed_this_address"] = vt_data1[3] 602 | if last10_ip_resolutions: 603 | results["vt_last10_ip_resolutions"] = last10_ip_resolutions 604 | results["vt_subdomains"] = vt_data1[14] 605 | results["vt_whois"] = vt_data1[13] 606 | results["vt_url"] = "https://www.virustotal.com/en/domain/%s/information/" % domain 607 | json.dump(results, f, indent=4) 608 | print("File domains_report.json created") 609 | elif args.file_hash: 610 | data = read_file(args.file_hash) 611 | if VT_API: 612 | args.vtapi = VT_API 613 | with open("hashes_report.json", "w") as f: 614 | results = {"hash": "", "vt_detection": "", "vt_last_time_analysed": "", "vt_details": ""} 615 | for h in data: 616 | print("Working on %s" % h) 617 | results["hash"] = h 618 | if args.vtapi: 619 | vt_data = vt_hash(h, args.vtapi) 620 | counter = 0 621 | while vt_data == 204 and counter < 3: 622 | print("Virustotal API limit reached. Public API limit is 4 req/per minute." 623 | "Sleeping for 30 seconds. Will try 3 attempts.") 624 | sleep(30) 625 | vt_data = vt_hash(h, args.vtapi) 626 | counter += 1 627 | print(counter) 628 | if vt_data and counter < 3: 629 | results["vt_detection"] = vt_data[1] 630 | results["vt_last_time_analysed"] = vt_data[0] 631 | results["vt_details"] = vt_data[2] 632 | json.dump(results, f, indent=4) 633 | print("File hashes_report.json is created") 634 | else: 635 | print(''' 636 | _ _ _ _ 637 | | | | | | | (_) 638 | | |__| | __ _ _ __| |__ _ _ __ __ _ ___ _ __ 639 | | __ |/ _` | '__| '_ \| | '_ \ / _` |/ _ \ '__| 640 | | | | | (_| | | | |_) | | | | | (_| | __/ | 641 | |_| |_|\__,_|_| |_.__/|_|_| |_|\__, |\___|_| 642 | __/ | 643 | |___/ 644 | Threat Intelligence 645 | 646 | ''') 647 | parser.print_help() 648 | main() 649 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | cymon 3 | beautifulsoup4 --------------------------------------------------------------------------------