Automated basic digital reconnaissance. Great for getting an initial footprint of your targets and discovering additional subdomains. InstaRecon will do:
– DNS (direct, PTR, MX, NS) lookups
– Whois (domains and IP) lookups
– Google dorks in search of subdomains
– Shodan lookups
– Reverse DNS lookups on entire CIDRs
…all printed nicely on your console or csv file.
InstaRecon will never scan a target directly. Information is retrieved from DNS/Whois servers, Google, and Shodan.
Requirement :
+ argparse==1.2.1
+ click==3.3
+ colorama==0.3.3
+ dnspython==1.12.0
+ ipaddr==2.1.11
+ ipaddress==1.0.7
+ ipwhois==0.10.1
+ pythonwhois==2.4.3
+ requests==2.5.3
+ shodan==1.2.6
+ simplejson==3.6.5
+ wsgiref==0.1.2
instarecon.py Code:
#!/usr/bin/env python import sys import socket import argparse import requests import re import time from random import randint import csv import os import datetime import pythonwhois as whois #http://cryto.net/pythonwhois/usage.html https://github.com/joepie91/python-whois from ipwhois import IPWhois as ipw #https://pypi.python.org/pypi/ipwhois import ipaddress as ipa #https://docs.python.org/3/library/ipaddress.html import dns.resolver,dns.reversename,dns.name #http://www.dnspython.org/docs/1.12.0/ import shodan #https://shodan.readthedocs.org/en/latest/index.html class Host(object): """ Host represents an entity on the internet. Can originate from a domain or from an IP. Keyword arguments: domain -- DNS/Domain name e.g. google.com type -- either 'domain' or 'ip', depending on original information passed to __init__ ips -- List of instances of IP. Each instance contains: ip -- Str representation of IP e.g. 8.8.8.8 rev_domains -- list of str representing reverse domains related to ip whois_ip -- Dict containing results from a Whois lookup on an IP shodan -- Dict containing results from Shodan lookups cidr -- Str of CIDR that this ip is part of (taken from whois_ip results) mx -- Set of Hosts for each DNS MX entry for original self.domain ns -- Set of Hosts for each DNS NS entry for original self.domain whois_domain -- str representation of the Whois query subdomains -- Set of Hosts for each related Host found that is a subdomain of self.domain linkedin_page -- Str of LinkedIn url that contains domain in html related_hosts -- Set of Hosts that may be related to host, as they're part of the same cidrs cidrs -- set of ipa.Ipv4Networks for each ip.cidr """ def __init__(self,domain=None,ips=(),reverse_domains=()): #Type check - depends on what parameters have been passed if domain: self.type = 'domain' elif ips: self.type = 'ip' else: raise ValueError self.domain = domain self.ips = [ IP(str(ip),reverse_domains) for ip in ips ] self.mx = set() self.ns = set() self.whois_domain = None self.subdomains = set() self.linkedin_page = None self.related_hosts = set() self.cidrs = set() dns_resolver = dns.resolver.Resolver() dns_resolver.timeout = 5 dns_resolver.lifetime = 5 def __str__(self): if self.type == 'domain': return str(self.domain) elif self.type == 'ip': return str(self.ips[0]) def __hash__(self): if self.type == 'domain': return hash(('domain',self.domain)) elif self.type == 'ip': return hash(('ip',','.join([str(ip) for ip in self.ips]))) def __eq__(self,other): if self.type == 'domain': return self.domain == other.domain elif self.type == 'ip': return self.ips == other.ips def dns_lookups(self): """ Basic DNS lookups on self. Returns self. 1) Direct DNS lookup on self.domain 2) Reverse DNS lookup on each self.ips """ if self.type == 'domain': self._get_ips() for ip in self.ips: ip.get_rev_domains() return self def mx_dns_lookup(self): """ DNS lookup to find MX entries i.e. mail servers responsible for self.domain """ if self.domain: mx_list = self._ret_mx_by_name(self.domain) if mx_list: self.mx.update([ Host(domain=mx).dns_lookups() for mx in mx_list ]) self._add_to_subdomains_if_valid(subdomains_as_hosts=self.mx) def ns_dns_lookup(self): """ DNS lookup to find NS entries i.e. name/DNS servers responsible for self.domain """ if self.domain: ns_list = self._ret_ns_by_name(self.domain) if ns_list: self.ns.update([ Host(domain=ns).dns_lookups() for ns in ns_list ]) self._add_to_subdomains_if_valid(subdomains_as_hosts=self.ns) def google_lookups(self): """ Google queries to find related subdomains and linkedin pages. """ if self.domain: self.linkedin_page = self._ret_linkedin_page_from_google(self.domain) self._add_to_subdomains_if_valid(subdomains_as_str=self._ret_subdomains_from_google()) def get_rev_domains_for_ips(self): """ Reverse DNS lookup on each IP in self.ips """ if self.ips: for ip in self.ips: ip.get_rev_domains() return self def get_whois_domain(self,num=0): """ Whois lookup on self.domain. Saved in self.whois_domain as string, since each top-level domain has a way of representing data. This makes it hard to index it, almost a project on its on. """ try: if self.domain: query = whois.get_whois(self.domain) if 'raw' in query: self.whois_domain = query['raw'][0].split('<')[0].lstrip().rstrip() except Exception as e: InstaRecon.error(e,sys._getframe().f_code.co_name) pass def get_all_whois_ip(self): """ IP Whois lookups on each ip within self.ips Saved in each ip.whois_ip as dict, since this is how it is returned by ipwhois library. """ #Keeps track of lookups already made - cidr as key, whois_ip dict as val cidrs_found = {} for ip in self.ips: cidr_found = False for cidr,whois_ip in cidrs_found.iteritems(): if ipa.ip_address(ip.ip.decode('unicode-escape')) in cidr: #cidr already IPv4.Network obj #If cidr is already in Host, we won't get_whois_ip again. #Instead we will save cidr in ip just to make it easier to relate them later ip.cidr,ip.whois_ip = cidr,whois_ip cidr_found = True break if not cidr_found: ip.get_whois_ip() cidrs_found[ip.cidr] = ip.whois_ip self.cidrs = set([ip.cidr for ip in self.ips if ip.cidr]) def get_all_shodan(self,key): """ Shodan lookups for each ip within self.ips. Saved in ip.shodan as dict. """ if key: for ip in self.ips: ip.get_shodan(key) def _get_ips(self): """ Does direct DNS lookup to get IPs from self.domains. Used internally by self.dns_lookups() """ if self.domain and not self.ips: ips = self._ret_host_by_name(self.domain) if ips: self.ips = [ IP(str(ip)) for ip in ips ] return self @staticmethod def _ret_host_by_name(name): try: return Host.dns_resolver.query(name) except (dns.resolver.NXDOMAIN,dns.resolver.NoAnswer,dns.exception.Timeout) as e: InstaRecon.error('[-] Host lookup failed for '+name,sys._getframe().f_code.co_name) pass @staticmethod def _ret_mx_by_name(name): try: #rdata.exchange for domains and rdata.preference for integer return [str(mx.exchange).rstrip('.') for mx in Host.dns_resolver.query(name,'MX')] except (dns.resolver.NXDOMAIN,dns.resolver.NoAnswer,dns.exception.Timeout) as e: InstaRecon.error('[-] MX lookup failed for '+name,sys._getframe().f_code.co_name) pass @staticmethod def _ret_ns_by_name(name): try: #rdata.exchange for domains and rdata.preference for integer return [str(ns).rstrip('.') for ns in Host.dns_resolver.query(name,'NS')] except (dns.resolver.NXDOMAIN,dns.resolver.NoAnswer,dns.exception.Timeout) as e: InstaRecon.error('[-] NS lookup failed for '+name,sys._getframe().f_code.co_name) pass @staticmethod def _ret_linkedin_page_from_google(name): """ Uses a google query to find a possible LinkedIn page related to name (usually self.domain) Google query is "site:linkedin.com/company name", and first result is used """ try: request='http://google.com/search?hl=en&meta=&num=10&q=site:linkedin.com/company%20"'+name+'"' google_search = requests.get(request) google_results = re.findall('<cite>(.+?)<\/cite>', google_search.text) for url in google_results: if 'linkedin.com/company/' in url: return re.sub('<.*?>', '', url) except Exception as e: InstaRecon.error(e,sys._getframe().f_code.co_name) def _ret_subdomains_from_google(self): """ This method uses google dorks to get as many subdomains from google as possible It returns a set of Hosts for each subdomain found in google Each Host will have dns_lookups() already callled, with possibly ips and rev_domains filled """ def _google_subdomains_lookup(domain,subdomains_to_avoid,num,counter): """ Sub method that reaches out to google using the following query: site:*.domain -site:subdomain_to_avoid1 -site:subdomain_to_avoid2 -site:subdomain_to_avoid3... Returns list of unique subdomain strings """ #Sleep some time between 0 - 4.999 seconds time.sleep(randint(0,4)+randint(0,1000)*0.001) request = 'http://google.com/search?hl=en&meta=&num='+str(num)+'&start='+str(counter)+'&q='+\ 'site%3A%2A'+domain for subdomain in subdomains_to_avoid: #Don't want to remove original name from google query if subdomain != domain: request = ''.join([request,'%20%2Dsite%3A',str(subdomain)]) google_search = None try: google_search = requests.get(request) except Exception as e: InstaRecon.error(e,sys._getframe().f_code.co_name) new_subdomains = set() if google_search: google_results = re.findall('<cite>(.+?)<\/cite>', google_search.text) for url in set(google_results): #Removing html tags from inside url (sometimes they ise <b> or <i> for ads) url = re.sub('<.*?>', '', url) #Follows Javascript pattern of accessing URLs g_host = url g_protocol = '' g_pathname = '' temp = url.split('://') #If there is g_protocol e.g. http://, ftp://, etc if len(temp)>1: g_protocol = temp[0] #remove g_protocol from url url = ''.join(temp[1:]) temp = url.split('/') #if there is a pathname after host if len(temp)>1: g_pathname = '/'.join(temp[1:]) g_host = temp[0] new_subdomains.add(g_host) #TODO do something with g_pathname and g_protocol #Currently not using protocol or pathname for anything return list(new_subdomains) #Keeps subdomains found by _google_subdomains_lookup subdomains_discovered = [] #Variable to check if there is any new result in the last iteration subdomains_in_last_iteration = -1 while len(subdomains_discovered) > subdomains_in_last_iteration: subdomains_in_last_iteration = len(subdomains_discovered) subdomains_discovered += _google_subdomains_lookup(self.domain,subdomains_discovered,100,0) subdomains_discovered = list(set(subdomains_discovered)) subdomains_discovered += _google_subdomains_lookup(self.domain,subdomains_discovered,100,100) subdomains_discovered = list(set(subdomains_discovered)) return subdomains_discovered def _add_to_subdomains_if_valid(self,subdomains_as_str=None,subdomains_as_hosts=None): """ Add Hosts from subdomains_as_str or subdomains_as_hosts to self.subdomains if indeed these hosts are subdomains subdomains_as_hosts and subdomains_as_str should be iterable list or set """ if subdomains_as_str: self.subdomains.update( [ Host(domain=subdomain).dns_lookups() for subdomain in subdomains_as_str if self._is_parent_domain_of(subdomain) ] ) elif subdomains_as_hosts: self.subdomains.update( [ subdomain for subdomain in subdomains_as_hosts if self._is_parent_domain_of(subdomain) ] ) def _is_parent_domain_of(self,subdomain): """ Checks if subdomain is indeed a subdomain of self.domain In addition it filters out invalid dns names """ if isinstance(subdomain, Host): #If subdomain has .domain if subdomain.domain: try: return dns.name.from_text(subdomain.domain).is_subdomain(dns.name.from_text(self.domain)) except Exception as e: pass #If subdomain doesn't have .domain, if was found through reverse dns scan on cidr #So I must add the rev parameter to subdomain as .domain, so it looks better on the csv elif subdomain.ips[0].rev_domains: for rev in subdomain.ips[0].rev_domains: try: if dns.name.from_text(rev).is_subdomain(dns.name.from_text(self.domain)): #Adding a .rev_domains str to .domain subdomain.domain = rev return True except Exception as e: pass else: try: return dns.name.from_text(subdomain).is_subdomain(dns.name.from_text(self.domain)) except dns.name.EmptyLabel: #EmptyLabel is an exception raised for bad dns strings pass return False def reverse_lookup_on_related_cidrs(self,feedback=False): """ Does reverse dns lookups in all cidrs that are related to this host Will be used to check for subdomains found through reverse lookup """ for cidr in self.cidrs: for ip in cidr: #Holds lookup results lookup_result = None #Used to repeat same scan if user issues KeyboardInterrupt this_scan_completed = False while not this_scan_completed: try: lookup_result = Host.dns_resolver.query(dns.reversename.from_address(str(ip)),'PTR') this_scan_completed = True except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.NoNameservers, dns.exception.Timeout) as e: this_scan_completed = True except KeyboardInterrupt: if isinstance(self, Network): raise KeyboardInterrupt else: if raw_input('[-] Sure you want to stop scanning '+str(cidr)+\ '? Program flow will continue normally. (y/N):') in ['Y','y']: return if lookup_result: #Organizing reverse lookup results reverse_domains = [ str(domain).rstrip('.') for domain in lookup_result ] #Creating new host new_host = Host(ips=[ip],reverse_domains=reverse_domains) #Append host to current host self.related_hosts self.related_hosts.add(new_host) #Don't want to do this in case self is Network if type(self) is Host: #Adds new_host to self.subdomains if new_host indeed is subdomain self._add_to_subdomains_if_valid(subdomains_as_hosts=[new_host]) if feedback: print new_host.print_all_ips() if not self.related_hosts: print '# No results for this range' def print_all_ips(self): if self.ips: return '\n'.join([ ip.print_ip() for ip in self.ips ]).rstrip() def print_subdomains(self): return self._print_domains(sorted(self.subdomains, key=lambda x: x.domain)) @staticmethod def _print_domains(hosts): #Static method that prints a list of domains with its respective ips and rev_domains #domains should be a list of Hosts if hosts: ret = '' for host in hosts: ret = ''.join([ret,host.domain]) p = host.print_all_ips() if p: ret = ''.join([ret,'\n\t',p.replace('\n','\n\t')]) ret = ''.join([ret,'\n']) return ret.rstrip().lstrip() def print_all_ns(self): #Print all NS records return self._print_domains(self.ns) def print_all_mx(self): #Print all MS records return self._print_domains(self.mx) def print_dns_only(self): return self._print_domains([self]) def print_all_whois_ip(self): #Prints whois_ip records related to all self.ips ret = set([ip.print_whois_ip() for ip in self.ips if ip.whois_ip]) return '\n'.join(ret).lstrip().rstrip() def print_all_shodan(self): #Print all Shodan entries (one for each IP in self.ips) ret = [ ip.print_shodan() for ip in self.ips if ip.shodan ] return '\n'.join(ret).lstrip().rstrip() def print_as_csv_lines(self): """Generator that yields each IP within self.ips as a csv line.""" yield ['Target:', str(self)] if self.ips: yield [ 'Domain', 'IP', 'Reverse domains', 'NS', 'MX', # 'Subdomains', 'Domain whois', 'IP whois', 'Shodan', 'LinkedIn page', 'CIDRs' ] for ip in self.ips: yield [ self.domain, str(ip), '\n'.join(ip.rev_domains), self.print_all_ns(), self.print_all_mx(), # self.print_subdomains(), self.whois_domain, ip.print_whois_ip(), ip.print_shodan(), self.linkedin_page, ', '.join([str(cidr) for cidr in self.cidrs]) ] if self.subdomains: yield ['\n'] yield ['Subdomains for '+str(self.domain)] yield ['Domain','IP','Reverse domains'] for sub in sorted(self.subdomains, key=lambda x: x.domain): for ip in sub.ips: if sub.domain: yield [ sub.domain,ip.ip,','.join( ip.rev_domains ) ] else: yield [ ip.rev_domains[0],ip.ip,','.join( ip.rev_domains ) ] if self.related_hosts: yield ['\n'] yield ['Hosts in same CIDR as',str(self),'(all results found, including subdomains)'] yield ['IP','Reverse domains'] for sub in sorted(self.related_hosts, key=lambda x: x.ips[0]): yield [ ','.join([ str(ip) for ip in sub.ips ]), ','.join([ ','.join(ip.rev_domains) for ip in sub.ips ]), ] def do_all_lookups(self, shodan_key=None): """ This method does all possible direct lookups for a Host. Not called by any Host or Scan function, only here for testing purposes. """ self.dns_lookups() self.ns_dns_lookup() self.mx_dns_lookup() self.get_whois_domain() self.get_all_whois_ip() if shodan_key: self.get_all_shodan(shodan_key) self.google_lookups() class Network(Host): """ Subclass of Host that represents an IP network to be scanned. Keywork arguments: cidrs -- set of IPv4Networks to be scanned related_hosts -- set of valid Hosts found by scanning each cidr in cidrs """ def __init__(self,cidrs=()): """ cidrs parameter should be an iterable containing a single ipaddress.IPv4Network It is treated as a set of CIDRs to allow reuse of methods from Host """ self.cidrs = set([ cidr for cidr in cidrs if isinstance(cidr,ipa.IPv4Network) ]) if not self.cidrs: raise ValueError self.related_hosts = set() def __str__(self): return ','.join([str(cidr) for cidr in self.cidrs]) def __hash__(self): return hash(('cidrs',','.join([str(cidr) for cidr in self.cidrs]))) def __eq__(self,other): return self.cidrs == other.cidrs def print_as_csv_lines(self): """Overrides method from Host. Yields each Host in related_hosts as csv line""" yield ['Target: '+', '.join([ str(cidr) for cidr in self.cidrs ])] if self.related_hosts: yield ['IP','Reverse domains',] for host in self.related_hosts: yield [ ', '.join([ str(ip) for ip in host.ips ]), ', '.join([ ', '.join(ip.rev_domains) for ip in host.ips ]), ] else: yield ['No results'] class IP(object): """ IP and information specific to it. Hosts contain multiple IPs, as a domain can resolve to multiple IPs. Keyword arguments: ip -- Str representation of this ip e.g. '8.8.8.8' whois_ip -- Dict containing results for Whois lookups against self.ip cidr -- ipa.IPv4Network that contains self.ip (taken from whois_ip), e.g. 8.8.8.0/24 rev_domains -- List of str for each reverse domain for self.ip, found through reverse DNS lookups shodan -- Dict containing Shodan results """ def __init__(self,ip,rev_domains=None): if rev_domains is None: rev_domains = [] self.ip = str(ip) self.rev_domains = rev_domains self.whois_ip = {} self.cidr = None self.shodan = None def __str__(self): return str(self.ip) def __hash__(self): return hash(('ip',self.ip)) def __eq__(self,other): return self.ip == other.ip @staticmethod def _ret_host_by_ip(ip): try: return Host.dns_resolver.query(dns.reversename.from_address(ip),'PTR') except (dns.resolver.NXDOMAIN,dns.resolver.NoAnswer,dns.exception.Timeout) as e: InstaRecon.error('[-] Host lookup failed for '+ip,sys._getframe().f_code.co_name) def get_rev_domains(self): rev_domains = None rev_domains = self._ret_host_by_ip(self.ip) if rev_domains: self.rev_domains = [ str(domain).rstrip('.') for domain in rev_domains ] return self def get_shodan(self, key): try: shodan_api_key = key api = shodan.Shodan(shodan_api_key) self.shodan = api.host(str(self)) except Exception as e: InstaRecon.error(e,sys._getframe().f_code.co_name) def get_whois_ip(self): try: self.whois_ip = ipw(str(self)).lookup() or None except Exception as e: InstaRecon.error(e,sys._getframe().f_code.co_name) if self.whois_ip: if 'nets' in self.whois_ip: if self.whois_ip['nets']: cidrs = [ ipa.ip_network(net['cidr'].decode('unicode-escape'),strict=False) for net in self.whois_ip['nets'] ] for cidr in cidrs: self.cidr = self.cidr or cidr if cidr.num_addresses > self.cidr.num_addresses: self.cidr = cidr return self def print_ip(self): ret = str(self.ip) if self.rev_domains: if len(self.rev_domains) < 2: ret = ''.join([ret,' - ',self.rev_domains[0]]) else: for rev in self.rev_domains: ret = '\t'.join([ret,'\n',rev]) return ret def print_whois_ip(self): if self.whois_ip: result = '' #Printing all lines except 'nets' annd 'query' for key,val in sorted(self.whois_ip.iteritems()): if val and key not in ['nets','query']: result = '\n'.join([result,key+': '+str(val)]) #Printing each dict within 'nets' for key,net in enumerate(self.whois_ip['nets']): result = '\n'.join([result,'net '+str(key)+':']) if net['cidr']: result = '\n\t'.join([ result,'cidr' + ': ' + net['cidr'].replace('\n',', ') ]) if net['range']: result = '\n\t'.join([ result,'range' + ': ' + net['range'].replace('\n',', ') ]) if net['name']: result = '\n\t'.join([ result,'name' + ': ' + net['name'].replace('\n',', ') ]) if net['description']: result = '\n\t'.join([ result,'description' + ': ' + net['description'].replace('\n',', ') ]) if net['handle']: result = '\n\t'.join([ result,'handle' + ': ' + net['handle'].replace('\n',', ') ]) result = '\n\t'.join([ result,'' ]) if net['address']: result = '\n\t'.join([ result,'address' + ': ' + net['address'].replace('\n',', ') ]) if net['city']: result = '\n\t'.join([ result,'city' + ': ' + net['city'].replace('\n',', ') ]) if net['state']: result = '\n\t'.join([ result,'state' + ': ' + net['state'].replace('\n',', ') ]) if net['postal_code']: result = '\n\t'.join([ result,'postal_code' + ': ' + net['postal_code'].replace('\n',', ') ]) if net['country']: result = '\n\t'.join([ result,'country' + ': ' + net['country'].replace('\n',', ') ]) result = '\n\t'.join([ result,'' ]) if net['abuse_emails']: result = '\n\t'.join([ result,'abuse_emails' + ': ' + net['abuse_emails'].replace('\n',', ') ]) if net['tech_emails']: result = '\n\t'.join([ result,'tech_emails' + ': ' + net['tech_emails'].replace('\n',', ') ]) if net['misc_emails']: result = '\n\t'.join([ result,'misc_emails' + ': ' + net['misc_emails'].replace('\n',', ') ]) result = '\n\t'.join([ result,'' ]) if net['created']: result = '\n\t'.join([ result,'created' + ': ' + time.strftime("%Y-%m-%d %H:%M:%S", time.strptime(net['created'].replace('\n',', '),'%Y-%m-%dT%H:%M:%S')) ]) if net['updated']: result = '\n\t'.join([ result,'updated' + ': ' + time.strftime("%Y-%m-%d %H:%M:%S", time.strptime(net['updated'].replace('\n',', '),'%Y-%m-%dT%H:%M:%S')) ]) # for key2,val2 in sorted(net.iteritems()): # result = '\n\t'.join([result,key2+': '+str(val2).replace('\n',', ')]) return result.lstrip().rstrip() def print_shodan(self): if self.shodan: result = ''.join(['IP: ',self.shodan.get('ip_str'),'\n']) result = ''.join([result,'Organization: ',self.shodan.get('org','n/a'),'\n']) if self.shodan.get('os','n/a'): result = ''.join([result,'OS: ',self.shodan.get('os','n/a'),'\n']) if self.shodan.get('isp','n/a'): result = ''.join([result,'ISP: ',self.shodan.get('isp','n/a'),'\n']) if len(self.shodan['data']) > 0: for item in self.shodan['data']: result = '\n'.join([ result, 'Port: {}'.format(item['port']), 'Banner: {}'.format(item['data'].replace('\n','\n\t').rstrip()), ]) return result.rstrip().lstrip() class InstaRecon(object): """ Holds all Host entries and manages scans, interpret user input, threads and outputs. Keyword arguments: feedback -- Bool flag for output printing. Static variable. versobe -- Bool flag for verbose output printing. Static variable. nameserver -- Str DNS server to be used for lookups (consumed by dns.resolver module) shodan_key -- Str key used for Shodan lookups targets -- Set of Hosts or Networks that will be scanned1 bad_targets -- Set of user inputs that could not be understood or resolved """ version = '0.1' feedback = False verbose = False shodan_key = None entry_banner = '# InstaRecon v'+version+' - by Luis Teixeira (teix.co)' exit_banner = '# Done' def __init__(self,nameserver=None,timeout=None,shodan_key=None,feedback=False,verbose=False,dns_only=False): InstaRecon.feedback = feedback InstaRecon.verbose=verbose InstaRecon.shodan_key = shodan_key if nameserver: Host.dns_resolver.nameservers = [nameserver] if timeout: Host.dns_resolver.timeout = timeout Host.dns_resolver.lifetime = timeout self.dns_only = dns_only self.targets = set() self.bad_targets = set() @staticmethod def error(e, method_name=None): if InstaRecon.feedback and InstaRecon.verbose: print '# Error:', str(e),'| method:',method_name def populate(self, user_supplied_list): for user_supplied in user_supplied_list: self.add_host(user_supplied) if self.feedback: if not self.targets: print '# No hosts to scan' else: print '# Scanning',str(len(self.targets))+'/'+str(len(user_supplied_list)),'hosts' if not self.dns_only: if not self.shodan_key: print '# No Shodan key provided' else: print'# Shodan key provided -',self.shodan_key def add_host(self, user_supplied): """ Add string passed by user to self.targets as proper Host objects For this, it parses user_supplied strings to separate IPs, Domains, and Networks. """ #Test if user_supplied is an IP? try: ip = ipa.ip_address(user_supplied.decode('unicode-escape')) #if not (ip.is_multicast or ip.is_unspecified or ip.is_reserved or ip.is_loopback): self.targets.add(Host(ips=[str(ip)])) return except ValueError as e: pass #Test if user_supplied is a valid network range? try: net = ipa.ip_network(user_supplied.decode('unicode-escape'),strict=False) self.targets.add(Network([net])) return except ValueError as e: pass #Test if user_supplied is a valid DNS? try: ips = Host.dns_resolver.query(user_supplied) self.targets.add(Host(domain=user_supplied,ips=[str(ip) for ip in ips])) return except (dns.resolver.NXDOMAIN, dns.exception.SyntaxError) as e: #If here so results from network won't be so verbose if InstaRecon.feedback: print '[-] Couldn\'t resolve or understand -', user_supplied pass self.bad_targets.add(user_supplied) def scan_targets(self): for target in self.targets: if type(target) is Host: if self.dns_only: self.dns_scan_on_host(target) else: self.full_scan_on_host(target) elif type(target) is Network: self.reverse_dns_on_network(target) def reverse_dns_on_network(self,network): """Does reverse dns lookups on a network object""" fb = self.feedback if fb: print '' print '# _____________ Reverse DNS lookups on {} _____________ #'.format(str(network)) network.reverse_lookup_on_related_cidrs(fb) def full_scan_on_host(self,host): """Does all possible scans for host""" fb = self.feedback if fb: print '' print '# ____________________ Scanning {} ____________________ #'.format(str(host)) ###DNS and Whois lookups if fb: print '' print '# DNS lookups' host.dns_lookups() if fb: if host.domain: print '[*] Domain: '+host.domain #IPs and reverse domains if host.ips: print '' print '[*] IPs & reverse DNS: ' print host.print_all_ips() host.ns_dns_lookup() #NS records if host.ns and fb: print '' print '[*] NS records:' print host.print_all_ns() host.mx_dns_lookup() #MX records if host.mx and fb: print '' print '[*] MX records:' print host.print_all_mx() if fb: print '' print '# Whois lookups' host.get_whois_domain() if host.whois_domain and fb: print '' print '[*] Whois domain:' print host.whois_domain host.get_all_whois_ip() if fb: m = host.print_all_whois_ip() if m: print '' print '[*] Whois IP:' print m #Shodan lookup if self.shodan_key: if fb: print '' print '# Querying Shodan for open ports' host.get_all_shodan(self.shodan_key) if fb: m = host.print_all_shodan() if m: print '[*] Shodan:' print m #Google subdomains lookup if host.domain: if fb: print '' print '# Querying Google for subdomains and Linkedin pages, this might take a while' host.google_lookups() if fb: if host.linkedin_page: print '[*] Possible LinkedIn page: '+host.linkedin_page if host.subdomains: print '[*] Subdomains:'+'\n'+host.print_subdomains() else: print '[-] Error: No subdomains found in Google. If you are scanning a lot, Google might be blocking your requests.' #DNS lookups on entire CIDRs taken from host.get_whois_ip() if host.cidrs: if fb: print '' print '# Reverse DNS lookup on range {}'.format(', '.join([str(cidr) for cidr in host.cidrs])) host.reverse_lookup_on_related_cidrs(feedback=True) def dns_scan_on_host(self,host): """Does only direct and reverse DNS lookups for host""" fb = self.feedback if fb: print '' print '# _________________ DNS lookups on {} _________________ #'.format(str(host)) host.dns_lookups() if fb: if host.domain: print '' print host.print_dns_only() def write_output_csv(self, filename=None): """Writes output for each target as csv in filename""" if filename: filename = os.path.expanduser(filename) fb = self.feedback if fb: print '# Saving output csv file' output_as_lines = [] for host in self.targets: try: #Using generator to get one csv line at a time (one Host can yield multiple lines) generator = host.print_as_csv_lines() while True: output_as_lines.append(generator.next()) except StopIteration: #Space between targets output_as_lines.append(['\n']) output_written = False while not output_written: try: with open(filename, 'wb') as f: writer = csv.writer(f) for line in output_as_lines: writer.writerow(line) output_written = True except Exception as e: error = '[-] Something went wrong, can\'t open output file. Press anything to try again.' if self.verbose: error = ''.join([error,'\nError: ',str(e)]) raw_input(error) except KeyboardInterrupt: if raw_input('[-] Sure you want to exit without saving your file (Y/n)?') in ['y','Y','']: sys.exit('# Scan interrupted') if __name__ == '__main__': parser = argparse.ArgumentParser( description=InstaRecon.entry_banner, usage='%(prog)s [options] target1 [target2 ... targetN]', epilog=argparse.SUPPRESS, ) parser.add_argument('targets', nargs='+', help='targets to be scanned - can be in the format of a domain (google.com), an IP (8.8.8.8) or a network range (8.8.8.0/24)') parser.add_argument('-o', '--output', required=False,nargs='?',help='output filename as csv') parser.add_argument('-n', '--nameserver', required=False,nargs='?',help='alternative DNS server to query') parser.add_argument('-s', '--shodan_key', required=False,nargs='?',help='shodan key for automated port/service information') parser.add_argument('-v','--verbose', action='store_true',help='verbose errors') parser.add_argument('-d','--dns_only', action='store_true',help='direct and reverse DNS lookups only') args = parser.parse_args() targets = sorted(set(args.targets)) scan = InstaRecon( nameserver=args.nameserver, shodan_key=args.shodan_key, feedback=True, verbose=args.verbose, dns_only=args.dns_only, ) try: print scan.entry_banner scan.populate(targets) scan.scan_targets() scan.write_output_csv(args.output) print scan.exit_banner except KeyboardInterrupt: sys.exit('# Scan interrupted') except (dns.resolver.NoNameservers): sys.exit('# Something went wrong. Sure you got internet connection?')
Example Output :
$ ./instarecon.py -s <shodan_key> -o ~/Desktop/github.com.csv github.com # InstaRecon v0.1 - by Luis Teixeira (teix.co) # Scanning 1/1 hosts # Shodan key provided - <shodan_key> # ____________________ Scanning github.com ____________________ # # DNS lookups [*] Domain: github.com [*] IPs & reverse DNS: 192.30.252.130 - github.com [*] NS records: ns4.p16.dynect.net 204.13.251.16 - ns4.p16.dynect.net ns3.p16.dynect.net 208.78.71.16 - ns3.p16.dynect.net ns2.p16.dynect.net 204.13.250.16 - ns2.p16.dynect.net ns1.p16.dynect.net 208.78.70.16 - ns1.p16.dynect.net [*] MX records: ALT2.ASPMX.L.GOOGLE.com 173.194.64.27 - oa-in-f27.1e100.net ASPMX.L.GOOGLE.com 74.125.203.26 ALT3.ASPMX.L.GOOGLE.com 64.233.177.26 ALT4.ASPMX.L.GOOGLE.com 173.194.219.27 ALT1.ASPMX.L.GOOGLE.com 74.125.25.26 - pa-in-f26.1e100.net # Whois lookups [*] Whois domain: Domain Name: github.com Registry Domain ID: 1264983250_DOMAIN_COM-VRSN Registrar WHOIS Server: whois.markmonitor.com Registrar URL: http://www.markmonitor.com Updated Date: 2015-01-08T04:00:18-0800 Creation Date: 2007-10-09T11:20:50-0700 Registrar Registration Expiration Date: 2020-10-09T11:20:50-0700 Registrar: MarkMonitor, Inc. Registrar IANA ID: 292 Registrar Abuse Contact Email: abusecomplaints@markmonitor.com Registrar Abuse Contact Phone: +1.2083895740 Domain Status: clientUpdateProhibited (https://www.icann.org/epp#clientUpdateProhibited) Domain Status: clientTransferProhibited (https://www.icann.org/epp#clientTransferProhibited) Domain Status: clientDeleteProhibited (https://www.icann.org/epp#clientDeleteProhibited) Registry Registrant ID: Registrant Name: GitHub Hostmaster Registrant Organization: GitHub, Inc. Registrant Street: 88 Colin P Kelly Jr St, Registrant City: San Francisco Registrant State/Province: CA Registrant Postal Code: 94107 Registrant Country: US Registrant Phone: +1.4157354488 Registrant Phone Ext: Registrant Fax: Registrant Fax Ext: Registrant Email: hostmaster@github.com Registry Admin ID: Admin Name: GitHub Hostmaster Admin Organization: GitHub, Inc. Admin Street: 88 Colin P Kelly Jr St, Admin City: San Francisco Admin State/Province: CA Admin Postal Code: 94107 Admin Country: US Admin Phone: +1.4157354488 Admin Phone Ext: Admin Fax: Admin Fax Ext: Admin Email: hostmaster@github.com Registry Tech ID: Tech Name: GitHub Hostmaster Tech Organization: GitHub, Inc. Tech Street: 88 Colin P Kelly Jr St, Tech City: San Francisco Tech State/Province: CA Tech Postal Code: 94107 Tech Country: US Tech Phone: +1.4157354488 Tech Phone Ext: Tech Fax: Tech Fax Ext: Tech Email: hostmaster@github.com Name Server: ns1.p16.dynect.net Name Server: ns2.p16.dynect.net Name Server: ns4.p16.dynect.net Name Server: ns3.p16.dynect.net DNSSEC: unsigned URL of the ICANN WHOIS Data Problem Reporting System: http://wdprs.internic.net/ >>> Last update of WHOIS database: 2015-05-04T06:48:47-0700 [*] Whois IP: asn: 36459 asn_cidr: 192.30.252.0/24 asn_country_code: US asn_date: 2012-11-15 asn_registry: arin net 0: cidr: 192.30.252.0/22 range: 192.30.252.0 - 192.30.255.255 name: GITHUB-NET4-1 description: GitHub, Inc. handle: NET-192-30-252-0-1 address: 88 Colin P Kelly Jr Street city: San Francisco state: CA postal_code: 94107 country: US abuse_emails: abuse@github.com tech_emails: hostmaster@github.com created: 2012-11-15 00:00:00 updated: 2013-01-05 00:00:00 # Querying Shodan for open ports [*] Shodan: IP: 192.30.252.130 Organization: GitHub ISP: GitHub Port: 22 Banner: SSH-2.0-libssh-0.6.0 Key type: ssh-rsa Key: AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PH kccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETY P81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoW f9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lG HSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== Fingerprint: 16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48 Port: 80 Banner: HTTP/1.1 301 Moved Permanently Content-length: 0 Location: https://192.30.252.130/ Connection: close # Querying Google for subdomains and Linkedin pages, this might take a while [*] Possible LinkedIn page: https://au.linkedin.com/company/github [*] Subdomains: blueimp.github.com 199.27.75.133 bounty.github.com 199.27.75.133 designmodo.github.com 199.27.75.133 developer.github.com 199.27.75.133 digitaloxford.github.com 199.27.75.133 documentcloud.github.com 199.27.75.133 education.github.com 50.19.229.116 - ec2-50-19-229-116.compute-1.amazonaws.com 50.17.253.231 - ec2-50-17-253-231.compute-1.amazonaws.com 54.221.249.148 - ec2-54-221-249-148.compute-1.amazonaws.com enterprise.github.com 54.243.192.65 - ec2-54-243-192-65.compute-1.amazonaws.com 54.243.49.169 - ec2-54-243-49-169.compute-1.amazonaws.com erkie.github.com 199.27.75.133 eternicode.github.com 199.27.75.133 facebook.github.com 199.27.75.133 fortawesome.github.com 199.27.75.133 gist.github.com 192.30.252.141 - gist.github.com guides.github.com 199.27.75.133 h5bp.github.com 199.27.75.133 harvesthq.github.com 199.27.75.133 help.github.com 199.27.75.133 hexchat.github.com 199.27.75.133 hubot.github.com 199.27.75.133 ipython.github.com 199.27.75.133 janpaepke.github.com 199.27.75.133 jgilfelt.github.com 199.27.75.133 jobs.github.com 54.163.15.207 - ec2-54-163-15-207.compute-1.amazonaws.com kangax.github.com 199.27.75.133 karlseguin.github.com 199.27.75.133 kouphax.github.com 199.27.75.133 learnboost.github.com 199.27.75.133 liferay.github.com 199.27.75.133 lloyd.github.com 199.27.75.133 mac.github.com 199.27.75.133 mapbox.github.com 199.27.75.133 matplotlib.github.com 199.27.75.133 mbostock.github.com 199.27.75.133 mdo.github.com 199.27.75.133 mindmup.github.com 199.27.75.133 mrdoob.github.com 199.27.75.133 msysgit.github.com 199.27.75.133 nativescript.github.com 199.27.75.133 necolas.github.com 199.27.75.133 nodeca.github.com 199.27.75.133 onedrive.github.com 199.27.75.133 pages.github.com 199.27.75.133 panrafal.github.com 199.27.75.133 parquet.github.com 199.27.75.133 pnts.github.com 199.27.75.133 raw.github.com 199.27.75.133 rg3.github.com 199.27.75.133 rosedu.github.com 199.27.75.133 schacon.github.com 199.27.75.133 scottjehl.github.com 199.27.75.133 shop.github.com 192.30.252.129 - github.com shopify.github.com 199.27.75.133 status.github.com 184.73.218.119 - ec2-184-73-218-119.compute-1.amazonaws.com 107.20.225.214 - ec2-107-20-225-214.compute-1.amazonaws.com thoughtbot.github.com 199.27.75.133 tomchristie.github.com 199.27.75.133 training.github.com 199.27.75.133 try.github.com 199.27.75.133 twbs.github.com 199.27.75.133 twitter.github.com 199.27.75.133 visualstudio.github.com 54.192.134.13 - server-54-192-134-13.syd1.r.cloudfront.net 54.230.135.112 - server-54-230-135-112.syd1.r.cloudfront.net 54.192.134.21 - server-54-192-134-21.syd1.r.cloudfront.net 54.230.134.194 - server-54-230-134-194.syd1.r.cloudfront.net 54.192.133.169 - server-54-192-133-169.syd1.r.cloudfront.net 54.192.133.193 - server-54-192-133-193.syd1.r.cloudfront.net 54.230.134.145 - server-54-230-134-145.syd1.r.cloudfront.net 54.240.176.208 - server-54-240-176-208.syd1.r.cloudfront.net wagerfield.github.com 199.27.75.133 webcomponents.github.com 199.27.75.133 webpack.github.com 199.27.75.133 weheart.github.com 199.27.75.133 # Reverse DNS lookup on range 192.30.252.0/22 192.30.252.80 - ns1.github.com 192.30.252.81 - ns2.github.com 192.30.252.86 - live.github.com 192.30.252.87 - live.github.com 192.30.252.88 - live.github.com 192.30.252.97 - ops-lb-ip1.iad.github.com 192.30.252.98 - ops-lb-ip2.iad.github.com 192.30.252.128 - github.com 192.30.252.129 - github.com 192.30.252.130 - github.com 192.30.252.131 - github.com 192.30.252.132 - assets.github.com 192.30.252.133 - assets.github.com 192.30.252.134 - assets.github.com 192.30.252.135 - assets.github.com 192.30.252.136 - api.github.com 192.30.252.137 - api.github.com 192.30.252.138 - api.github.com 192.30.252.139 - api.github.com 192.30.252.140 - gist.github.com 192.30.252.141 - gist.github.com 192.30.252.142 - gist.github.com 192.30.252.143 - gist.github.com 192.30.252.144 - codeload.github.com 192.30.252.145 - codeload.github.com 192.30.252.146 - codeload.github.com 192.30.252.147 - codeload.github.com 192.30.252.148 - ssh.github.com 192.30.252.149 - ssh.github.com 192.30.252.150 - ssh.github.com 192.30.252.151 - ssh.github.com 192.30.252.152 - pages.github.com 192.30.252.153 - pages.github.com 192.30.252.154 - pages.github.com 192.30.252.155 - pages.github.com 192.30.252.156 - githubusercontent.github.com 192.30.252.157 - githubusercontent.github.com 192.30.252.158 - githubusercontent.github.com 192.30.252.159 - githubusercontent.github.com 192.30.252.192 - github-smtp2-ext1.iad.github.net 192.30.252.193 - github-smtp2-ext2.iad.github.net 192.30.252.194 - github-smtp2-ext3.iad.github.net 192.30.252.195 - github-smtp2-ext4.iad.github.net 192.30.252.196 - github-smtp2-ext5.iad.github.net 192.30.252.197 - github-smtp2-ext6.iad.github.net 192.30.252.198 - github-smtp2-ext7.iad.github.net 192.30.252.199 - github-smtp2-ext8.iad.github.net 192.30.253.1 - ops-puppetmaster1-cp1-prd.iad.github.com 192.30.253.2 - janky-nix101-cp1-prd.iad.github.com 192.30.253.3 - janky-nix102-cp1-prd.iad.github.com 192.30.253.4 - janky-nix103-cp1-prd.iad.github.com 192.30.253.5 - janky-nix104-cp1-prd.iad.github.com 192.30.253.6 - janky-nix105-cp1-prd.iad.github.com 192.30.253.7 - janky-nix106-cp1-prd.iad.github.com 192.30.253.8 - janky-nix107-cp1-prd.iad.github.com 192.30.253.9 - janky-nix108-cp1-prd.iad.github.com 192.30.253.10 - gw.internaltools-esx1-cp1-prd.iad.github.com 192.30.253.11 - janky-chromium101-cp1-prd.iad.github.com 192.30.253.12 - gw.internaltools-esx2-cp1-prd.iad.github.com 192.30.253.13 - github-mon2ext-cp1-prd.iad.github.net 192.30.253.16 - github-smtp2a-ext-cp1-prd.iad.github.net 192.30.253.17 - github-smtp2b-ext-cp1-prd.iad.github.net 192.30.253.23 - ops-bastion1-cp1-prd.iad.github.com 192.30.253.30 - github-slowsmtp1-ext-cp1-prd.iad.github.net 192.30.254.1 - github-lb3a-cp1-prd.iad.github.com 192.30.254.2 - github-lb3b-cp1-prd.iad.github.com 192.30.254.3 - github-lb3c-cp1-prd.iad.github.com 192.30.254.4 - github-lb3d-cp1-prd.iad.github.com # Saving output csv file # Done
Download : Master.zip | Clone Url
Source : https://github.com/vergl4s