def load_ports(self): if not os.path.exists('data/port-popularity'): error('Could not find {bgreen}data/port-popularity{rst}.') return False self.port_popularity = {} with open('data/port-popularity', 'r') as f: data = yaml.load(f, Loader=yaml.BaseLoader) self.port_popularity = {'TCP': data[0]['TCP'], 'UDP': data[1]['UDP']}
def serve(self): loop = asyncio.get_event_loop() tcp_success = 0 for port in self.tcp_ports: if self.verbose >= 2: debug('Starting listener for TCP port {byellow}{port}{rst}...') try: tcp_socket = socket.socket(family=socket.AF_INET6, type=socket.SOCK_STREAM) tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) tcp_socket.bind(('::', int(port))) loop_server = loop.run_until_complete( loop.create_server( lambda: HoneypotServerTCP(self), sock=tcp_socket)) loop.create_task(loop_server.serve_forever()) tcp_success += 1 except: error('Failed to bind to TCP port {port}.') info('Started listening on {byellow}{tcp_success}{rst} TCP ports.') udp_success = 0 for port in self.udp_ports: if self.verbose >= 3: debug('Starting listener for UDP port {byellow}{port}{rst}...') try: udp_socket = socket.socket(family=socket.AF_INET6, type=socket.SOCK_DGRAM) udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) udp_socket.bind(('::', int(port))) transport, protocol = loop.run_until_complete( loop.create_datagram_endpoint( lambda: HoneypotServerUDP(self), sock=udp_socket)) udp_success += 1 except: error('Failed to bind to UDP port {port}.') info('Started listening on {byellow}{udp_success}{rst} UDP ports.') try: loop.run_forever() except: pass
def load_probes(self): if not os.path.exists('data/service-probes'): error('Could not find {bgreen}data/service-probes{rst}.') return False self.probes = {'UDP': {}, 'TCP': {}} self.payloads = {'UDP': {}, 'TCP': {}} with open('data/service-probes', 'r') as f: lines = f.readlines() current = None for line in lines: line = line.strip() lowline = line.lower() if not line: continue if lowline.startswith('probe '): probe_type, proto, name, payload = line.split(' ', 3) payload = payload[2:-1].encode('utf-8').decode('unicode_escape') self.probes[proto][name] = payload current = (proto, name) elif lowline.startswith('match ') or lowline.startswith('softmatch '): if not current: continue action, name, regex = line.split(' ', 2) # check what separator is being used for the regex # and extract the regex itself rgxsep = regex[1] rgxend = regex[2:].find(rgxsep) + 2 regex = regex[2:rgxend] if current[1] not in self.payloads[current[0]]: self.payloads[current[0]][current[1]] = [] self.payloads[current[0]][current[1]].append(regex) if self.verbose >= 1: tcp_probes = len(self.probes['TCP']) udp_probes = len(self.probes['UDP']) debug('Loaded {bgreen}{tcp_probes}{rst} TCP and {bgreen}{udp_probes}{rst} UDP probes.') return len(self.probes['UDP']) > 0 and len(self.probes['TCP']) > 0
def _scan_host(self, scanner_group, address, results): for idx, scanner in enumerate(scanner_group): name = scanner.name() cache = scanner.code() result = None if idx > 0: info( 'Re-trying {bblue}{name}{rst}/{byellow}{address}{rst} with next implementation...' ) if self.has_cached_result(address, cache): if self.verbose >= 1: debug( 'Returning {bblue}{name}{rst}/{byellow}{address}{rst} from recent cache.' ) result = self.read_result(address, cache) if result is None and not self.no_query: if self.verbose >= 1: debug( 'Getting fresh {bblue}{name}{rst}/{byellow}{address}{rst} data...' ) result = scanner.get(address) if result is not None: self.write_result(address, cache, result) if result is None: error( 'Failed to get passive scan data for {byellow}{address}{rst}.' ) continue parsed = scanner.enum(result) if self.verbose >= 1: for svc in parsed: debug( 'Discovered service {bgreen}{svc[service]}{rst} on port {bgreen}{svc[port]}{rst}/{bgreen}{svc[transport]}{rst} running {bgreen}{svc[product]}{rst}/{bgreen}{svc[version]}{rst}.' ) results[name] = parsed break
def get_scanners(self): base = PassiveBase() order = (base.config('order') or '').split(':') if len(order) == 1 and not order[0]: error( 'Scanner order is not specified in {byellow}precon.conf{rst}.') return None scanners = [] for group in order: group = group.split(',') scanner_group = [] for name in group: if name not in globals(): error( 'Specified class {byellow}{name}{rst} does not exist.') continue clss = globals()[name] if not issubclass(clss, PassiveBase): error( 'Specified class {byellow}{name}{rst} is not a supported scanner.' ) continue inst = clss() scanner_group.append(inst) scanners.append(scanner_group) return scanners
def get(self, address): if not self.enabled(): return None req = requests.get('https://www.shodan.io/host/' + address + '/raw', headers=self.headers( 'https://www.shodan.io/host/' + address, 'www.shodan.io')) if req.status_code != 200: error( 'Failed to get {bblue}Shodan{rst}/{byellow}{address}{rst}: status code is {bred}{req.status_code}{rst}.' ) return None match = re.search(r'let data = ({.+});', req.text) if not match or not match.group(1): error( 'Failed to get {bblue}Shodan{rst}/{byellow}{address}{rst}: could not extract data.' ) return None # use YAML to parse the JSON, as Shodan sometimes returns invalid escapes, which the YAML parser is more lax with data = None try: data = yaml.load(match.group(1), Loader=yaml.FullLoader) except: error( 'Failed to get {bblue}Shodan{rst}/{byellow}{address}{rst}: failed to parse data.' ) return None return data
def run_cmd(self, cmd, tag='?', redirect=None): if redirect is None: redirect = self.verbose >= 2 info(('Skipping' if self.dryrun else 'Running') + ' task {bgreen}{tag}{rst}' + (' with {bblue}{cmd}{rst}' if self.verbose >= 1 else '...')) if self.dryrun: return True proc = subprocess.Popen( cmd, shell=True, stdout=subprocess.PIPE if redirect else subprocess.DEVNULL, stderr=subprocess.PIPE if redirect else subprocess.DEVNULL) if redirect: thdout = threading.Event() thderr = threading.Event() threading.Thread(target=self.dump_pipe, args=(proc.stdout, thdout, tag)).start() threading.Thread(target=self.dump_pipe, args=(proc.stderr, thderr, tag, Fore.RED)).start() ret = proc.wait() if redirect: thdout.set() thderr.set() if ret != 0: error('Task {bred}{tag}{rst} returned non-zero exit code: {ret}') else: info('Task {bgreen}{tag}{rst} finished successfully.') return ret == 0
def get(self, address): if not self.enabled(): return None req = requests.get('https://leakix.net/host/' + address, headers=self.headers(self.config('key'))) if req.status_code != 200: error( 'Failed to get {bblue}LeakIX{rst}/{byellow}{address}{rst}: status code is {bred}{req.status_code}{rst}.' ) return None data = None try: data = yaml.load(req.text, Loader=yaml.FullLoader) except: error( 'Failed to get {bblue}LeakIX{rst}/{byellow}{address}{rst}: failed to parse data.' ) return None return data
def get(self, address): if not self.enabled(): return None req = requests.get('https://api.zoomeye.org/host/search', headers=self.headers(self.config('key')), params=(('query', address), ('sub_type', 'all'))) if req.status_code != 200: error( 'Failed to get {bblue}ZoomEye{rst}/{byellow}{address}{rst}: status code is {bred}{req.status_code}{rst}.' ) return None data = None try: data = yaml.load(req.text, Loader=yaml.FullLoader) except: error( 'Failed to get {bblue}ZoomEye{rst}/{byellow}{address}{rst}: failed to parse data.' ) return None return data
def get(self, address): if not self.enabled(): return None req = requests.get('https://search.censys.io/api/v2/hosts/' + address, headers=self.headers(), auth=(self.config('id'), self.config('secret'))) if req.status_code != 200: error( 'Failed to get {bblue}Censys{rst}/{byellow}{address}{rst}: status code is {bred}{req.status_code}{rst}.' ) return None data = None try: data = yaml.load(req.text, Loader=yaml.FullLoader) except: error( 'Failed to get {bblue}Censys{rst}/{byellow}{address}{rst}: failed to parse data.' ) return None return data['result'] if 'result' in data else None
def get(self, address): if not self.enabled(): return None req = requests.get( 'https://search.censys.io/hosts/' + address + '/data/json', headers=self.headers('https://search.censys.io/hosts/' + address, 'search.censys.io')) if req.status_code != 200: error( 'Failed to get {bblue}Censys{rst}/{byellow}{address}{rst}: status code is {bred}{req.status_code}{rst}.' ) return None match = re.search( r'<pre><code class="language-json">({.+})</code></pre>', req.text, re.DOTALL) if not match or not match.group(1): error( 'Failed to get {bblue}Censys{rst}/{byellow}{address}{rst}: could not extract data.' ) return None json = match.group(1) json = re.sub(r'<a (?:href|class)=".*?</a>', '-', json) json = html.unescape(json) data = None try: data = yaml.load(json, Loader=yaml.FullLoader) except: error( 'Failed to get {bblue}Censys{rst}/{byellow}{address}{rst}: failed to parse data.' ) return None return data
def scan_service(self, address, port, service): if port < 0: is_udp = True port *= -1 else: is_udp = False info( 'Scanning service {bgreen}{service}{rst} on port {bgreen}{port}{rst}/{bgreen}{proto}{rst}...', proto='udp' if is_udp else 'tcp') basedir = os.path.join(self.outdir, address + self.srvname) os.makedirs(basedir, exist_ok=True) if self.bruteforce: error('self.bruteforce-only mode is currently not available.') return if 'http' in service: self.enum_http(address, port, service, basedir) elif 'smtp' in service: self.enum_smtp(address, port, service, basedir) elif 'pop3' in service: self.enum_pop3(address, port, service, basedir) elif 'imap' in service: self.enum_imap(address, port, service, basedir) elif 'ftp' in service: self.enum_ftp(address, port, service, basedir) elif 'microsoft-ds' in service or 'netbios' in service: self.enum_smb(address, port, service, basedir) elif 'ms-sql' in service or 'msSql' in service: self.enum_mssql(address, port, service, basedir) elif 'mysql' in service: self.enum_mysql(address, port, service, basedir) elif 'oracle' in service: self.enum_oracle(address, port, service, basedir) elif 'nfs' in service or 'rpcbind' in service: self.enum_nfs(address, port, service, basedir) elif 'snmp' in service: self.enum_snmp(address, port, service, basedir) elif 'domain' in service or 'dns' in service: self.enum_dns(address, port, service, basedir) elif 'rdp' in service or 'ms-wbt-server' in service or 'ms-term-serv' in service: self.enum_rdp(address, port, service, basedir) elif 'vnc' in service: self.enum_vnc(address, port, service, basedir) elif not is_udp: warn( 'Service {byellow}{service}{rst} will be scanned generically.') self.enum_generic_tcp(address, port, service, basedir) else: if port <= 1024: warn( 'Service {byellow}{service}{rst} will be scanned generically.' ) self.enum_generic_udp(address, port, service, basedir) else: warn( 'Service {byellow}{service}{rst} will not be scanned generically.' ) with open(os.path.join(basedir, '0_untouched.txt'), 'a') as file: file.writelines( str(port) + '\t' + ('udp' if is_udp else 'tcp') + '\t' + service + '\n')
def get(self, address): if not self.enabled(): return None req = requests.get('https://leakix.net/host/' + address, headers=self.headers( 'https://leakix.net/search?scope=service&q=' + address, 'leakix.net')) if req.status_code != 200: error( 'Failed to get {bblue}LeakIX{rst}/{byellow}{address}{rst}: status code is {bred}{req.status_code}{rst}.' ) return None tree = etree.HTML(req.text) svcs = tree.xpath('//ul[@id="service-panel"]/li') # enumerate services tab with ports + banners ports = {} for svc in svcs: port = svc.xpath('.//a[starts-with(@href, "/host")]/text()') if len(port) > 0: port = port[0].split(':')[-1] else: continue banner = svc.xpath('.//pre') if len(banner) > 0: banner = banner[0].text else: banner = None if port not in ports or not ports[port]: ports[port] = banner # enumerate software list data = [] softs = tree.xpath( '//div[h5[contains(text(), "Software information")]]//div[contains(@class, "list-group-item")]' ) for soft in softs: prod = soft.xpath('./p[@class="h5"]/small') version = None if len(prod) > 0: version = prod[0].text prod = prod[0].xpath('./preceding-sibling::text()')[-1].strip() else: prod = None svcs = soft.xpath('.//span[contains(@class, "badge")]/text()') for svc in svcs: svc = svc.split('/') data.append({ 'port': svc[1], 'transport': svc[0], 'product': prod, 'version': version, 'banner': ports[svc[1]] if svc[1] in ports else None }) # check if anything is missing if len(data) == 0 and len(ports) == 0: error( 'Failed to get {bblue}LeakIX{rst}/{byellow}{address}{rst}: no services found.' ) return None for svc in data: if svc['port'] in ports: del ports[svc['port']] for port in ports: data.append({ 'port': port, 'transport': 'tcp', 'product': None, 'version': None, 'banner': ports[port] }) return data
def get(self, address): if not self.enabled(): return None req = requests.get( 'https://www.zoomeye.org/search', headers=self.headers( 'https://www.zoomeye.org/searchResult?q=ip%3A%22' + address + '%22', 'www.zoomeye.org'), params=( ('q', 'ip%3A%22' + address + '%22'), ('page', '1'), ('pageSize', '20'), ('t', 'v4+v6'), )) if req.status_code != 200: error( 'Failed to get {bblue}ZoomEye{rst}/{byellow}{address}{rst}: HTTP status code is {bred}{req.status_code}{rst}.' ) return None try: search = yaml.load(req.text, Loader=yaml.FullLoader) except: error( 'Failed to get {bblue}ZoomEye{rst}/{byellow}{address}{rst}: failed to parse data.' ) return None print(search) if 'status' in search and search['status'] != 200: error( 'Failed to get {bblue}ZoomEye{rst}/{byellow}{address}{rst}: API status code is {bred}{search[status]}{rst}.' ) return None if 'matches' not in search or len(search['matches']) == 0: error( 'Failed to get {bblue}ZoomEye{rst}/{byellow}{address}{rst}: no results.' ) return None host_token = None web_token = None for match in search['matches']: if address not in match['ip']: continue if host_token is None and match['type'] == 'host': host_token = match['token'] elif web_token is None and match['type'] == 'web': web_token = match['token'] if web_token is None and host_token is None: error( 'Failed to get {bblue}ZoomEye{rst}/{byellow}{address}{rst}: failed to find tokens in results.' ) return None token = host_token if host_token is not None else web_token type = 'host' if host_token is not None else 'web' req = requests.get( 'https://www.zoomeye.org/' + type + '/details/' + token, headers=self.headers( 'https://www.zoomeye.org/searchDetail?type=' + type + '&title=' + token, 'www.zoomeye.org'), params=(('from', 'detail'), )) if req.status_code != 200: error( 'Failed to get {bblue}ZoomEye{rst}/{byellow}{address}{rst}: status code is {bred}{req.status_code}{rst}.' ) return None data = None try: data = yaml.load(req.text, Loader=yaml.FullLoader) except: error( 'Failed to get {bblue}ZoomEye{rst}/{byellow}{address}{rst}: failed to parse data.' ) return None return data
def error_received(self, exc): lport = self.transport.get_extra_info('sockname')[1] error('Error on {byellow}udp:{lport}{rst}: {bred}{exc}{rst}')
verbose = args.verbose if not os.path.isfile('vulns.db'): fail( 'Failed to find {bgreen}vulns.db{rst}. Use {bblue}-u{rst} to download the dependencies and build the database.' ) conn = sqlite3.connect('vulns.db') c = conn.cursor() if args.query.lower().startswith('cpe:/'): info('Finding vulnerabilities for {bgreen}{query}{rst}...', query=args.query.lower()) get_vulns_cli(args.query.lower()) elif os.path.isfile(args.query): info('Processing nmap report {bgreen}{args.query}{rst}...') process_nmap(args.query) else: info('Performing fuzzy matching for {bgreen}{args.query}{rst}...') cpe = fuzzy_find_cpe(args.query) if cpe is None: error('Failed to resolve query to a CPE name.') else: info('Fuzzy-matched query to name {byellow}cpe:/{cpe}{rst}.') get_vulns_cli(cpe) conn.close()