def smart_check(self, grab_banner_nmap=False): """ Check if the target is reachable and update target info :return: Boolean indicating status """ # If no IP, means that DNS lookup has failed if not self.service.host.ip: return False # For HTTP: Check URL availability, grab headers, grab HTML title if self.service.url: is_reachable, status, resp_headers = WebUtils.is_url_reachable(self.service.url) self.service.up = is_reachable if resp_headers: self.service.http_headers = '\n'.join("{}: {}".format(key,val) for (key,val) in resp_headers.items()) else: self.service.http_headers = '' self.service.comment = WebUtils.grab_html_title(self.service.url) # For any other service: Simple port check elif self.service.protocol == Protocol.TCP: self.service.up = NetUtils.is_tcp_port_open(str(self.service.host.ip), self.service.port) else: self.service.up = NetUtils.is_udp_port_open(str(self.service.host.ip), self.service.port) # Banner grabbing via Nmap (for TCP only) only if there is no banner already stored in db if grab_banner_nmap and self.service.up and self.service.protocol == Protocol.TCP and not self.service.banner: self.service.banner = NetUtils.grab_banner_nmap(str(self.service.host.ip), self.service.port) return self.service.up
def initWithIP(self): """ Initialize the target with an IP:PORT @Returns boolean """ # Check port if self.port < 0 or self.port > 65535: return False # Case where ip is actually a hostname if not NetUtils.isValidIP(self.ip): self.host = self.ip # DNS lookup to get IP corresponding to host (if several, just take the first one) ips = DnsUtils.dnsLookup(self.host) if not ips: return False self.ip = ips[0] else: self.host = self.ip # TODO: Optional Check # Check if IP:PORT is reachable if not self.no_port_check: if self.protocol == 'tcp': if not NetUtils.isTcpPortOpen(self.ip, self.port): self.is_reachable = False return False else: if not NetUtils.isUdpPortOpen(self.ip, self.port): self.is_reachable = False return False self.is_reachable = True return True
def __init_with_url(self): """ Initialize with an URL (when targeting HTTP). This method updates: URL, Hostname, IP, Port :raises TargetException: Exception raised if DNS lookup fails """ self.service.url = WebUtils.add_prefix_http(self.service.url) self.service.url = WebUtils.remove_ending_slash(self.service.url) url = urlparse(self.service.url) if NetUtils.is_valid_ip(url.hostname): self.service.host.ip = url.hostname self.service.host.hostname = url.hostname # updated in smart_check else: self.service.host.ip = NetUtils.dns_lookup(url.hostname) if not self.service.host.ip: raise TargetException('Unable to resolve {}'.format( url.hostname)) self.service.host.hostname = url.hostname if not self.service.port: self.service.port = WebUtils.get_port_from_url(self.service.url) if not NetUtils.is_valid_port(self.service.port): raise TargetException('Invalid port number {}'.format( self.service.port))
def __init_with_ip(self): """ Initialize with an IP address or a hostname. This method updates: Hostname, IP :raises TargetException: Exception raised if DNS lookup fails """ if NetUtils.is_valid_ip(self.service.host.ip): self.service.host.hostname = str(self.service.host.ip) # updated in smart_check else: # host.ip actually stores a hostname at this point, a DNS lookup is needed self.service.host.hostname = self.service.host.ip self.service.host.ip = NetUtils.dns_lookup( self.service.host.hostname) if self.service.host.ip: logger.info('DNS lookup on {hostname} -> IP: {ip}'.format( hostname=self.service.host.hostname, ip=self.service.host.ip)) else: raise TargetException('Unable to resolve {}'.format( self.service.host.hostname)) # Forge URL for http services if self.service.name == 'http': if self.get_specific_option_value('https'): proto = 'https' else: proto = 'http' self.service.url = '{proto}://{ip}:{port}'.format( proto=proto, ip=self.service.host.ip, port=self.service.port)
def check_arg_ip_port(self, ip_port): ip, port = ip_port.split(':', maxsplit=1) if not NetUtils.is_valid_port(port): raise argparse.ArgumentTypeError('Invalid port number') if not NetUtils.is_valid_ip(ip): raise argparse.ArgumentTypeError('Invalid IP address') return ip, port
def __init_with_ip(self): """ """ if NetUtils.is_valid_ip(self.service.host.ip): self.service.host.hostname = NetUtils.reverse_dns_lookup(str(self.service.host.ip)) else: # host.ip actually stores a hostname at this point self.service.host.ip = NetUtils.dns_lookup(str(self.service.host.ip)) self.service.host.hostname = self.service.host.ip
def add_service(self, ip, hostname, port, protocol, service): protocol = { 'tcp': Protocol.TCP, 'udp': Protocol.UDP }.get(protocol, Protocol.TCP) matching_service = self.sqlsess.query(Service).join(Host).join(Mission)\ .filter(Mission.name == self.current_mission)\ .filter(Host.ip == ip)\ .filter(Service.port == int(port))\ .filter(Service.protocol == protocol).first() if protocol == Protocol.TCP: up = NetUtils.is_tcp_port_open(ip, port) else: up = NetUtils.is_udp_port_open(ip, port) if matching_service: logger.warning('Service already present into database') else: if up: logger.info( 'Grabbing banner from {ip}:{port} with Nmap...'.format( ip=ip, port=port)) banner = NetUtils.grab_banner_nmap(ip, port) logger.info('Banner: {}'.format(banner or 'None')) os = NetUtils.os_from_nmap_banner(banner) if os: logger.info('Detected Host OS: {}'.format(os)) else: logger.warning('Port seems to be closed !') # Add service in db (and host if not existing) service = Service(name=service, port=int(port), protocol=protocol, up=up, banner=banner) matching_host = self.sqlsess.query(Host).join(Mission)\ .filter(Mission.name == self.current_mission)\ .filter(Host.ip == ip).first() new_host = Host(ip=ip, hostname=hostname, os=os) if matching_host: matching_host.merge(new_host) self.sqlsess.commit() service.host = matching_host else: mission = self.sqlsess.query(Mission).filter( Mission.name == self.current_mission).first() new_host.mission = mission service.host = new_host self.sqlsess.add(new_host) self.sqlsess.add(service) self.sqlsess.commit() logger.success('Service added')
def __init_with_ip_port(self, ip, port, ssl): if not NetUtils.is_valid_ip(ip): raise TargetException('Invalid IP address') if not NetUtils.is_valid_port(port): raise TargetException('Invalid port number {}'.format(port)) self.ip = ip self.port = port self.url = 'http{s}://{ip}:{port}'.format(s='s' if self.ssl else '', ip=self.ip, port=self.port)
def __translate_ip(self, value): """ Translate IP address or IP range into Sqlalchemy filter. Range must be in CIDR format, e.g. 1.1.1.1/24 """ if NetUtils.is_valid_ip(value): return (Host.ip == value) elif NetUtils.is_valid_ip_range(value): return (Host.is_in_ip_range(value)) else: raise FilterException( '{value} invalid IP/range'.format(value=value))
def __translate_port(self, value): """ Translate port number or ports range into Sqlalchemy filter. Ports range in format: 8000-9000 """ if NetUtils.is_valid_port(value): return (Service.port == int(value)) elif NetUtils.is_valid_port_range(value): minport, maxport = value.split('-') return (Sevrice.port.between(int(minport), int(maxport))) else: raise FilterException( '{value} invalid port/range'.format(value=value))
def __translate_ip(self, value): """ value can be: - Single IP - IP range - format: 1.1.1.1/24 """ if NetUtils.is_valid_ip(value): return (Host.ip == value) elif NetUtils.is_valid_ip_range(value): return (Host.is_in_ip_range(value)) else: raise FilterException( '{value} invalid IP/range'.format(value=value))
def __translate_port(self, value): """ value can be: - Single port number - Port range - format 8000-8100 """ if NetUtils.is_valid_port(value): return (Service.port == int(value)) elif NetUtils.is_valid_port_range(value): minport, maxport = value.split('-') return (Service.port.between(int(minport), int(maxport))) else: raise FilterException( '{value} invalid port/range'.format(value=value))
def __run_nmap(self): """ Run Nmap against service to retrieve: - Service banner - OS info (os name, os vendor, os family) if possible - Device info (MAC, vendor, device type) if possible Updated in this method: - self.service.banner - self.service.host.os - self.service.host.os_vendor - self.service.host.os_family - self.service.host.mac - self.service.host.vendor - self.service.host.type """ # Run Nmap scan nmap_info = NetUtils.grab_nmap_info(str(self.service.host.ip), self.service.port) # Get original service name as returned by Nmap self.service.name_original = nmap_info['service_name'] # Get banner self.service.banner = NetUtils.clean_nmap_banner(nmap_info['banner']) logger.info('Banner = {banner}'.format(banner=self.service.banner)) # Get OS information if nmap_info['os']: if not self.service.host.os: logger.info('Detected OS = {os}'.format(os=nmap_info['os'])) elif self.service.host.os != nmap_info['os']: logger.info('Detected OS has changed = {os}'.format( os=nmap_info['os'])) self.service.host.os = nmap_info['os'] self.service.host.os_vendor = nmap_info['os_vendor'] self.service.host.os_family = nmap_info['os_family'] # Get device information if nmap_info['mac']: self.service.host.mac = nmap_info['mac'] if nmap_info['vendor']: self.service.host.vendor = nmap_info['vendor'] if nmap_info['type']: self.service.host.type = nmap_info['type']
def __init_with_url(self): """ """ self.service.url = WebUtils.add_prefix_http(self.service.url) url = urlparse(self.service.url) if NetUtils.is_valid_ip(url.hostname): self.service.host.ip = url.hostname self.service.host.hostname = NetUtils.reverse_dns_lookup(url.hostname) else: self.service.host.ip = NetUtils.dns_lookup(url.hostname) self.service.host.hostname = url.hostname if not self.service.port: self.service.port = WebUtils.get_port_from_url(self.service.url)
def __init_with_url(self, url): p = urlparse(url) if NetUtils.is_valid_ip(p.hostname): self.ip = p.hostname else: self.ip = NetUtils.dns_lookup(p.hostname) if not self.ip: raise TargetException('Unable to resolve {}'.format(p.hostname)) self.port = NetUtils.get_port_from_url(url) if not NetUtils.is_valid_port(self.port): raise TargetException('Invalid port number {}'.format(self.port)) if url.lower().startswith('https://'): self.ssl = True
def __replace_tag_localip(self): """ Replace tag [LOCALIP] by the local IP address in self.formatted_cmdline. """ pattern = re.compile('\[LOCALIP\]', re.IGNORECASE) self.formatted_cmdline = pattern.sub(NetUtils.get_local_ip_address(), self.formatted_cmdline)
def __availability_check(self): """ Check if TCP/UDP port is open Updated in this method: - self.service.up """ if self.service.protocol == Protocol.TCP: # For TCP: simple port check self.service.up = NetUtils.is_tcp_port_open( str(self.service.host.ip), self.service.port) else: # For UDP: simple port check self.service.up = NetUtils.is_udp_port_open( str(self.service.host.ip), self.service.port)
def do_hosts(self, args): """Hosts in the current mission scope""" print() req = HostsRequester(self.sqlsess) req.select_mission(self.current_mission) # Logical AND is applied between all specified filtering options filter_ = Filter(FilterOperator.AND) if args.addrs: for addr in args.addrs: if NetUtils.is_valid_ip(addr) or NetUtils.is_valid_ip_range(addr): filter_.add_condition(Condition(addr, FilterData.IP)) else: filter_.add_condition(Condition(addr, FilterData.HOST)) if args.search: filter_search = Filter(FilterOperator.OR) filter_search.add_condition(Condition(args.search, FilterData.HOST)) filter_search.add_condition(Condition(args.search, FilterData.OS)) filter_search.add_condition(Condition(args.search, FilterData.COMMENT_HOST)) filter_.add_condition(filter_search) if args.order: req.order_by(args.order) try: req.add_filter(filter_) except FilterException as e: logger.error(e) return # Operations if args.comment: if not req.filter_applied: if not Output.prompt_confirm('No filter applied. Are you sure you want to edit comment for ALL hosts in current mission ?', default=False): logger.info('Canceled') return req.edit_comment(args.comment) elif args.delete: if not req.filter_applied: if not Output.prompt_confirm('No filter applied. Are you sure you want to delete ALL hosts and related services in current mission', default=False): logger.info('Canceled') return req.delete() else: req.show() print()
def run(self, target): try: self.command = Command(self.rawcmd, self.type) except CommandException as e: logger.error(e) return None # Build script to run if self.type == 'rce-blind': logger.warning('WARNING: This attack box must be reachable from the target !') logger.info('If target is vulnerable, exploit will try to ping local ' \ 'IP = {localip} from target'.format( localip=NetUtils.get_local_ip_address())) print(self.command.get_cmdline(target)) script = SCRIPT_RCE_BLIND.format( exploit_dir=self.directory, command=self.command.get_cmdline(target)) elif self.type == 'rce-standard': script = self.command.get_cmdline(target) else: logger.error('Unsupported exploit type') return None # Run subprocess try: logger.info('Exploit will be run from directory: {directory}'.format( directory=self.directory)) proc = subprocess.Popen(script, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # Agressivelly get the output while True: out = proc.stdout.read(1) # We put that inside try block to avoid utf8 decoding error try: out = out.decode(sys.stdout.encoding) sys.stdout.write(out) self.output += out except: pass # Break if process has finished if out == '' and proc.poll() != None: break except Exception as e: logger.error('Error when trying to run command: {exception}'.format( exception=e)) return None return self.output
def __reverse_dns_lookup(self): """ Attempt to perform reverse DNS lookup (i.e. IP -> Hostname) Updated in this method: - self.service.host.hostname """ hostname = NetUtils.reverse_dns_lookup(self.service.host.ip) if hostname != self.service.host.ip: logger.info('{ip} -> {hostname}'.format(ip=self.service.host.ip, hostname=hostname)) else: logger.info('No DNS name found for IP') self.service.host.hostname = hostname
def smart_check(self, reverse_dns=True, availability_check=True, grab_banner_nmap=False, web_technos_detection=True): """ Check if the target is reachable and update target information :param bool reverse_dns: Set to True to attempt performing reverse DNS lookup when no hostname is specified (only IP) :param bool availability_check: Set to True to check for availability of the target, and also grab headers and HTML title for HTTP services :param bool grab_banner_nmap: Set to True to grab the Nmap banner (for TCP) :param bool web_technos_detection: Set to True to run WebTechnoDetector if target service is HTTP :return: Result of check :rtype: bool """ # If no IP, means that DNS lookup has failed if not self.service.host.ip: return False # Perform reverse DNS lookup if hostname not defined # Note: If lookup fails, it fallbacks to IP if reverse_dns: if self.service.host.hostname == self.service.host.ip: logger.info('Reverse DNS lookup for {ip}...'.format( ip=str(self.service.host.ip))) hostname = NetUtils.reverse_dns_lookup(self.service.host.ip) if hostname != self.service.host.ip: logger.info('{ip} -> {hostname}'.format( ip=self.service.host.ip, hostname=hostname)) else: logger.info('No DNS name found for IP') self.service.host.hostname = hostname # Perform availability check if availability_check: logger.info('Check if service is reachable...') # For HTTP: Check URL availability, grab headers, grab HTML title if self.service.url: is_reachable, status, resp_headers = WebUtils.is_url_reachable( self.service.url) self.service.up = is_reachable if is_reachable: if resp_headers: self.service.http_headers = '\n'.join('{}: {}'.format(key,val) \ for (key,val) in resp_headers.items()) else: self.service.http_headers = '' if not self.service.html_title: self.service.html_title = WebUtils.grab_html_title( self.service.url) # For any other service: Simple port check elif self.service.protocol == Protocol.TCP: self.service.up = NetUtils.is_tcp_port_open( str(self.service.host.ip), self.service.port) else: self.service.up = NetUtils.is_udp_port_open( str(self.service.host.ip), self.service.port) if not self.service.up: return False else: self.service.up = True # consider it as up anyway # Banner grabbing via Nmap (for TCP only) only if there is no banner # already stored in db if grab_banner_nmap \ and self.service.up \ and self.service.protocol == Protocol.TCP \ and not self.service.banner: logger.info( 'Grab banner for [{service}] via Nmap...'.format(service=self)) self.service.banner = NetUtils.clean_nmap_banner( NetUtils.grab_banner_nmap(str(self.service.host.ip), self.service.port)) logger.info('Banner: {banner}'.format(banner=self.service.banner)) # Try to deduce OS from banner if possible if not self.service.host.os: detected_os = NetUtils.os_from_nmap_banner(self.service.banner) if detected_os: self.service.host.os = detected_os logger.info('Detected OS from banner = {os}'.format( os=detected_os)) # Web technologies detection for HTTP if self.service.name == 'http' and web_technos_detection: logger.info('Web technologies detection using Wappalyzer...') detector = WebTechnoDetector(self.service.url) technos = detector.detect() self.service.web_technos = str(technos) detector.print_technos() # Try to deduce OS from detected web technologies if not detected_os: detected_os = detector.get_os() if detected_os: self.service.host.os = detected_os logger.info( 'Detected OS from web technologies = {os}'.format( os=detected_os)) return self.service.up
def get_cmdline(self, target): cmdline = self.rawcmd # Replace tag [IP] pattern = re.compile('\[IP\]', re.IGNORECASE) cmdline = pattern.sub(target.ip, cmdline) # Replace tag [PORT] pattern = re.compile('\[PORT\]', re.IGNORECASE) cmdline = pattern.sub(target.port, cmdline) # Replace tag [URL] pattern = re.compile('\[URL\]', re.IGNORECASE) cmdline = pattern.sub(target.url, cmdline) # Replace tag [URIPATH] pattern = re.compile('\[URIPATH\]', re.IGNORECASE) try: o = urllib.parse.urlparse(url) uripath = o.path or '/' except: uripath = '/' cmdline = pattern.sub(uripath, cmdline) # Replace tag [SSL true="..."] pattern = re.compile( r'\[SSL\s+true\s*=\s*[\'"](?P<option>.*?)[\'"]\s*\]', re.IGNORECASE) m = pattern.search(cmdline) if target.ssl == True: cmdline = pattern.sub(m.group('option'), cmdline) else: cmdline = pattern.sub('', cmdline) # Replace tag [CMD] if '[cmd]' in cmdline.lower(): try: pattern = re.compile('\[CMD\]', re.IGNORECASE) if CMD[self.type]['linux'] != CMD[self.type]['windows']: cmdline_lin = pattern.sub(CMD[self.type]['linux'], cmdline) cmdline_win = pattern.sub(CMD[self.type]['windows'], cmdline) cmdline = '{0}; {1}'.format(cmdline_lin, cmdline_win) else: cmdline = pattern.sub(CMD[self.type]['linux'], cmdline) except Exception as e: raise CommandException(e) elif '[cmdlinux]' in cmdline.lower( ) or '[cmdwindows]' in cmdline.lower(): pattern = re.compile('\[CMDLINUX\]', re.IGNORECASE) cmdline = pattern.sub(CMD[self.type]['linux'], cmdline) pattern = re.compile('\[CMDWINDOWS\]', re.IGNORECASE) cmdline = pattern.sub(CMD[self.type]['windows'], cmdline) # Replace tag [LOCALIP] localip = NetUtils.get_local_ip_address() if localip == '127.0.0.1': raise CommandException('Unable to get local IP address') pattern = re.compile('\[LOCALIP\]', re.IGNORECASE) cmdline = pattern.sub(localip, cmdline) return cmdline
def run(self, target, mode, rce_command=''): """ :param Target targer: Target instance :param str mode: mode can be either "detect" or "exploit" :param str rce_command: RCE command to run when running exploit (requires mode=exploit) """ try: if mode == 'detect': self.command = Command(self.detection_rawcmd, self.type) elif mode == 'exploit': self.command = Command(self.exploit_rawcmd, self.type, self.exploit_rce_output) except CommandException as e: logger.error(e) return None # Build script to run if mode == 'exploit': if self.type == 'rce': if not self.exploit_rce_output: logger.warning('The exploit will attempt to execute command on remote system but no ' 'output will be available !') # For RCE exploit without command output in test mode (no rce_command provided): # Use script template that check for reverse connection with ICMP ping and HTTP requests if len(rce_command) == 0: logger.warning('WARNING: This attack box must be reachable from the target !') logger.info('No command supplied to run through RCE, automatic exploit test will be started...') logger.info('If target is vulnerable, exploit will try to ping (ICMP Echo request) and ' 'to send HTTP request to local IP = {localip} from target'.format( localip=NetUtils.get_local_ip_address())) cmdline = self.command.get_cmdline(target) print(cmdline) script = SCRIPT_RCE_BLIND.format( exploit_dir=self.directory, command=cmdline) else: cmdline = self.command.get_cmdline(target, rce_command) print(cmdline) script = 'cd {exploit_dir}; '.format(exploit_dir=self.directory) script += cmdline else: if len(rce_command) == 0: logger.info('No command supplied to run through RCE, automatic exploit test will be started...') logger.info('If target is vulnerable, exploit will try to run an echo command on target') cmdline = self.command.get_cmdline(target) print(cmdline) script = 'cd {exploit_dir}; '.format(exploit_dir=self.directory) script += cmdline else: cmdline = self.command.get_cmdline(target, rce_command) print(cmdline) script = 'cd {exploit_dir}; '.format(exploit_dir=self.directory) script += cmdline else: cmdline = self.command.get_cmdline(target) print(cmdline) script = 'cd {exploit_dir}; '.format(exploit_dir=self.directory) script += cmdline else: logger.warning('The script will attempt to detect if remote system is vulnerable without ' 'actually exploiting the vulnerability.') logger.warning('WARNING: False Positive is possible !') cmdline = self.command.get_cmdline(target) script = 'cd {exploit_dir}; '.format(exploit_dir=self.directory) script += cmdline # Run subprocess try: logger.info('{script} will be run from directory: {directory}'.format( script='Exploit' if mode == 'exploit' else 'Detection script', directory=self.directory)) proc = subprocess.Popen(script, shell=True, executable='/bin/bash', stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # Agressivelly get the output while True: out = proc.stdout.read(1) # We put that inside try block to avoid utf8 decoding error try: out = out.decode(sys.stdout.encoding) sys.stdout.write(out) self.output += out except: pass # Break if process has finished if out == '' and proc.poll() != None: break except Exception as e: logger.error('Error when trying to run command: {exception}'.format( exception=e)) return None return self.output
def parse(self, http_recheck=True, grab_html_title=True): """ Parse the Nmap results :param bool http_recheck: If set to True, TCP ports are re-checked for HTTP(s) :param bool grab_html_title: If set to True, grab title of HTML page (text in <title> tags) and put it as comment for HTTP service. :return: Hosts :rtype: list(Host)|None """ try: nmap_report = NmapParser.parse_fromfile(self.nmap_file) except Exception as e: logger.error('Error when parsing the Nmap file: {0}'.format(e)) return None results = list() for h in nmap_report.hosts: # Get the fingerprinted OS if available os = '' os_vendor = '' os_family = '' device_type = '' if h.os_fingerprinted is True and h.os_match_probabilities( ) is not None: os_matchs = h.os_match_probabilities() os = os_matchs[0].name if len(os_matchs) > 0 else '' if os_matchs[0].osclasses is not None \ and len(os_matchs[0].osclasses) > 0: os_vendor = os_matchs[0].osclasses[0].vendor os_family = os_matchs[0].osclasses[0].osfamily device_type = NetUtils.get_device_type( os, os_family, os_matchs[0].osclasses[0].type) # Create Host object host = Host(ip=h.ipv4, hostname=h.hostnames[0] if h.hostnames else '', os=os, os_vendor=os_vendor, os_family=os_family, mac=h.mac, vendor=h.vendor, type=device_type) logger.info('Parsing host: {ip}{hostname} ...'.format( ip=host.ip, hostname=' (' + host.hostname + ')' if host.hostname else '')) # Loop over open ports for p in h.get_open_ports(): s = h.get_service(p[0], protocol=p[1]) name = NmapResultsParser.nmap_to_joker_service_name(s.service) url = '' comment = '' html_title = '' # Get URL for http services if name == 'http': if 'https' in s.service \ or 'ssl' in s.service \ or s.tunnel in ('ssl', 'tls'): proto = 'https' else: proto = 'http' url = '{proto}://{host}:{port}'.format(proto=proto, host=host.ip, port=s.port) # Recheck for HTTP/HTTPS for services undetermined by Nmap if http_recheck \ and s.protocol == 'tcp' \ and not self.services_config.is_service_supported(name, multi=False): url = WebUtils.is_returning_http_data( host.hostname or host.ip, s.port) if url: logger.success('{url} seems to return HTTP data, marking it ' \ 'as http service'.format(url=url)) name = 'http' # Grab page title for HTTP services if grab_html_title and name == 'http': html_title = WebUtils.grab_html_title(url) # Only keep services supported by Jok3r if not self.services_config.is_service_supported(name, multi=False): logger.info('Service not supported: host {ip} | port ' \ '{port}/{proto} | service {service}'.format( ip = h.ipv4, port=s.port, proto=s.protocol, service=name)) continue else: logger.info('Parsing service: host {ip} | port {port}/{proto} ' \ '| service {service}'.format( ip = h.ipv4, port=s.port, proto=s.protocol, service=name)) # Deduce OS from banner if possible if not os: host.os = NetUtils.os_from_nmap_banner(s.banner) # Clean Nmap banner banner = NetUtils.clean_nmap_banner(s.banner) # Create Service object service = Service(name=name, port=s.port, protocol={ 'tcp': Protocol.TCP, 'udp': Protocol.UDP }.get(s.protocol), url=url, up=True, banner=banner, comment=comment, html_title=html_title) # Already add specific option https=True if possible if name == 'http' and url.startswith('https://'): service.options.append(Option(name='https', value='true')) host.services.append(service) if host.services: results.append(host) return results
def parse(self, http_recheck=True, html_title_grabbing=True, nmap_banner_grabbing=False, web_technos_detection=True): """ Parse the Nmap results :param bool http_recheck: If set to True, TCP ports are re-checked for HTTP(s) :param bool html_title_grabbing: If set to True, grab title of HTML page (text in <title> tags) and put it as comment for HTTP service :param bool nmap_banner_grabbing: If set to True, run Nmap to grab service banner for each service where it is missing (might be useful if imported Nmap results come from a scan run without -sV/-A) :param bool web_technos_detection: If set to True, try to detect web technos for HTTP service :return: Hosts :rtype: list(Host)|None """ try: nmap_report = NmapParser.parse_fromfile(self.nmap_file) except Exception as e: logger.error('Error when parsing the Nmap file: {0}'.format(e)) return None results = list() host_id = 0 for h in nmap_report.hosts: host_id += 1 # Get the fingerprinted OS if available os = '' os_vendor = '' os_family = '' device_type = '' if h.os_fingerprinted is True \ and h.os_match_probabilities() is not None \ and len(h.os_match_probabilities()) > 0: os_matchs = h.os_match_probabilities() if len(os_matchs) > 0: os = os_matchs[0].name if os_matchs[0].osclasses is not None \ and len(os_matchs[0].osclasses) > 0: os_vendor = os_matchs[0].osclasses[0].vendor os_family = os_matchs[0].osclasses[0].osfamily device_type = OSUtils.get_device_type( os, os_family, os_matchs[0].osclasses[0].type) # Create Host object host = Host(ip=h.ipv4, hostname=h.hostnames[0] if h.hostnames else h.ipv4, os=os, os_vendor=os_vendor, os_family=os_family, mac=h.mac, vendor=h.vendor, type=device_type) logger.info('[File {file} | Host {current_host}/{total_host}] ' \ 'Parsing host: {ip}{hostname} ...'.format( file=FileUtils.extract_filename(self.nmap_file), current_host=host_id, total_host=len(nmap_report.hosts), ip=host.ip, hostname=' ('+host.hostname+')' if host.hostname != host.ip else '')) # Loop over open ports port_id = 0 for p in h.get_open_ports(): port_id += 1 s = h.get_service(p[0], protocol=p[1]) name = get_service_name(s.service) url = '' comment = '' html_title = '' # Print current processed service print() logger.info('[File {file} | Host {current_host}/{total_host} | ' \ 'Service {current_svc}/{total_svc}] Parsing service: ' \ 'host {ip} | port {port}/{proto} | service {service} ...'.format( file=FileUtils.extract_filename(self.nmap_file), current_host=host_id, total_host=len(nmap_report.hosts), current_svc=port_id, total_svc=len(h.get_open_ports()), ip=h.ipv4, port=s.port, proto=s.protocol, service=name)) # Get URL for http services if name == 'http': if 'https' in s.service \ or 'ssl' in s.service \ or s.tunnel in ('ssl', 'tls'): proto = 'https' else: proto = 'http' url = '{proto}://{host}:{port}'.format( proto=proto, host=host.hostname, port=s.port) # Recheck for HTTP/HTTPS for services undetermined by Nmap if http_recheck \ and s.protocol == 'tcp' \ and not self.services_config.is_service_supported(name, multi=False): url = WebUtils.is_returning_http_data(host.ip, s.port) if url: logger.success('{url} seems to return HTTP data, marking it ' \ 'as http service'.format(url=url)) name = 'http' # Only keep services supported by Jok3r if not self.services_config.is_service_supported(name, multi=False): logger.warning('Service not supported: host {ip} | port ' \ '{port}/{proto} | service {service}'.format( ip = h.ipv4, port=s.port, proto=s.protocol, service=name)) continue # # Deduce OS from banner if possible # if not os: # host.os = OSUtils.os_from_nmap_banner(s.banner) # if host.os: # host.os_vendor = OSUtils.get_os_vendor(host.os) # host.os_family = OSUtils.get_os_family(host.os) # Clean Nmap banner banner = NetUtils.clean_nmap_banner(s.banner) # Create Service object service = Service( name=name, name_original=s.service, port=s.port, protocol={'tcp': Protocol.TCP,'udp': Protocol.UDP}.get(s.protocol), url=url, up=True, banner=banner, comment=comment, html_title=html_title) host.services.append(service) # Target smart check: # - Nmap banner grabbing if specified by user and banner is missing in # imported results; # - HTML title and HTTP response headers grabbing for HTTP service; # - Web technologies detection for HTTP service, except if disabled by # user; # - Initialize the context of the target via SmartModules, based on the # information already known (i.e. banner, web technologies...) target = Target(service, self.services_config) up = target.smart_check( reverse_dns_lookup=False, # Done by Nmap availability_check=False, # Done by Nmap nmap_banner_grabbing=nmap_banner_grabbing, # Default: False html_title_grabbing=html_title_grabbing, web_technos_detection=web_technos_detection, # Default: True smart_context_initialize=True) if not up: logger.warning('Service not reachable') if host.services: results.append(host) return results
def __replace_tag_localip(self): pattern = re.compile('\[LOCALIP\]', re.IGNORECASE) self.parsed_cmdline = pattern.sub(NetUtils.get_local_ip_address(), self.parsed_cmdline)
def add_url(self, url): matching_service = self.sqlsess.query(Service).join(Host).join(Mission)\ .filter(Mission.name == self.current_mission)\ .filter(Service.url == url).first() if matching_service: logger.warning('URL already present into database') else: # Parse URL: Get IP, hostname, port parsed = urlparse(url) if NetUtils.is_valid_ip(parsed.hostname): ip = parsed.hostname hostname = NetUtils.reverse_dns_lookup(parsed.hostname) else: ip = NetUtils.dns_lookup(parsed.hostname) if not ip: logger.error('Host cannot be resolved') return hostname = parsed.hostname port = WebUtils.get_port_from_url(url) # Check URL, grab headers, html title is_reachable, status, resp_headers = WebUtils.is_url_reachable(url) if is_reachable: comment = WebUtils.grab_html_title(url) if resp_headers: http_headers = '\n'.join("{}: {}".format(key, val) for (key, val) in resp_headers.items()) logger.info('HTTP Headers:') print(http_headers) logger.info('Title: {}'.format(comment)) logger.info( 'Grabbing banner from {ip}:{port} with Nmap...'.format( ip=ip, port=port)) banner = NetUtils.grab_banner_nmap(ip, port) logger.info('Banner: {}'.format(banner or 'None')) os = NetUtils.os_from_nmap_banner(banner) if os: logger.info('Detected Host OS: {}'.format(os)) else: comment = 'Not reachable' banner = http_headers = '' logger.warning('URL seems not to be reachable') # Add service in db (and host if not existing) service = Service(name='http', port=port, protocol=Protocol.TCP, url=url, up=is_reachable, http_headers=http_headers, banner=banner, comment=comment) matching_host = self.sqlsess.query(Host).join(Mission)\ .filter(Mission.name == self.current_mission)\ .filter(Host.ip == ip).first() new_host = Host(ip=ip, hostname=hostname, os=os) if matching_host: matching_host.merge(new_host) self.sqlsess.commit() service.host = matching_host else: mission = self.sqlsess.query(Mission).filter( Mission.name == self.current_mission).first() new_host.mission = mission service.host = new_host self.sqlsess.add(new_host) self.sqlsess.add(service) self.sqlsess.commit() logger.success('Service/URL added')
def get_cmdline(self, target, rce_command=''): """ :param Target target: Target instance :param str rce_command: Command to execute on vulnerable system through RCE vuln """ cmdline = self.rawcmd # Replace tag [IP] pattern = re.compile('\[IP\]', re.IGNORECASE) cmdline = pattern.sub(target.ip, cmdline) # Replace tag [PORT] pattern = re.compile('\[PORT\]', re.IGNORECASE) cmdline = pattern.sub(str(target.port), cmdline) # Replace tag [URL] pattern = re.compile('\[URL\]', re.IGNORECASE) cmdline = pattern.sub(str(target.url), cmdline) # Replace tag [BASEURL] pattern = re.compile('\[BASEURL\]', re.IGNORECASE) baseurl = target.url.split('//')[0] + '//' + target.url.split( '//')[1].split('/')[0] cmdline = pattern.sub(str(baseurl), cmdline) # Replace tag [URIPATH] pattern = re.compile('\[URIPATH\]', re.IGNORECASE) try: o = urllib.parse.urlparse(url) uripath = o.path or '/' except: uripath = '/' cmdline = pattern.sub(uripath, cmdline) # Replace tag [SSL true="..."] pattern = re.compile( r'\[SSL\s+true\s*=\s*[\'"](?P<option>.*?)[\'"]\s*\]', re.IGNORECASE) m = pattern.search(cmdline) if m: if target.ssl == True: cmdline = pattern.sub(m.group('option'), cmdline) else: cmdline = pattern.sub('', cmdline) # Replace tag [CMD] if '[cmd]' in cmdline.lower(): try: pattern = re.compile('\[CMD\]', re.IGNORECASE) # If command provided by user, replace tag by this command, otherwise # use the predefined commands for automatic test if len(rce_command) > 0: cmdline = pattern.sub(rce_command, cmdline) else: if self.type == 'rce' and not self.exploit_rce_output: cmdline_lin = pattern.sub(CMD['rce-blind']['linux'], cmdline) cmdline_lin2 = pattern.sub(CMD['rce-blind']['linux2'], cmdline) cmdline_lin3 = pattern.sub(CMD['rce-blind']['linux3'], cmdline) cmdline_win = pattern.sub(CMD['rce-blind']['windows'], cmdline) cmdline = '{0}; {1}; {2}; {3}'.format( cmdline_lin, cmdline_lin2, cmdline_lin3, cmdline_win) else: cmdline = pattern.sub(CMD['rce-standard']['linux'], cmdline) except Exception as e: raise CommandException(e) # Special case where Linux/Windows command line differ elif '[cmdlinux]' in cmdline.lower( ) or '[cmdwindows]' in cmdline.lower(): try: pattern_linux = re.compile('\[CMDLINUX\]', re.IGNORECASE) pattern_windows = re.compile('\[CMDWINDOWS\]', re.IGNORECASE) # If command provided by user, replace both tag by this command, otherwise # use the predefined commands for automatic test if len(rce_command) > 0: cmdline = pattern_linux.sub(rce_command, cmdline) cmdline = pattern_windows.sub(rce_command, cmdline) else: if self.type == 'rce' and not self.exploit_rce_output: # Replace [CMDLINUX] cmdline_lin = pattern_linux.sub( CMD['rce-blind']['linux'], cmdline) cmdline_lin2 = pattern_linux.sub( CMD['rce-blind']['linux2'], cmdline) cmdline_lin3 = pattern_linux.sub( CMD['rce-blind']['linux3'], cmdline) cmdline = '{0}; {1}; {2}'.format( cmdline_lin, cmdline_lin2, cmdline_lin3) # Replace [CMDWINDOWS] cmdline = pattern_windows.sub( CMD['rce-blind']['windows'], cmdline) else: # Replace [CMDLINUX] cmdline = pattern_linux.sub( CMD['rce-standard']['linux'], cmdline) # Replace [CMDWINDOWS] cmdline = pattern_windows.sub( CMD['rce-standard']['windows'], cmdline) except Exception as e: raise CommandException(e) # Replace tag [LOCALIP] localip = NetUtils.get_local_ip_address() if localip == '127.0.0.1': raise CommandException('Unable to get local IP address') pattern = re.compile('\[LOCALIP\]', re.IGNORECASE) cmdline = pattern.sub(localip, cmdline) return cmdline
def do_services(self, args): """Services in the current mission scope""" print() req = ServicesRequester(self.sqlsess) req.select_mission(self.current_mission) # Logical AND is applied between all specified filtering options filter_ = Filter(FilterOperator.AND) if args.names: for n in args.names: if not self.settings.services.is_service_supported(n, multi=False): logger.error('Service {name} is not valid/supported'.format(name=n.lower())) return filter_.add_condition(Condition(args.names, FilterData.SERVICE_EXACT)) if args.order: req.order_by(args.order) if args.hostname: # OR between submitted hostnames filter_.add_condition(Condition(args.hostname.split(','), FilterData.HOST)) if args.ip: # OR between submitted ips/ranges filter_.add_condition(Condition(args.ip.split(','), FilterData.IP)) if args.port: # OR between ports/port-ranges filter_.add_condition(Condition(args.port.split(','), FilterData.PORT)) if args.proto: filter_.add_condition(Condition(args.proto, FilterData.PROTOCOL)) if args.up: filter_.add_condition(Condition(args.up, FilterData.UP)) if args.search: filter_search = Filter(FilterOperator.OR) filter_search.add_condition(Condition(args.search, FilterData.HOST)) filter_search.add_condition(Condition(args.search, FilterData.BANNER)) filter_search.add_condition(Condition(args.search, FilterData.URL)) filter_search.add_condition(Condition(args.search, FilterData.COMMENT_SERVICE)) filter_.add_condition(filter_search) try: req.add_filter(filter_) except FilterException as e: logger.error(e) return # Operations if args.add: host, port, service = args.add if NetUtils.is_valid_ip(host): ip = host hostname = NetUtils.reverse_dns_lookup(ip) logger.info('Reverse DNS lookup on IP {ip}: {hostname}'.format(ip=ip, hostname=hostname)) else: ip = NetUtils.dns_lookup(host) if not ip: logger.error('Cannot resolve hostname') return hostname = host logger.info('DNS lookup on {hostname}: IP {ip}'.format(hostname=host, ip=ip)) if not NetUtils.is_valid_port(port): logger.error('Port is invalid, not in range [0-65535]') elif not self.settings.services.is_service_supported(service, multi=False): logger.error('Service {name} is not valid/supported'.format(name=service.lower())) else: req.add_service(ip, hostname, port, self.settings.services.get_protocol(service), service) elif args.url: args.url = WebUtils.add_prefix_http(args.url) if not WebUtils.is_valid_url(args.url): logger.error('URL is invalid') else: req.add_url(args.url) elif args.delete: if not req.filter_applied: if not Output.prompt_confirm('No filter applied. Are you sure you want to delete ALL services in current mission ?', default=False): logger.info('Canceled') return req.delete() elif args.comment: if not req.filter_applied: if not Output.prompt_confirm('No filter applied. Are you sure you want to edit comment for ALL services in current mission ?', default=False): logger.info('Canceled') return req.edit_comment(args.comment) elif args.https: if not req.filter_applied: if not Output.prompt_confirm('No filter applied. Are you sure you want to apply switch for ALL URLs in current mission ?', default=False): logger.info('Canceled') return req.switch_https() elif args.addcred: if not req.filter_applied: if not Output.prompt_confirm('No filter applied. Are you sure you want to add same creds for ALL services in current mission ?', default=False): logger.info('Canceled') return req.add_cred(args.addcred[0], args.addcred[1], None) elif args.addcred_http: if not req.are_only_http_services_selected(): logger.warning('Some non-HTTP services are selected. Use --addcred instead for non-HTTP services') return if not self.settings.services.is_valid_authentication_type(args.addcred_http[2]): logger.warning('Invalid HTTP authentication type') logger.info('List of supported authentication types: ') for auth_type in self.settings.services.get_authentication_types('http'): logger.info('- {type}'.format(type=auth_type)) return if not req.filter_applied: if not Output.prompt_confirm('No filter applied. Are you sure you want to add same creds for ALL HTTP services in current mission ?', default=False): logger.info('Canceled') return req.add_cred(args.addcred_http[0], args.addcred_http[1], args.addcred_http[2]) elif args.adduser: if not req.filter_applied: if not Output.prompt_confirm('No filter applied. Are you sure you want to add same username for ALL services in current mission ?', default=False): logger.info('Canceled') return req.add_cred(args.adduser[0], None, None) elif args.adduser_http: if not req.are_only_http_services_selected(): logger.warning('Some non-HTTP services are selected. Use --adduser instead for non-HTTP services') return if not self.settings.services.is_valid_authentication_type(args.adduser_http[1]): logger.warning('Invalid HTTP authentication type') logger.info('List of supported authentication types: ') for auth_type in self.settings.services.get_authentication_types('http'): logger.info('- {type}'.format(type=auth_type)) return if not req.filter_applied: if not Output.prompt_confirm('No filter applied. Are you sure you want to add same username for ALL HTTP services in current mission ?', default=False): logger.info('Canceled') return req.add_cred(args.adduser_http[0], None, args.adduser_http[1]) else: req.show() print()