def show(self): """Display selected credentials""" results = self.get_results() if not results: logger.warning('No credential to display') else: data = list() columns = [ 'IP', 'Hostname', 'Service', 'Port', 'Proto', 'Type', 'Username', 'Password', 'URL', 'Comment', ] for r in results: data.append([ r.service.host.ip, r.service.host.hostname \ if r.service.host.hostname != str(r.service.host.ip) else '', r.service.name, r.service.port, {Protocol.TCP: 'tcp', Protocol.UDP: 'udp'}.get(r.service.protocol), r.type or '', '<empty>' if r.username == '' else r.username, {'': '<empty>', None: '<???>'}.get(r.password, r.password), StringUtils.wrap(r.service.url, 50), StringUtils.wrap(r.comment, 50), ]) Output.table(columns, data, hrules=False)
def show(self): """Show selected products""" results = self.get_results() if not results: logger.warning('No product to display') else: data = list() columns = [ 'IP', 'Hostname', 'Service', 'Port', 'Proto', 'Type', 'Name', 'Version', ] for r in results: data.append([ r.service.host.ip, r.service.host.hostname \ if r.service.host.hostname != str(r.service.host.ip) else '', r.service.name, r.service.port, {Protocol.TCP: 'tcp', Protocol.UDP: 'udp'}.get(r.service.protocol), r.type, r.name, r.version, ]) Output.table(columns, data, hrules=False)
def add_cred(self, service_id, username, password, auth_type=None): cred = self.sqlsess.query(Credential).join(Service)\ .filter(Service.id == service_id)\ .filter(Credential.username == username)\ .filter(Credential.password == password)\ .filter(Credential.type == auth_type).first() if cred: logger.warning('Credential already exists in database') else: service = self.sqlsess.query(Service).filter( Service.id == service_id).first() if not service: logger.error( 'Service id {id} is invalid'.format(id=service_id)) else: cred = Credential(username=username, password=password, type=auth_type if service.name == 'http' else None) # auth type relevant only for http self.sqlsess.add(cred) service.credentials.append(cred) logger.success('Credential {username}/{password}{auth_type} added to service {service} '\ 'host={ip}{hostname} port={port}/{proto}'.format( username = '******' if username == '' else username, password = {'': '<empty>', None: '<???>'}.get(password, password), auth_type = '('+str(auth_type)+')' if auth_type else '', service = service.name, ip = service.host.ip, hostname = '('+service.host.hostname+')' if service.host.hostname else '', port = service.port, proto = {Protocol.TCP: 'tcp', Protocol.UDP: 'udp'}.get(service.protocol))) self.sqlsess.commit()
def do_nmap(self, args): """Import Nmap results""" print() file = os.path.expanduser(args.file[0]) if not FileUtils.can_read(file): logger.error('Cannot read specified file') return logger.info('Importing Nmap results from {file}'.format(file=file)) if not args.no_http_recheck: logger.info('Each service will be re-checked to detect HTTP services. Use --no-http-recheck if you want to disable it (faster import)') parser = NmapResultsParser(file, self.settings.services) results = parser.parse(not args.no_http_recheck) if results is not None: if len(results) == 0: logger.warning('No new service has been added into current mission') else: req = HostsRequester(self.sqlsess) req.select_mission(self.current_mission) for host in results: req.add_or_merge_host(host) logger.success('Nmap results imported with success into current mission') print()
def show(self, highlight=None): """ Display selected missions. :param str highlight: Name of the mission to highlight """ results = self.get_results() if not results: logger.warning('No matching mission') else: data = list() columns = [ 'Mission', 'Creation date', 'Comment', '# Hosts', '# Services', ] for mission in results: color = 'light_green' if mission.name == highlight else None data.append([ Output.colored(mission.name, color=color), Output.colored(str(mission.creation_date), color=color), Output.colored(StringUtils.wrap(mission.comment, 50), color=color), Output.colored(len(mission.hosts), color=color), Output.colored(mission.get_nb_services(), color=color), ]) Output.table(columns, data, hrules=False)
def detect_os(self): """ Detect product from command output """ for os in os_match.keys(): if self.tool_name in os_match[os].keys(): patterns = os_match[os][self.tool_name] if type(patterns) == str: patterns = [patterns] for pattern in patterns: logger.debug('Search for os pattern: {pattern}'.format( pattern=pattern)) try: m = re.search(pattern, self.cmd_output, re.IGNORECASE) except Exception as e: logger.warning('Error with matchstring [{pattern}], ' \ 'you should review it. Exception: {exc}'.format( pattern=pattern, exc=e)) break # If pattern matches, add detected OS if m: logger.debug('OS pattern matches') # Add detected OS to the context self.cu.add_os(os) return
def attack(self): """ Run the attack against all targets :param fast_mode: """ # Initialize top status/progress bar # If single target (total=None), the counter format will be used instead of the progress bar format attack_progress = manager.counter( total=len(self.targets) + 1 if len(self.targets) > 1 else None, desc='', unit='target', bar_format=STATUSBAR_FORMAT, # For multi targets counter_format=STATUSBAR_FORMAT_SINGLE) # For single target time.sleep(.5) # hack for progress bar display for i in range(1, len(self.targets) + 1): print() self.show_summary() print() # Target selection if not self.fast_mode: if len(self.targets) > 1: self.current_targetid = Output.prompt_choice_range( 'Attack target # ? [{default}] '.format( default=self.current_targetid), 1, len(self.targets), self.current_targetid) else: if Output.prompt_confirm('Start attack ?', default=True): self.current_targetid = 1 else: logger.warning('Attack canceled !') sys.exit(1) target = self.targets[self.current_targetid - 1] # Update status/progress bar status = 'Current target [{cur}/{total}]: host {ip} | port {port}/{proto} | service {service}'.format( cur=i, total=len(self.targets), ip=target.get_ip(), port=target.get_port(), proto=target.get_protocol(), service=target.get_service_name()) attack_progress.desc = '{status}{fill}'.format( status=status, fill=' ' * (DESC_LENGTH - len(status))) attack_progress.update() print() # Launch the attack on the selected target self.__attack_target(self.current_targetid, attack_progress) self.current_targetid += 1 attack_progress.update() time.sleep(.5) attack_progress.close() manager.stop() # Clear progress bars
def __run_install_update(self, fast_mode, update=False): """ Run install or update command. :param fast_mode: Set to true to disable prompts :param update: Mode selector, True for update | False for install (default) :return: Install/Update status :rtype: bool """ if update : cmd = self.update_command.get_cmdline(self.tool_dir) else : cmd = self.install_command.get_cmdline(self.tool_dir) mode = 'update' if update else 'install' logger.info('Description: {descr}'.format(descr=self.description)) #Output.print('{mode} command : {cmd}'.format( # mode=mode.capitalize(), cmd=cmd_short)) if fast_mode \ or Output.prompt_confirm('Confirm {mode} ?'.format(mode=mode), default=True): Output.begin_cmd(cmd) ProcessLauncher(cmd).start() Output.delimiter() logger.success('Tool {mode} has finished'.format(mode=mode)) return True else: logger.warning('Tool {mode} aborted'.format(mode=mode)) return False
def show(self): """Display selected vulnerabilities""" results = self.get_results() if not results: logger.warning('No vulnerability to display') else: data = list() columns = [ 'IP', 'Service', 'Port', 'Proto', 'Vulnerability', ] for r in results: data.append([ r.service.host.ip, r.service.name, r.service.port, { Protocol.TCP: 'tcp', Protocol.UDP: 'udp' }.get(r.service.protocol), StringUtils.wrap(r.name, 140), ]) Output.table(columns, data, hrules=False)
def show(self): """Display selected specific options""" results = self.get_results() if not results: logger.warning('No specific option to display') else: data = list() columns = [ 'IP', 'Hostname', 'Service', 'Port', 'Proto', 'Name', 'Value', ] for r in results: data.append([ r.service.host.ip, r.service.host.hostname \ if r.service.host.hostname != str(r.service.host.ip) else '', r.service.name, r.service.port, {Protocol.TCP: 'tcp', Protocol.UDP: 'udp'}.get(r.service.protocol), r.name, r.value, ]) Output.table(columns, data, hrules=False)
def show(self): """Display selected results""" results = self.get_results() Output.title2('Attacks results:') if not results: logger.warning('No results to display') else: data = list() columns = [ 'IP', 'Port', 'Proto', 'Service', 'Check id', 'Category', 'Check', '# Commands run', ] for r in results: data.append([ r.service.host.ip, r.service.port, { Protocol.TCP: 'tcp', Protocol.UDP: 'udp' }.get(r.service.protocol), r.service.name, r.id, r.category, r.check, len(r.command_outputs), ]) Output.table(columns, data, hrules=False)
def __parse_commands(self, service, section): """ Create Commands object for a given tool. Each command is defined in settings file by: - command_<command_number> - context_<command_number> (optional) :param service: Service name :param section: Section name [check_(.+)] :return: List of Command instances """ log_prefix = '[{filename}{ext} | Section "{section}"]'.format( filename=service, ext=CONF_EXT, section=section) commands = list() cmdlines = self.config_parsers[service].safe_get_multi(section, 'command', default=None) i = 0 for cmd in cmdlines: context = self.config_parsers[service].safe_get(section, 'context_'+str(i+1), default=None) context = self.__parse_context(service, section, i, context) if context is None: logger.warning('{prefix} Context is invalid, the check is ignored'.format(prefix=log_prefix)) return None commands.append(Command(cmdtype=CMD_RUN, cmdline=cmdlines[i], context=context, services_config=self.services)) i += 1 if not commands: logger.warning('{prefix} No command is specified, the check is ignored'.format(prefix=log_prefix)) return commands
def __check_pre_update(self, settings, fast_mode): """ Checks to run before updating the tool :param settings: Settings instance :param fast_mode: Boolean indicating whether prompts must be displayed or not :return: Boolean indicating status """ if not self.installed: logger.info( '{tool} is not installed yet (according to settings), skipped'. format(tool=self.name_display)) return False elif not self.update_command: logger.warning( 'No tool update command specified in config file, skipped.') return False # Create directory for the tool if necessary (should not be necessary because only update) if self.install_command and not FileUtils.is_dir(self.tool_dir): logger.warning( 'Tool directory does not exist but tool marked as installed. Trying to re-install it...' ) return self.install(settings, fast_mode) return True
def __recreate_driver(self): """ Attempt to re-create selenium FirefoxDriver. Called when an error has occured and before making a retry. :return: Boolean indicating status :rtype: bool """ self.driver.quit() self.create_driver() if not self.driver: logger.warning('Web Screenshot: An error occured when reinitializing ' \ 'WebDriver') return False else: return True # driver = create_driver() # if not driver : # screenshot = capture_host('https://github.com/', driver) # import io # from PIL import Image # size = 300,300 # image = Image.open(io.BytesIO(screenshot)) # image.thumbnail(size, Image.ANTIALIAS) # image.save('screen.thumb.png') # region = image.crop(size) # region.save('sample_screenshot_3.jpg', 'JPEG', optimize=True, quality=95)
def show_products(self, filter_service=None): """ Display supported products in a table :param list filter_service: Filter on services (default: all) """ data = list() columns = [ 'Type', 'Product Names', ] services = self.list_services() if filter_service is None else [ filter_service ] for service in services: products = self.services[service]['products'] for product_type in products: names = sorted( self.services[service]['products'][product_type]) names = StringUtils.wrap(', '.join(names), 100) data.append([product_type, names]) Output.title1('Available products for {filter}'.format( filter='all services' if filter_service is None \ else 'service ' + filter_service)) if not data: logger.warning('No product') else: Output.table(columns, data)
def show(self): """Display selected hosts""" results = self.get_results() if not results: logger.warning('No host to display') else: data = list() columns = [ 'IP', 'Hostname', 'OS', 'Type', 'Vendor', 'Comment', 'TCP', 'UDP', ] for r in results: data.append([ r.ip, StringUtils.wrap(r.hostname, 45) if r.hostname != str(r.ip) else '', StringUtils.wrap(r.os, 50), r.type, StringUtils.wrap(r.vendor, 30), StringUtils.shorten(r.comment, 40), r.get_nb_services(Protocol.TCP), r.get_nb_services(Protocol.UDP), ]) Output.table(columns, data, hrules=False)
def __check_pre_update(self, settings, fast_mode=False): """ Perform some checks before trying to update the tool (already installed ?, update command ?). :param Settings settings: Settings from config files :param bool fast_mode: Set to true to disable prompts :return: Result of checks :rtype: bool """ if not self.installed: logger.info('{tool} is not installed yet (according to settings), ' \ 'skipped'.format(tool=self.name)) return False elif not self.update_command: logger.warning('No tool update command specified in config file, skipped.') return False # Create directory for the tool if necessary # (should not be necessary because only update) if self.install_command and not FileUtils.is_dir(self.tool_dir): logger.warning('Tool directory does not exist but tool marked as ' \ 'installed. Trying to re-install it...') return self.install(settings, fast_mode) return True
def show(self): """Display selected services""" results = self.get_results() if not results: logger.warning('No service to display') else: data = list() columns = [ 'id', 'IP', #'Hostname', 'Port', 'Proto', 'Service', 'Banner', 'URL', 'Comment/Title', 'Checks', 'Creds', 'Vulns', ] for r in results: # Creds numbers nb_userpass = r.get_nb_credentials(single_username=False) nb_usernames = r.get_nb_credentials(single_username=True) nb_creds = '{}{}{}'.format( '{}'.format(Output.colored(str(nb_userpass), color='green' \ if nb_userpass > 0 else None)) if nb_userpass > 0 else '', '/' if nb_userpass > 0 and nb_usernames > 0 else '', '{} usr'.format(Output.colored(str(nb_usernames), color='yellow' \ if nb_usernames > 0 else None)) if nb_usernames > 0 else '') nb_vulns = Output.colored(str(len(r.vulns)), color='green' \ if len(r.vulns) > 0 else None) if len(r.vulns) > 0 else '' # Col "Comment/Title" (title is for HTML title for HTTP) if r.html_title: comment = r.html_title else: comment = r.comment data.append([ r.id, r.host.ip, #r.host.hostname, r.port, { Protocol.TCP: 'tcp', Protocol.UDP: 'udp' }.get(r.protocol), r.name, StringUtils.wrap(r.banner, 55), StringUtils.wrap(r.url, 50), StringUtils.shorten(comment, 40), len(r.results), nb_creds, nb_vulns, ]) Output.table(columns, data, hrules=False)
def rename(self, old, new): if old == 'default': logger.warning('Default mission cannot be renamed') else: mission = self.sqlsess.query(Mission).filter( Mission.name == old).first() mission.name = new self.sqlsess.commit() logger.success('Mission renamed: {old} -> {new}'.format())
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 add_cred(self, service_id, username, password, auth_type=None): """ Add new credential for a given service. :param int service_id: Id of service :param str username: Username :param str password: Password (None if unknown) :param str auth_type: Authentication type for HTTP service """ cred = self.sqlsess.query(Credential).join(Service)\ .filter(Service.id == service_id)\ .filter(Credential.username == username)\ .filter(Credential.password == password)\ .filter(Credential.type == auth_type).first() if cred: logger.warning('Credential already exists in database') else: service = self.sqlsess.query(Service).filter(Service.id == service_id)\ .first() if not service: logger.error( 'Service id {id} is invalid'.format(id=service_id)) else: cred = Credential( username=username, password=password, type=auth_type if service.name == 'http' else None) self.sqlsess.add(cred) service.credentials.append(cred) username = '******' if username == '' else username password = { '': '<empty>', None: '<???>' }.get(password, password) auth_typ = '(' + str(auth_type) + ')' if auth_type else '' hostname = '(' + service.host.hostname + ')' if service.host.hostname else '' protocol = { Protocol.TCP: 'tcp', Protocol.UDP: 'udp' }.get(service.protocol) logger.success('Credential {username}/{password}{auth_type} added ' \ 'to service {service} host={ip}{hostname} ' \ 'port={port}/{proto}'.format( username = username, password = password, auth_type = auth_typ, service = service.name, ip = service.host.ip, hostname = hostname, port = service.port, proto = protocol)) self.sqlsess.commit()
def order_by(self, column): mapping = { 'ip': Host.ip, 'hostname': Host.hostname, 'os': Host.os, 'comment': Host.comment, } if column.lower() not in mapping.keys(): logger.warning('Ordering by column {col} is not supported'.format( col=column.lower())) return super().order_by(mapping[column.lower()])
def print_technos(self): """ Display web technologies detected in a table. """ if len(self.technos) > 0: data = list() columns = ['Name', 'Version'] for t in self.technos: data.append([t['name'], t['version']]) Output.table(columns, data, hrules=False) else: logger.warning('No technology detected')
def __check_post_install_update(self, settings, fast_mode, update=False): """ Post-install/update checks :param settings: Settings instance :param fast_mode: Boolean indicating whether prompts must be displayed or not :return: Boolean indicating status """ mode = ('update', 'updated') if update else ('install', 'installed') status = True if not fast_mode: if not self.check_command: logger.info('No check_command defined in settings for {tool}, will assume it is ' \ 'correctly {mode}'.format(tool=self.name_display, mode=mode[1])) else: logger.info( 'Now, checking if {tool} has been {mode} correctly. Hit any key to run test...' .format(tool=self.name_display, mode=mode[1])) CLIUtils.getch() status = self.__run_check_command() # Change install status in configuration file if status: try: if settings.change_installed_status(self.target_service, self.name_clean, install_status=True): logger.success( 'Tool {tool} has been marked as successfully {mode}'. format(tool=self.name_display, mode=mode[1])) return True else: logger.error( 'Error when updating configuration file "{filename}{ext}"' .format(filename=INSTALL_STATUS_CONF_FILE, ext=CONF_EXT)) return False except SettingsException as e: logger.error('An unexpected error occured when trying to mark the tool as {mode}: ' \ '{exception}'.format(exception=e, mode=mode[1])) if not update: self.remove(settings) return False else: logger.warning('Tool {tool} has not been marked as {mode}'.format( tool=self.name_display, mode=mode[1])) if not update: self.remove(settings) else: if not fast_mode and Output.prompt_confirm( 'Do you want to try to re-install ?', default=True): return self.__reinstall(settings, fast_mode) return False
def __parse_commands(self, service, section): """ Parse commands for a given tool and create Commands object. Each command is defined in configuration file by: - command_<command_number> - context_<command_number> (optional) :param str service: Service name :param str section: Section name of the check :return: Created Command objects :rtype: list(Command) """ log_prefix = '[{filename}{ext} | Section "{section}"]'.format( filename=service, ext=CONF_EXT, section=section) commands = list() # Get command lines cmdlines = self.config_parsers[service].safe_get_multi(section, 'command', default=None) i = 0 # Loop over command lines for cmd in cmdlines: # Parse context requirements and create ContextRequirements object context = self.config_parsers[service].safe_get(section, 'context_' + str(i + 1), default=None) context_requirements = self.__parse_context_requirements( service, section, i + 1, context) if context_requirements is None: logger.warning('{prefix} Context requirements are invalid, the check ' \ 'is ignored'.format(prefix=log_prefix)) return None # Create the Command object command = Command(cmdtype=CmdType.RUN, cmdline=cmdlines[i], context_requirements=context_requirements, services_config=self.services) commands.append(command) i += 1 if not commands: logger.warning('{prefix} No command is specified, the check is ' \ 'ignored'.format(prefix=log_prefix)) return commands
def __detect_specific_options(self): """Detect specific option update from command output""" if self.service.name in options_match.keys(): if self.tool_name in options_match[self.service.name].keys(): p = options_match[self.service.name][self.tool_name] for pattern in p.keys(): logger.debug('Search for option pattern: {pattern}'.format( pattern=pattern)) try: m = re.search(pattern, self.cmd_output, re.IGNORECASE|re.MULTILINE) except Exception as e: logger.warning('Error with matchstring [{pattern}], you should '\ 'review it. Exception: {exception}'.format( pattern=pattern, exception=e)) break # If pattern matches cmd output, update specific option if m: logger.debug('Option pattern matches') if 'name' in p[pattern]: name = self.__replace_tokens_from_matchobj( p[pattern]['name'], m) if name is None: continue else: logger.smarterror('Invalid matchstring for ' \ 'service={service}, tool={tool}: Missing ' \ '"name" key'.format( service=self.service.name, tool=self.tool_name)) continue if 'value' in p[pattern]: value = self.__replace_tokens_from_matchobj( p[pattern]['value'], m) if value is None: continue else: logger.smarterror('Invalid matchstring for ' \ 'service={service}, tool={tool}: Missing ' \ '"value" key'.format( service=self.service.name, tool=self.tool_name)) continue # Add specific option to context self.cu.add_option(name, value)
def __create_toolbox(self): """ Create the toolbox :return: None """ self.toolbox = Toolbox(self, self.services.list_services(multi=True)) for section in self.config_parsers[TOOLBOX_CONF_FILE].sections(): newtool = self.__create_tool(section) if newtool is not None: if not self.toolbox.add_tool(newtool): logger.warning('[{filename}{ext} | Section "{section}"] Unable to add tool "{tool}" into the toolbox'.format( filename=TOOLBOX_CONF_FILE, ext=CONF_EXT, section=section, tool=newtool.name))
def rename(self, old, new): """ Rename selected missions. :param str old: Name of the mission to rename :param str new: New mission name """ if old == 'default': logger.warning('Default mission cannot be renamed') else: mission = self.sqlsess.query(Mission).filter(Mission.name == old).first() mission.name = new self.sqlsess.commit() logger.success('Mission renamed: {old} -> {new}'.format())
def add(self, name): mission = self.sqlsess.query(Mission).filter( Mission.name == name).first() if mission: logger.warning('A mission named "{name}" already exists'.format( name=mission.name)) return False else: self.sqlsess.add(Mission(name=name)) self.sqlsess.commit() logger.success( 'Mission "{name}" successfully added'.format(name=name)) return True
def remove_tool(self, tool_name): """ Remove one tool from the toolbox. :param str tool_name: Name of the tool to remove :return: Status of removal :rtype: bool """ tool = self.get_tool(tool_name) if not tool: logger.warning('No tool with this name in the toolbox') return False else: return tool.remove(self.settings)