class Integration(Main): def __init__(self): super().__init__() self.lexsi = LexsiConnector(self.cfg) self.TheHiveConnector = TheHiveConnector(self.cfg) def validateRequest(self, request): if request.is_json: content = request.get_json() if 'type' in content and content['type'] == "Active": workflowReport = self.allIncidents2Alert(content['type']) if workflowReport['success']: return json.dumps(workflowReport), 200 else: return json.dumps(workflowReport), 500 else: self.logger.error('Missing type or type is not supported') return json.dumps({ 'sucess': False, 'message': "Missing type or type is not supported" }), 500 else: self.logger.error('Not json request') return json.dumps({ 'sucess': False, 'message': "Request didn't contain valid JSON" }), 400 def allIncidents2Alert(self, status): """ Get all opened incidents created within lexsi and create alerts for them in TheHive """ self.logger.info('%s.allincident2Alert starts', __name__) incidentsList = self.lexsi.getOpenItems()['result'] report = dict() report['success'] = True report['incidents'] = list() try: # each incidents in the list is represented as a dict # we enrich this dict with additional details for incident in incidentsList: # Prepare new alert incident_report = dict() self.logger.debug("incident: %s" % incident) theHiveAlert = self.IncidentToHiveAlert(incident) # searching if the incident has already been converted to alert query = dict() query['sourceRef'] = str(incident['incident']) self.logger.info('Looking for incident %s in TheHive alerts', str(incident['incident'])) results = self.TheHiveConnector.findAlert(query) if len(results) == 0: self.logger.info( 'incident %s not found in TheHive alerts, creating it', str(incident['incident'])) try: theHiveEsAlertId = self.TheHiveConnector.createAlert( theHiveAlert)['id'] self.TheHiveConnector.promoteAlertToCase( theHiveEsAlertId) incident_report['raised_alert_id'] = theHiveEsAlertId incident_report['lexsi_incident_id'] = incident[ 'incident'] incident_report['success'] = True except Exception as e: self.logger.error(incident_report) self.logger.error('%s.allincident2Alert failed', __name__, exc_info=True) incident_report['success'] = False if isinstance(e, ValueError): errorMessage = json.loads(str(e))['message'] incident_report['message'] = errorMessage else: incident_report['message'] = str( e) + ": Couldn't raise alert in TheHive" incident_report['incident_id'] = incident['incident'] # Set overall success if any fails report['success'] = False else: self.logger.info( 'incident %s already imported as alert, checking for updates', str(incident['incident'])) alert_found = results[0] if self.TheHiveConnector.checkForUpdates( theHiveAlert, alert_found, str(incident['incident'])): # Mark the alert as read self.TheHiveConnector.markAlertAsRead( alert_found['id']) incident_report['updated_alert_id'] = alert_found['id'] incident_report['sentinel_incident_id'] = str( incident['incident']) incident_report['success'] = True else: incident_report['sentinel_incident_id'] = str( incident['incident']) incident_report['success'] = True report['incidents'].append(incident_report) thehiveAlerts, open_lexsi_cases = self.lexsi_opened_alerts_thehive( ) self.set_alert_status_ignored(incidentsList, thehiveAlerts, open_lexsi_cases) except Exception as e: self.logger.error( 'Failed to create alert from Lexsi incident (retrieving incidents failed)', exc_info=True) report['success'] = False report[ 'message'] = "%s: Failed to create alert from incident" % str( e) return report def IncidentToHiveAlert(self, incident): # # Creating the alert # # Setup Tags tags = ['Lexsi', 'incident', 'Synapse'] # Skip for now artifacts = [] # Retrieve the configured case_template CaseTemplate = self.cfg.get('Lexsi', 'case_template') # Build TheHive alert alert = self.TheHiveConnector.craftAlert( "{}: {}".format(incident['incident'], incident['title']), self.craftAlertDescription(incident), self.getHiveSeverity(incident), self.timestamp_to_epoch(incident['detected'], "%Y-%m-%d %H:%M:%S"), tags, 2, 'New', 'internal', 'Lexsi', str(incident['incident']), artifacts, CaseTemplate) return alert def craftAlertDescription(self, incident): """ From the incident metadata, crafts a nice description in markdown for TheHive """ self.logger.debug('craftAlertDescription starts') # Start empty description = "" # Add incident details table description += ( '#### Summary\n\n' + '| | |\n' + '| ----------------------- | ------------- |\n' + '| **URL** | ' + "{}{}{}".format("```", str(incident['url']), "```") + ' |\n' + '| **Type** | ' + str(incident['type']) + ' |\n' + '| **Severity** | ' + str(incident['severity']) + ' |\n' + '| **Category** | ' + str(incident['category']) + ' |\n' + '| **Updated** | ' + str(incident['updated']) + ' |\n' + '| **Detected** | ' + str(incident['detected']) + ' |\n' + '| **Source** | ' + str(incident['source']) + ' |\n' + '| **Analyst Name(Lexsi)** | ' + str(incident['analystName']) + ' |\n' + '| **Link to Orange Portal** | ' + str("https://portal.cert.orangecyberdefense.com/cybercrime/{}". format(incident['id'])) + ' |\n' + '\n\n\n\n') return description def timestamp_to_epoch(self, date_time, pattern): return int(time.mktime(time.strptime(date_time, pattern))) * 1000 def getHiveSeverity(self, incident): # severity in TheHive is either low, medium, high or critical # while severity in Lexsi is from 0 to 5 if int(incident['severity']) in {0, 5}: return 1 # elif int(incident['severity']) in {2,3}: # return 2 # elif int(incident['severity']) in {4,5}: # return 3 else: return 2 def lexsi_opened_alerts_thehive(self): thehiveAlerts = [] open_lexsi_cases = {} query = In('tags', ['Lexsi']) self.logger.info( 'Looking for incident in TheHive alerts with tag Lexsi') # self.logger.info(query) results = self.TheHiveConnector.findAlert(query) for alert_found in results: # Check if a case is linked if 'case' in alert_found: try: case_found = self.TheHiveConnector.getCase( alert_found['case']) # Check if the status is open. Only then append it to the list if case_found['status'] == "Open": open_lexsi_cases[alert_found['sourceRef']] = case_found thehiveAlerts.append(alert_found['sourceRef']) except Exception as e: self.logger.error("Could not find case: {}".format(e), exc_info=True) continue self.logger.debug( "Lexsi Alerts opened in theHive: {}".format(thehiveAlerts)) return thehiveAlerts, open_lexsi_cases def compare_lists(self, list1, list2): return list(set(list1) - set(list2)) def set_alert_status_ignored(self, incidentsList, thehiveAlerts, open_lexsi_cases): lexsi_reporting = [] # incidentsList = self.lexsi.getOpenItems()['result'] for incident in incidentsList: lexsi_reporting.append(incident['incident']) self.logger.debug( "the list of opened Lexsi Incidents: {}".format(lexsi_reporting)) uncommon_elements = self.compare_lists(thehiveAlerts, lexsi_reporting) # uncommon_elements=['476121'] self.logger.debug( "Open cases present in TheHive but not in list of opened Lexsi Incidents: {}" .format((uncommon_elements))) for element in uncommon_elements: self.logger.info( "Preparing to close the case for {}".format(element)) query = dict() query['sourceRef'] = str(element) self.logger.debug('Looking for incident %s in TheHive alerts', str(element)) try: if element in open_lexsi_cases: # Resolve the case case_id = open_lexsi_cases[element]['id'] self.logger.debug("Case id for element {}: {}".format( element, case_id)) self.logger.debug("Preparing to resolve the case") self.TheHiveConnector.closeCase(case_id) self.logger.debug("Closed case with id {} for {}".format( case_id, element)) except Exception as e: self.logger.error("Could not close case: {}".format(e), exc_info=True) continue
class Automators(Main): def __init__(self, cfg, use_case_config): self.logger = logging.getLogger(__name__) self.logger.info('Initiating The Hive Automator') self.cfg = cfg self.TheHiveConnector = TheHiveConnector(cfg) if self.cfg.getboolean('Cortex', 'enabled'): self.CortexConnector = CortexConnector(cfg) #Read mail config self.mailsettings = self.cfg.get('TheHive', 'mail') ''' Can be used to check if there is a match between tags and the provided list. Useful for checking if there is a customer tag (having a list of customers) present where only one can match. ''' def MatchValueAgainstTags(self, tags, list): for tag in tags: if tag in list: return tag def craftUcTask(self, title, description): self.logger.debug('%s.craftUcTask starts', __name__) self.uc_task = CaseTask(title=title, description=description) return self.uc_task def createBasicTask(self, action_config, webhook): #Only continue if the right webhook is triggered if webhook.isImportedAlert(): pass else: return False #Perform actions for the CreateBasicTask action self.case_id = webhook.data['object']['case'] self.title = action_config['title'] self.description = action_config['description'] self.logger.info('Found basic task to create: %s' % self.title) #Create Task self.uc_task = self.craftUcTask(self.title, self.description) self.uc_task_id = self.TheHiveConnector.createTask( self.case_id, self.uc_task) return True def createMailTask(self, action_config, webhook): #Only continue if the right webhook is triggered if webhook.isImportedAlert(): pass else: return False self.tags = webhook.data['object']['tags'] self.case_id = webhook.data['object']['case'] if self.cfg.getboolean('Automation', 'enable_customer_list', fallback=False): self.customer_id = self.MatchValueAgainstTags( self.tags, self.customers) self.logger.info('Found customer %s, retrieving recipient' % self.customer_id) else: self.customer_id = None self.notification_type = "email" self.title = action_config['title'] self.description = self.renderTemplate(action_config['long_template'], self.tags, webhook, self.notification_type, customer_id=self.customer_id, mail_settings=self.mailsettings) self.logger.info('Found mail task to create: %s' % self.title) #Create Task self.ucTask = self.craftUcTask(self.title, self.description) self.ucTaskId = self.TheHiveConnector.createTask( self.case_id, self.ucTask) if 'auto_send_mail' in action_config and action_config[ 'auto_send_mail'] and not self.stopsend: self.logger.info('Sending mail for task with id: %s' % self.ucTaskId) self.TheHiveConnector.runResponder( 'case_task', self.ucTaskId, self.use_case_config['configuration']['mail']['responder_id']) def runAnalyzer(self, action_config, webhook): #Automatically run Analyzers for newly created cases where supported IOC's are present if webhook.isNewArtifact(): self.logger.debug( 'Case artifact found. Checking if observable is of a supported type to automatically fire the analyzer' ) #Retrieve caseid self.caseid = webhook.data['rootId'] #List all supported ioc's for the case self.observable = webhook.data['object'] #When supported, start a cortex analyzer for it if self.observable['dataType'] in action_config['datatypes']: self.supported_observable = self.observable['_id'] #Blacklist IP addresses, make sure the blacklist is present if self.observable[ 'dataType'] == "ip" and 'blacklist' in action_config and 'ip' in action_config[ 'blacklist']: for entry in action_config['blacklist']['ip']: #Initial values match = False observable_ip = ipaddress.ip_address( self.observable['data']) #Match ip with CIDR syntax if entry[-3:] == "/32": bl_entry = ipaddress.ip_address(entry[:-3]) match = observable_ip == bl_entry #Match ip without CIDR syntax elif "/" not in entry: bl_entry = ipaddress.ip_address(entry) match = observable_ip == bl_entry #Capture actual network entries else: bl_entry = ipaddress.ip_network(entry, strict=False) match = observable_ip in bl_entry #If matched add it to new entries to use outside of the loop if match: self.logger.debug( "Observable {} has matched {} of blacklist. Ignoring..." .format(self.observable['data'], entry)) return #Trigger a search for the supported ioc self.logger.debug( 'Launching analyzers for observable: {}'.format( self.observable['_id'])) self.TheHiveConnector.runAnalyzer( action_config['cortex_instance'], self.supported_observable, action_config['analyzer']) def closeCaseForTaxonomyInAnalyzerResults(self, action_config, webhook): #If the Job result contains a successful search with minimum of 1 hit, create a task to investigate the results if webhook.isCaseArtifactJob() and webhook.isSuccess(): #Case ID self.caseid = webhook.data['rootId'] #Load Case information self.case_data = self.TheHiveConnector.getCase(self.caseid) self.logger.debug('Job {} has just finished'.format( webhook.data['object']['cortexJobId'])) #Check if the result count higher than 0 if webhook.data['object']['report']['summary']['taxonomies'][0][ 'level'] in action_config["taxonomy_level"]: self.logger.info( 'Job {} has configured taxonomy level, checking if a task is already present for this observable' .format(webhook.data['object']['cortexJobId'])) #Check if task is present for investigating the new results if self.case_data['status'] != "Resolved": self.logger.info( 'Case is not yet closed, closing case for {} now...'. format(webhook.data['object']['cortexJobId'])) #Close the case self.TheHiveConnector.closeCase(self.caseid) self.report_action = 'closeCase' return self.report_action def createTaskForTaxonomyinAnalyzerResults(self, action_config, webhook): #If the Job result contains a successful search with minimum of 1 hit, create a task to investigate the results if webhook.isCaseArtifactJob() and webhook.isSuccess(): #Case ID self.caseid = webhook.data['rootId'] #Load Case information self.case_data = self.TheHiveConnector.getCase(self.caseid) self.logger.debug('Job {} has just finished'.format( webhook.data['object']['cortexJobId'])) #Check if the result count higher than 0 if webhook.data['object']['report']['summary']['taxonomies'][0][ 'level'] in action_config["taxonomy_level"]: self.logger.info( 'Job {} has configured taxonomy level, checking if a task is already present for this observable' .format(webhook.data['object']['cortexJobId'])) #Retrieve case task information self.response = self.TheHiveConnector.getCaseTasks(self.caseid) self.case_tasks = self.response.json() #Load CaseTask template self.casetask = CaseTask() #Observable + Link self.observable = webhook.data['object']['artifactId'] self.observable_link = self.cfg.get( 'Automation', 'hive_url', fallback="https://localhost" ) + "/index.html#!/case/" + self.caseid + "/observables/" + webhook.data[ 'object']['artifactId'] #Task name self.casetask.title = "{} {}".format(action_config['title'], self.observable) #Date self.date_found = time.strftime("%d-%m-%Y %H:%M") self.case_task_found = False for case_task in self.case_tasks: #Check if task is present for investigating the new results if self.casetask.title == case_task['title']: self.case_task_found = True if not self.case_task_found: self.logger.info( 'No task found, creating task for observable found in job {}' .format(webhook.data['object']['cortexJobId'])) #Add description self.casetask.description = action_config['description'] self.casetask.description = self.casetask.description + "\n\n {} is seen on {}\n".format( self.observable_link, self.date_found) #Check if case is closed if self.case_data['status'] == "Resolved": #Create a Case object case = Case() #Add the case id to the object case.id = self.caseid self.logger.info('Updating case %s' % case.id) #Define which fields need to get updated fields = ['status'] #Reopen the case case.status = "Open" #Update the case self.TheHiveConnector.updateCase(case, fields) #Add the case task self.TheHiveConnector.createTask(self.caseid, self.casetask) self.report_action = 'createTask' return self.report_action