def __run_for_multi_targets(self, args): """Run attack against multiple targets from the database""" # Get Mission from which targets must be extracted mission = self.sqlsess.query(Mission)\ .filter(Mission.name == args.mission).first() if mission: logger.info('Extracting targets from mission "{mission}" ...'.format( mission=mission.name)) else: raise AttackException('Mission {mission} does not exist into the ' \ 'database'.format(mission=args.mission)) # Initialize Services requester and add filter if provided req = ServicesRequester(self.sqlsess) req.select_mission(args.mission) if args.filters_combined: for filt in args.filter: logger.info('Applying filters on mission scope: {filter}'.format( filter=filt)) if len(args.filter) > 1: logger.info('Note: Logical OR is applied between each filter') req.add_filter(args.filters_combined) # Retrieve targeted services from database services = req.get_results() if not services: raise AttackException('There is no matching service to target into the ' \ 'database') # Add each targeted service into Attack scope for service in services: # Update credentials, options, products if specified in command-line if args.creds: for c in args.creds[service.name]: service.add_credential(c.clone()) if args.users: for u in args.users[service.name]: service.add_credential(u.clone()) if args.products: for p in args.products[service.name]: service.add_product(p.clone()) if args.options: for o in args.options[service.name]: service.add_option(o.clone()) # Initialize Target try: target = Target(service, self.settings.services) except TargetException as e: logger.error(e) continue self.attack_scope.add_target(target) # Run the attack self.attack_scope.attack()
def run(self): # Extract HTTP services from the mission in database req = ServicesRequester(self.sqlsession) req.select_mission(self.mission_name) filter_ = Filter(FilterOperator.AND) filter_.add_condition(Condition('http', FilterData.SERVICE_EXACT)) req.add_filter(filter_) services = req.get_results() if len(services) == 0: return logger.info('Taking web page screenshots for HTTP services (total: ' \ '{nb})...'.format(nb=len(services))) screenshoter = WebScreenshoter() if not screenshoter.create_driver(): logger.error('No screenshot will be added to the report') return i = 1 for s in services: if s.screenshot is not None \ and s.screenshot.status == ScreenStatus.OK \ and s.screenshot.image is not None \ and s.screenshot.thumbnail is not None: logger.info('[{i}/{nb}] Screenshot already in database for {url}'.format( i=i, nb=len(services), url=s.url)) else: logger.info('[{i}/{nb}] Taking screenshot for {url}...'.format( i=i, nb=len(services), url=s.url)) status, screen = screenshoter.take_screenshot(s.url) # Create Screenshot entry in database if necessary if s.screenshot is None: screenshot = Screenshot(status=status) self.sqlsession.add(screenshot) s.screenshot = screenshot self.sqlsession.commit() # Create thumbnail if status is OK if status == ScreenStatus.OK: thumb = ImageUtils.create_thumbnail(screen, 300, 300) if not thumb: status = ScreenStatus.ERROR s.screenshot.status = status s.screenshot.image = screen s.screenshot.thumbnail = thumb else: s.screenshot.status = status self.sqlsession.commit() i += 1
def __init__(self, settings, arguments, sqlsession, mission, filter_categories=None, filter_checks=None, attack_profile=None, fast_mode=False): """ Construct AttackScope object :param Settings settings: Settings :param ArgumentsParser arguments: Arguments from command-line :param Session sqlsession: SQLAlchemy session :param str mission: Mission name :param list filter_categories: Selection of categories of checks to run (default is None, for all categories) :param list filter_checks: Selection of checks to run (default is None, for all checks) :param AttackProfile attack_profile: Attack profile (default is None, meaning no profile) :param bool fast_mode: Set to true to disable prompts """ self.settings = settings self.arguments = arguments self.sqlsess = sqlsession self.mission_name = mission self.services_requester = ServicesRequester(self.sqlsess) self.targets = list() self.current_targetid = 1 self.filter_categories = filter_categories self.filter_checks = filter_checks self.attack_profile = attack_profile self.fast_mode = fast_mode self.services_requester.select_mission(self.mission_name)
def __run_for_single_target(self, args): """Run attack against a single target specified into args""" req = ServicesRequester(self.sqlsess) mission = None # Get Mission if target must be added into a mission scope if args.add: mission = self.sqlsess.query(Mission).filter(Mission.name == args.add).first() if not mission: raise AttackException('The specified mission does not exist in the ' \ 'database. You should create it if needed') # Create new Service/Host objects (if service already exist, # will be merged by ServicesRequester.add_target) url = args.target_ip_or_url if args.target_mode == TargetMode.URL else '' ip = args.target_ip_or_url if args.target_mode == TargetMode.IP else '' service = Service( name=args.service, port=int(args.target_port), protocol=self.settings.services.get_protocol2(args.service), url=url) host = Host(ip=ip) # Will be updated when initializing Target() host.services.append(service) # Update context (credentials, options, products) if specified in command-line if args.creds: for c in args.creds[args.service]: self.sqlsess.add(c) service.credentials.append(c) if args.users: for u in args.users[args.service]: self.sqlsess.add(u) service.credentials.append(u) if args.products: for p in args.products[args.service]: self.sqlsess.add(p) service.products.append(p) if args.options: for o in args.options[args.service]: self.sqlsess.add(o) service.options.append(o) # Initialize Target try: target = Target(service, self.settings.services) except TargetException as e: logger.error(e) sys.exit(1) # Check Target and update its information: # - Reverse DNS lookup: by default # - Port check: always # - Nmap service detection: by default # - HTML title grabbing: always # - Web technologies detection: always # - Context initialization via SmartStart: always reachable = target.smart_check( reverse_dns_lookup=(args.reverse_dns is None or args.reverse_dns == 'on'), availability_check=True, nmap_banner_grabbing=(args.nmap_banner_grab is None \ or args.nmap_banner_grab == 'on'), html_title_grabbing=True, web_technos_detection=True, smart_context_initialize=True) # Display availability status, exit if not reachable if args.target_mode == TargetMode.IP: msg = 'Target service {neg}reachable: {target}'.format( neg='not ' if not reachable else '', target=target) else: msg = 'Target URL {url} is {neg}reachable'.format( url=target.get_url(), neg='not ' if not reachable else '') if reachable: logger.success(msg) else: logger.error(msg) return # Commit the target with updated information inside the appropriate # mission in the database if mission: logger.info('Results from this attack will be saved under mission ' \ '"{mission}" in database'.format(mission=mission.name)) req.select_mission(mission.name) req.add_target(target) # Run the attack self.attack_scope.add_target(target) self.attack_scope.attack() return
def run(self): # Create report directory dirname = '{mission}-{datetime}'.format( mission=StringUtils.clean(self.mission.replace(' ', '_'), allowed_specials=('_', '-')), datetime=datetime.datetime.now().strftime('%Y%m%d%H%M%S')) self.output_path = self.output_path + '/' + dirname if not FileUtils.create_directory(self.output_path): logger.error('Unable to create report directory: "{path}"'.format( path=self.output_path)) return False # Retrieve all services in selected mission req = ServicesRequester(self.sqlsession) req.select_mission(self.mission) services = req.get_results() # Generate screenshots processor = ScreenshotsProcessor(self.mission, self.sqlsession) processor.run() screens_dir = self.output_path + '/screenshots' if not FileUtils.create_directory(screens_dir): logger.warning( 'Unable to create screenshots directory: "{path}"'.format( path=screens_dir)) else: for service in services: if service.name == 'http' and service.screenshot is not None \ and service.screenshot.status == ScreenStatus.OK: img_name = 'scren-{ip}-{port}-{id}'.format( ip=str(service.host.ip), port=service.port, id=service.id) path = screens_dir + '/' + img_name ImageUtils.save_image(service.screenshot.image, path + '.png') ImageUtils.save_image(service.screenshot.thumbnail, path + '.thumb.png') # Create index.html html = self.__generate_index() if FileUtils.write(self.output_path + '/index.html', html): logger.info('index.html file generated') else: logger.error('An error occured while generating index.html') return False # Create results-<service>.html (1 for each service) for service in services: # Useless to create page when no check has been run for the service if len(service.results) == 0: continue html = self.__generate_results_page(service) # Create a unique name for the service HTML file filename = 'results-{ip}-{port}-{service}-{id}.html'.format( ip=str(service.host.ip), port=service.port, service=service.name, id=service.id) if FileUtils.write(self.output_path + '/' + filename, html): logger.info( '{filename} file generated'.format(filename=filename)) else: logger.error( 'An error occured while generating {filename}'.format( filename=filename)) return False logger.success('HTML Report written with success in: {path}'.format( path=self.output_path)) logger.info('Important: If running from Docker container, make sure to run ' \ '"xhost +" on the host before') if Output.prompt_confirm('Would you like to open the report now ?', default=True): webbrowser.open(self.output_path + '/index.html') return True
def __generate_table_web(self): """ Generate the table with HTTP services registered in the mission """ req = ServicesRequester(self.sqlsession) req.select_mission(self.mission) filter_ = Filter(FilterOperator.AND) filter_.add_condition(Condition('http', FilterData.SERVICE_EXACT)) req.add_filter(filter_) services = req.get_results() if len(services) == 0: html = """ <tr class="notfound"> <td colspan="7">No record found</td> </tr> """ else: html = '' # Unavailable thumbnail with open(REPORT_TPL_DIR + '/../img/unavailable.png', 'rb') as f: unavailable_b64 = base64.b64encode(f.read()).decode('ascii') for service in services: # Results HTML page name results = 'results-{ip}-{port}-{service}-{id}.html'.format( ip=str(service.host.ip), port=service.port, service=service.name, id=service.id) # Encrypted ? (SSL/TLS) enc = '<span class="mdi mdi-lock" title="SSL/TLS encrypted"></span>' \ if service.is_encrypted() else '' # Web technos (in a specific order) # try: # technos = ast.literal_eval(service.web_technos) # except Exception as e: # logger.debug('Error when retrieving "web_technos" field ' \ # 'from db: {exc} for {service}'.format( # exc=e, service=service)) # technos = list() # tmp = list() # for t in technos: # tmp.append('{}{}{}'.format( # t['name'], # ' ' if t['version'] else '', # t['version'] if t['version'] else '')) # webtechnos = ' | '.join(tmp) webtechnos = '' product_types = ('web-server', 'web-appserver', 'web-cms', 'web-language', 'web-framework', 'web-jslib') for t in product_types: product = service.get_product(t) if product: webtechnos += '<span class="badge badge-{type} badge-light">' \ '{name}{version}</span>'.format( type=t, name=product.name, version=' '+str(product.version) \ if product.version else '') # Web Application Firewall product = service.get_product('web-application-firewall') waf = '' if product: waf = '<span class="badge badge-web-application-firewall ' \ 'badge-light">{name}{version}</span>'.format( name=product.name, version=' '+str(product.version) \ if product.version else '') # Screenshot img_name = 'scren-{ip}-{port}-{id}'.format(ip=str( service.host.ip), port=service.port, id=service.id) path = self.output_path + '/screenshots' if service.screenshot is not None \ and service.screenshot.status == ScreenStatus.OK \ and FileUtils.exists(path + '/' + img_name + '.png') \ and FileUtils.exists(path + '/' + img_name + '.thumb.png'): screenshot = """ <a href="{screenlarge}" title="{url} - {title}" class="image-link"> <img src="{screenthumb}" class="border rounded"> </a> """.format(url=service.url, screenlarge='screenshots/' + img_name + '.png', title=service.html_title, screenthumb='screenshots/' + img_name + '.thumb.png') else: screenshot = """ <img src="data:image/png;base64,{unavailable}"> """.format(unavailable=unavailable_b64) # HTML for table row html += """ <tr{clickable}> <td>{url}</td> <td>{enc}</td> <td>{title}</td> <td>{webtechnos}</td> <td>{waf}</td> <td>{screenshot}</td> <td>{checks}</td> </tr> """.format( clickable=' class="clickable-row" data-href="{results}"'.format( results=results) if len(service.results) > 0 else '', url='<a href="{}" title="{}">{}</a>'.format( service.url, service.url, StringUtils.shorten(service.url, 50)) \ if service.url else '', enc=enc, title=StringUtils.shorten(service.html_title, 40), webtechnos=webtechnos, waf=waf, screenshot=screenshot, checks=len(service.results)) return html
def __generate_table_services(self): """ Generate the table with all services registered in the mission """ req = ServicesRequester(self.sqlsession) req.select_mission(self.mission) services = req.get_results() if len(services) == 0: html = """ <tr class="notfound"> <td colspan="12">No record found</td> </tr> """ else: html = '' for service in services: hostname = service.host.hostname \ if service.host.ip != service.host.hostname else '' # Number of checks if len(service.results) > 0: nb_checks = len(service.results) else: nb_checks = '<span class="mdi mdi-window-close"></span>' # Number of creds nb_userpass = service.get_nb_credentials(single_username=False) nb_usernames = service.get_nb_credentials(single_username=True) nb_creds = '{}{}{}'.format( '<span class="text-green">{}</span>'.format(str(nb_userpass)) \ if nb_userpass > 0 else '', '/' if nb_userpass > 0 and nb_usernames > 0 else '', '<span class="text-yellow">{}</span>'.format( str(nb_usernames)) if nb_usernames > 0 else '') #if nb_creds == '': # nb_creds = '<span class="mdi mdi-window-close"></span>' # Number of vulns if len(service.vulns) > 0: nb_vulns = '<span class="text-green">{}</span>'.format( len(service.vulns)) else: #nb_vulns = '<span class="mdi mdi-window-close"></span>' nb_vulns = '' # Encrypted ? (SSL/TLS) enc = '<span class="mdi mdi-lock" title="SSL/TLS encrypted"></span>' \ if service.is_encrypted() else '' # Service name service_name = IconsMapping.get_icon_html( 'service', service.name) service_name += str(service.name) # Technologies technos = '' # For HTTP, respect a given order for technos for better readability if service.name == 'http': product_types = ( 'web-server', 'web-appserver', # 'web-application-firewall', Displayed only in "web" tab # for better readability 'web-cms', 'web-language', 'web-framework', 'web-jslib') for t in product_types: product = service.get_product(t) if product: technos += '<span class="badge badge-{type} badge-light">' \ '{name}{version}</span>'.format( type=t, name=product.name, version=' '+str(product.version) \ if product.version else '') else: for p in service.products: technos += '<span class="badge badge-generic badge-light">' \ '{name}{version}</span>'.format( type=p.type, name=p.name, version=' '+str(p.version) if p.version else '') # Col "Comment/Title" (title is for HTML title for HTTP) if service.html_title: comment = service.html_title else: comment = service.comment # Results HTML page name results = 'results-{ip}-{port}-{service}-{id}.html'.format( ip=str(service.host.ip), port=service.port, service=service.name, id=service.id) html += """ <tr{clickable}> <td class="font-weight-bold">{ip}</td> <td>{hostname}</th> <td class="font-weight-bold">{port} /{proto}</td> <td>{service}</td> <td>{enc}</td> <td>{banner}</td> <td>{technos}</td> <td>{url}</td> <td>{comment}</td> <td>{nb_checks}</td> <td>{nb_creds}</td> <td>{nb_vulns}</td> </tr> """.format( clickable=' class="clickable-row" data-href="{results}"'.format( results=results) if len(service.results) > 0 else '', ip=service.host.ip, hostname=hostname, port=service.port, proto={Protocol.TCP: 'tcp', Protocol.UDP: 'udp'}.get( service.protocol), service=service_name, enc=enc, banner=service.banner, technos=technos, url='<a href="{}" title="{}">{}</a>'.format( service.url, service.url, StringUtils.shorten(service.url, 40)) \ if service.url else '', comment=StringUtils.shorten(comment, 40), nb_checks=nb_checks, nb_creds=nb_creds, nb_vulns=nb_vulns) return html
def __generate_table_web(self): """ Generate the table with HTTP services registered in the mission """ req = ServicesRequester(self.sqlsession) req.select_mission(self.mission) filter_ = Filter(FilterOperator.AND) filter_.add_condition(Condition('http', FilterData.SERVICE_EXACT)) req.add_filter(filter_) services = req.get_results() if len(services) == 0: html = """ <tr class="notfound"> <td colspan="5">No record found</td> </tr> """ else: html = '' # Unavailable thumbnail with open(REPORT_TPL_DIR + '/../img/unavailable.png', 'rb') as f: unavailable_b64 = base64.b64encode(f.read()).decode('ascii') for service in services: # Results HTML page name results = 'results-{ip}-{port}-{service}-{id}.html'.format( ip=str(service.host.ip), port=service.port, service=service.name, id=service.id) # Web technos try: technos = ast.literal_eval(service.web_technos) except Exception as e: logger.debug('Error when retrieving "web_technos" field ' \ 'from db: {exc} for {service}'.format( exc=e, service=service)) technos = list() tmp = list() for t in technos: tmp.append('{}{}{}'.format( t['name'], ' ' if t['version'] else '', t['version'] if t['version'] else '')) webtechnos = ' | '.join(tmp) # Screenshot img_name = 'scren-{ip}-{port}-{id}'.format(ip=str( service.host.ip), port=service.port, id=service.id) path = self.output_path + '/screenshots' if service.screenshot is not None \ and service.screenshot.status == ScreenStatus.OK \ and FileUtils.exists(path + '/' + img_name + '.png') \ and FileUtils.exists(path + '/' + img_name + '.thumb.png'): screenshot = """ <a href="{screenlarge}" title="{title}" class="image-link"> <img src="{screenthumb}" class="border rounded"> </a> """.format(screenlarge='screenshots/' + img_name + '.png', title=service.html_title, screenthumb='screenshots/' + img_name + '.thumb.png') else: screenshot = """ <img src="data:image/png;base64,{unavailable}"> """.format(unavailable=unavailable_b64) # HTML for table row html += """ <tr{clickable}> <td>{url}</td> <td>{title}</td> <td>{webtechnos}</td> <td>{screenshot}</td> <td>{checks}</td> </tr> """.format( clickable=' class="clickable-row" data-href="{results}"'.format( results=results) if len(service.results) > 0 else '', url='<a href="{}" title="{}">{}</a>'.format( service.url, service.url, StringUtils.shorten(service.url, 50)) \ if service.url else '', title=StringUtils.shorten(service.html_title, 40), webtechnos=webtechnos, screenshot=screenshot, checks=len(service.results)) return html
def __generate_table_services(self): """ Generate the table with all services registered in the mission """ req = ServicesRequester(self.sqlsession) req.select_mission(self.mission) services = req.get_results() if len(services) == 0: html = """ <tr class="notfound"> <td colspan="9">No record found</td> </tr> """ else: html = '' for service in services: # Creds numbers nb_userpass = service.get_nb_credentials(single_username=False) nb_usernames = service.get_nb_credentials(single_username=True) nb_creds = '{}{}{}'.format( '<span class="text-green">{}</span>'.format(str(nb_userpass)) \ if nb_userpass > 0 else '', '/' if nb_userpass > 0 and nb_usernames > 0 else '', '<span class="text-yellow">{}</span> user(s)'.format( str(nb_usernames)) if nb_usernames > 0 else '') # Col "Comment/Title" (title is for HTML title for HTTP) if service.html_title: comment = service.html_title else: comment = service.comment # Results HTML page name results = 'results-{ip}-{port}-{service}-{id}.html'.format( ip=str(service.host.ip), port=service.port, service=service.name, id=service.id) html += """ <tr{clickable}> <td>{ip}</td> <td>{port}</td> <td>{proto}</td> <td>{service}</td> <td>{banner}</td> <td>{url}</td> <td>{comment}</td> <td>{checks}</td> <td>{creds}</td> </tr> """.format( clickable=' class="clickable-row" data-href="{results}"'.format( results=results) if len(service.results) > 0 else '', ip=service.host.ip, port=service.port, proto={Protocol.TCP: 'tcp', Protocol.UDP: 'udp'}.get( service.protocol), service=service.name, banner=service.banner, url='<a href="{}" title="{}">{}</a>'.format( service.url, service.url, StringUtils.shorten(service.url, 50)) \ if service.url else '', comment=StringUtils.shorten(comment, 40), checks=len(service.results), creds=nb_creds) return html
def __run_for_single_target(self, args): """ Run attack against a single target specified into argss """ req = ServicesRequester(self.sqlsess) mission = None # Get Mission if target must be added into a mission scope if args.add: mission = self.sqlsess.query(Mission).filter( Mission.name == args.add).first() if not mission: raise AttackException( 'The specified mission does not exist in the database. You should create it if needed' ) # Create new Service/Host objects (if service already exist, will be merged by ServicesRequester.add_target) service = Service( name=args.service, port=int(args.target_port), protocol={ 'tcp': Protocol.TCP, 'udp': Protocol.UDP }.get(self.settings.services.get_protocol(args.service)), url=args.target_ip_or_url if args.target_mode == TargetMode.URL else '') host = Host( ip=args.target_ip_or_url if args.target_mode == TargetMode.IP else '') # Will be updated when initializing Target() host.services.append(service) # Update credentials and options if needed for c in self.creds[args.service]: service.credentials.append(c) for u in self.users[args.service]: service.credentials.append(u) for o in self.options[args.service]: service.options.append(o) # Initialize Target and check if reachable target = Target(service, self.settings.services) if args.disable_banner_grab: logger.info('Check if target is reachable...') else: logger.info( 'Check if target is reachable and grab banner using Nmap...') reachable = target.smart_check( grab_banner_nmap=not args.disable_banner_grab) if args.target_mode == TargetMode.IP: msg = 'Target {neg}reachable: host {ip} | port {port}/{proto} | service {service}'.format( neg='not ' if not reachable else '', ip=target.get_ip(), port=target.get_port(), proto=target.get_protocol(), service=target.get_service_name()) else: msg = 'Target URL {url} is {neg}reachable'.format( url=target.get_url(), neg='not ' if not reachable else '') if reachable: service.up = True logger.success(msg) else: raise AttackException(msg) # Commit new data into database if target must be added to a mission if mission: logger.info( 'Results from this attack will be saved under mission "{mission}" in database' .format(mission=mission.name)) req.select_mission(mission.name) req.add_target(target) # Run the attack self.attack_scope.add_target(target) self.attack_scope.attack()
def __run_for_multi_targets(self, args): """ Run attack against multiple targets from the database """ # Get Mission from which targets must be extracted mission = self.sqlsess.query(Mission).filter( Mission.name == args.mission).first() if mission: logger.info( 'Extracting targets from mission "{mission}" ...'.format( mission=mission.name)) else: raise AttackException( 'Mission {mission} does not exist into the database'.format( mission=args.mission)) # Initialize Services requester and add filter if provided requester = ServicesRequester(self.sqlsess) requester.select_mission(args.mission) if args.filters_combined: for filt in args.filter: logger.info( 'Applying filters on mission scope: {filter}'.format( filter=filt)) if len(args.filter) > 1: logger.info('Logical or is applied between each filter') requester.add_filter(args.filters_combined) # Retrieve targeted services from database services = requester.get_results() if not services: raise AttackException( 'There is no matching service to target into the database') # Add each targeted service into Attack scope logger.info('Checking if targets are reachable...') for service in services: # Update credentials and options if needed for c in self.creds[service.name]: service.credentials.append(c) for u in self.users[service.name]: service.credentials.append(u) for o in self.options[service.name]: service.options.append(o) # Initialize Target and check if reachable target = Target(service, self.settings.services) service.up = target.smart_check(grab_banner_nmap=False) self.sqlsess.commit() msg = 'host {ip} | port {port}/{proto} | service {service}'.format( ip=target.get_ip(), port=target.get_port(), proto=target.get_protocol(), service=target.get_service_name()) if service.up: logger.success('Target reachable: ' + msg) else: logger.warning('Target not reachable (skipped): ' + msg) continue # Update info into database if needed #requester.add_target(target) self.attack_scope.add_target(target) self.attack_scope.attack()
class AttackScope: """Stores all targets selected for the current attack.""" def __init__(self, settings, arguments, sqlsession, mission, filter_categories=None, filter_checks=None, attack_profile=None, fast_mode=False): """ Construct AttackScope object :param Settings settings: Settings :param ArgumentsParser arguments: Arguments from command-line :param Session sqlsession: SQLAlchemy session :param str mission: Mission name :param list filter_categories: Selection of categories of checks to run (default is None, for all categories) :param list filter_checks: Selection of checks to run (default is None, for all checks) :param AttackProfile attack_profile: Attack profile (default is None, meaning no profile) :param bool fast_mode: Set to true to disable prompts """ self.settings = settings self.arguments = arguments self.sqlsess = sqlsession self.mission_name = mission self.services_requester = ServicesRequester(self.sqlsess) self.targets = list() self.current_targetid = 1 self.filter_categories = filter_categories self.filter_checks = filter_checks self.attack_profile = attack_profile self.fast_mode = fast_mode self.services_requester.select_mission(self.mission_name) #------------------------------------------------------------------------------------ def add_target(self, target): """ Add a target to the scope. :param Target target: Target to add """ self.targets.append(target) #------------------------------------------------------------------------------------ # Run Methods def attack(self): """Run the attack against all targets in the scope""" # 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 # Loop over the targets for i in range(1, len(self.targets) + 1): # In Multi-targets mode: # Display summary table and prompt for target selection # (not if too many target to avoid poor output) if 2 <= len(self.targets) <= 15: self.show_summary() if not self.fast_mode and len(self.targets) > 1: self.current_targetid = Output.prompt_choice_range( 'Attack target # (Ctrl+C to quit) ? [{default}] '.format( default=self.current_targetid), 1, len(self.targets), self.current_targetid) target = self.targets[self.current_targetid - 1] # Update status/progress bar status = 'Current target [{cur}/{total}]: {target}'.format( cur=i, total=len(self.targets), target=target) attack_progress.desc = '{status}{fill}'.format( status=status, fill=' ' * (DESC_LENGTH - len(status))) attack_progress.update() print() # Check the current target # For single target mode: already done in AttackController if len(self.targets) > 1: # By default, do NOT perform reverve DNS lookup & Nmap banner grabbing # because we assume it has been added via Nmap results in most cases # and thus, has already been done (behaviour can be changed with opt) reachable = target.smart_check( reverse_dns=(self.arguments.args.reverse_dns == 'on'), availability_check=True, grab_banner_nmap=( self.arguments.args.nmap_banner_grab == 'on'), web_technos_detection=False) if target.service.name == 'http': msg = 'Target URL {url} is {neg}reachable'.format( url=target.get_url(), neg='not ' if not reachable else '') else: msg = 'Target {neg}reachable: {target}'.format( neg='not ' if not reachable else '', target=target) # Update info into database if needed self.services_requester.add_target(target) if reachable: #target.service.up = True logger.success(msg) else: # Skip target if not reachable logger.error(msg) continue # In Single-target mode: Display summary table and prompt for confirmation if len(self.targets) == 1: self.show_summary() if not self.fast_mode: if Output.prompt_confirm('Start attack ?', default=True): self.current_targetid = 1 else: logger.warning('Attack canceled !') sys.exit(1) # Launch the attack on the selected target self.__attack_target(target, attack_progress) self.current_targetid += 1 self.current_targetid = self.current_targetid % len(self.targets) attack_progress.update() time.sleep(.5) attack_progress.close() manager.stop() # Clear progress bars def __attack_target(self, target, attack_progress): """ Run security checks against one target. :param Target target: The Target :param enlighten.Counter attack_progress: Attack progress """ # Print target information target.print_http_headers() target.print_context() # Run start method from SmartModule start = SmartStart(target.service, self.sqlsess) start.run() # Run security checks service_checks = self.settings.services.get_service_checks( target.get_service_name()) service_checks.run(target, self.arguments, self.sqlsess, filter_categories=self.filter_categories, filter_checks=self.filter_checks, attack_profile=self.attack_profile, attack_progress=attack_progress) #------------------------------------------------------------------------------------ # Output methods def show_summary(self): """Display a table showing the summary of the attack scope.""" data = list() columns = [ 'id', 'IP/', 'Hostname', 'Port', 'Proto', 'Service', 'Banner', 'URL', ] id_ = 1 for target in self.targets: pointer_color = 'blue' if self.current_targetid == id_ else None pointer_attr = 'bold' if self.current_targetid == id_ else None data.append([ Output.colored('>'+str(id_) if self.current_targetid == id_ \ else str(id_), color=pointer_color, attrs=pointer_attr), Output.colored(target.get_ip(), color=pointer_color, attrs=pointer_attr), Output.colored(StringUtils.wrap(target.get_host(), 50), color=pointer_color, attrs=pointer_attr), Output.colored(str(target.get_port()), color=pointer_color, attrs=pointer_attr), Output.colored(target.get_protocol(), color=pointer_color, attrs=pointer_attr), Output.colored(target.get_service_name(), color=pointer_color, attrs=pointer_attr), Output.colored(StringUtils.wrap(target.get_banner(), 55), color=pointer_color, attrs=pointer_attr), Output.colored(StringUtils.wrap(target.get_url(), 50), color=pointer_color, attrs=pointer_attr), ]) id_ += 1 print() Output.table(columns, data, hrules=False) print()
def __run_for_single_target(self, args): """Run attack against a single target specified into args""" req = ServicesRequester(self.sqlsess) mission = None # Get Mission if target must be added into a mission scope if args.add: mission = self.sqlsess.query(Mission).filter(Mission.name == args.add).first() if not mission: raise AttackException('The specified mission does not exist in the ' \ 'database. You should create it if needed') # Create new Service/Host objects (if service already exist, # will be merged by ServicesRequester.add_target) url = args.target_ip_or_url if args.target_mode == TargetMode.URL else '' ip = args.target_ip_or_url if args.target_mode == TargetMode.IP else '' service = Service(name=args.service, port=int(args.target_port), protocol=self.settings.services.get_protocol2(args.service), url=url) host = Host(ip=ip) # Will be updated when initializing Target() host.services.append(service) # Update credentials, options, products if specified in command-line for c in self.creds[args.service] : service.credentials.append(c) for u in self.users[args.service] : service.credentials.append(u) for p in self.products[args.service] : service.products.append(p) for o in self.options[args.service] : service.options.append(o) # Initialize Target try: target = Target(service, self.settings.services) except TargetException as e: logger.error(e) sys.exit(1) # Check if target is reachable # (by default, perform reverve DNS lookup & Nmap banner grabbing) reachable = target.smart_check( reverse_dns=(args.reverse_dns is None or args.reverse_dns == 'on'), availability_check=True, grab_banner_nmap=(args.nmap_banner_grab is None \ or args.nmap_banner_grab == 'on')) if args.target_mode == TargetMode.IP: msg = 'Target {neg}reachable: {target}'.format( neg='not ' if not reachable else '', target=target) else: msg = 'Target URL {url} is {neg}reachable'.format( url=target.get_url(), neg='not ' if not reachable else '') if reachable: logger.success(msg) else: # Skip target if not reachable logger.error(msg) return # Commit new data into database if target must be added to a mission if mission: logger.info('Results from this attack will be saved under mission ' \ '"{mission}" in database'.format(mission=mission.name)) req.select_mission(mission.name) req.add_target(target) # Run the attack self.attack_scope.add_target(target) self.attack_scope.attack()
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()
class AttackScope: """Stores all targets selected for the current attack.""" def __init__(self, settings, arguments, sqlsession, mission, filter_categories=None, filter_checks=None, attack_profile=None, fast_mode=False): """ Construct AttackScope object :param Settings settings: Settings :param ArgumentsParser arguments: Arguments from command-line :param Session sqlsession: SQLAlchemy session :param str mission: Mission name :param list filter_categories: Selection of categories of checks to run (default is None, for all categories) :param list filter_checks: Selection of checks to run (default is None, for all checks) :param AttackProfile attack_profile: Attack profile (default is None, meaning no profile) :param bool fast_mode: Set to true to disable prompts """ self.settings = settings self.arguments = arguments self.sqlsess = sqlsession self.mission_name = mission self.services_requester = ServicesRequester(self.sqlsess) self.targets = list() self.current_targetid = 1 self.filter_categories = filter_categories self.filter_checks = filter_checks self.attack_profile = attack_profile self.fast_mode = fast_mode self.services_requester.select_mission(self.mission_name) #------------------------------------------------------------------------------------ def add_target(self, target): """ Add a target to the scope. :param Target target: Target to add """ self.targets.append(target) #------------------------------------------------------------------------------------ # Run Methods def attack(self): """Run the attack against all targets in the scope""" # 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 # Loop over the targets for i in range(1,len(self.targets)+1): # Display table with targets self.show_summary() # Prompt for target selection if not self.fast_mode: if len(self.targets) == 1: if Output.prompt_confirm('Start attack ?', default=True): self.current_targetid = 1 else: logger.warning('Attack canceled !') sys.exit(1) else: self.current_targetid = Output.prompt_choice_range( 'Attack target # (Ctrl+C to quit) ? [{default}] '.format( default=self.current_targetid), 1, len(self.targets), self.current_targetid) target = self.targets[self.current_targetid-1] # Update status/progress bar status = 'Current target [{cur}/{total}]: {target}'.format( cur = i, total = len(self.targets), target = target) attack_progress.desc = '{status}{fill}'.format( status = status, fill = ' '*(DESC_LENGTH-len(status))) attack_progress.update() print() # Check the selected target and update its information # This is done for targets loaded from the database in multi-targets mode # (For single target, done before adding it to a mission in AttackController) # # - Reverse DNS lookup: not by default (should have already been done) # - Port check: always (target might not been reachable anymore) # - Nmap service detection: not by default (should have already been done) # - HTML title grabbing: always # - Web technologies detection: always # - Context initialization via SmartStart: always if len(self.targets) > 1: reachable = target.smart_check( reverse_dns_lookup=(self.arguments.args.reverse_dns == 'on'), availability_check=True, nmap_banner_grabbing=(self.arguments.args.nmap_banner_grab == 'on'), html_title_grabbing=True, web_technos_detection=True, smart_context_initialize=True) # Update info into database if needed self.services_requester.add_target(target) # Display availability status, skip if not reachable if target.service.name == 'http': msg = 'Target URL {url} is {neg}reachable'.format( url=target.get_url(), neg='not ' if not reachable else '') else: msg = 'Target {neg}reachable: {target}'.format( neg='not ' if not reachable else '', target=target) if reachable: logger.success(msg) else: logger.error(msg) self.__next_target() continue # Launch the attack on the selected target self.__attack_target(target, attack_progress) # Move to next target self.__next_target() # Clear progress bars attack_progress.update() time.sleep(.5) attack_progress.close() manager.stop() def __attack_target(self, target, attack_progress): """ Run security checks against one target. :param Target target: The Target :param enlighten.Counter attack_progress: Attack progress """ # Print target information target.print_http_headers() target.print_context() # Run security checks service_checks = self.settings.services.get_service_checks( target.get_service_name()) service_checks.run(target, self.arguments, self.sqlsess, filter_categories=self.filter_categories, filter_checks=self.filter_checks, attack_profile=self.attack_profile, attack_progress=attack_progress) def __next_target(self): """ Move to next target by incrementing current target id """ if self.current_targetid == len(self.targets): self.current_targetid = 1 else: self.current_targetid += 1 #------------------------------------------------------------------------------------ # Output methods def show_summary(self): """ Display a table showing the summary of the attack scope. The table has a max size defined in lib.core.Config, to avoid displaying an unreadable summary when large amount of targets have been loaded. """ if len(self.targets) > ATTACK_SUMMARY_TABLE_MAX_SIZE: id_min = self.current_targetid-2 if id_min < 1: id_min = 1 id_max = self.current_targetid+ATTACK_SUMMARY_TABLE_MAX_SIZE-1 \ -(self.current_targetid-id_min) if id_max > len(self.targets): id_min = id_min-(id_max-len(self.targets)) id_max = len(self.targets) else: id_min = 1 id_max = len(self.targets) data = list() columns = [ 'id', 'IP', 'Hostname', 'Port', 'Proto', 'Service', 'Banner', 'URL', ] id_ = 1 for target in self.targets: if id_ < id_min: id_ += 1 continue if id_ > id_max: break pointer_color = 'blue' if self.current_targetid == id_ else None pointer_attr = 'bold' if self.current_targetid == id_ else None data.append([ Output.colored('>'+str(id_) if self.current_targetid == id_ \ else str(id_), color=pointer_color, attrs=pointer_attr), Output.colored(target.get_ip(), color=pointer_color, attrs=pointer_attr), Output.colored(StringUtils.wrap(target.get_host(), 50), color=pointer_color, attrs=pointer_attr), Output.colored(str(target.get_port()), color=pointer_color, attrs=pointer_attr), Output.colored(target.get_protocol(), color=pointer_color, attrs=pointer_attr), Output.colored(target.get_service_name(), color=pointer_color, attrs=pointer_attr), Output.colored(StringUtils.wrap(target.get_banner(), 55), color=pointer_color, attrs=pointer_attr), Output.colored(StringUtils.wrap(target.get_url(), 50), color=pointer_color, attrs=pointer_attr), ]) id_ += 1 print() Output.table(columns, data, hrules=False) if len(self.targets) > ATTACK_SUMMARY_TABLE_MAX_SIZE: logger.info('Table has been truncated. Total number of loaded ' \ 'targets: {}'.format(len(self.targets))) print()