def create_thehive_alerts(config, alerts): """ :param config: TheHive config :type config: dict :param alerts: List of alerts :type alerts: list :return: create TH alert """ thapi = TheHiveApi(config.get('url', None), config.get('key'), config.get('password', None), config.get('proxies')) for a in alerts: thapi.create_alert(a)
def send_thehive_message(rule, message, asset, description): print("send_thehive_message:", rule, message, asset, description) thehive_url = Setting.objects.get(key="alerts.endpoint.thehive.url") thehive_apikey = Setting.objects.get(key="alerts.endpoint.thehive.apikey") alert_message = "[Alert][Rule={}]{}".format(rule.title, message) api = TheHiveApi(thehive_url.value, thehive_apikey.value) sourceRef = str(uuid.uuid4())[0:6] rule_severity = 0 if rule.severity == "Low": rule_severity = 1 elif rule.severity == "Medium": rule_severity = 2 elif rule.severity == "High": rule_severity = 3 tlp = 0 if asset.criticity == "low": tlp = 1 elif asset.criticity == "medium": tlp = 2 elif asset.criticity == "high": tlp = 3 if asset: artifacts = [AlertArtifact(dataType=asset.type, data=asset.value)] try: alert = Alert( title=alert_message, tlp=tlp, severity=rule_severity, tags=['src:PatrOwl'], description=description, type='external', source='patrowl', sourceRef=sourceRef, artifacts=artifacts) response = api.create_alert(alert) if response.status_code == 201: alert_id = response.json()['id'] # todo: track theHive alerts Event.objects.create( message="[Rule][send_thehive_message()] " "Alert sent to TheHive with message '{}' (alert id: {})".format(message, alert_id), type="DEBUG", severity="DEBUG" ) else: Event.objects.create( message="[Rule][send_thehive_message()] Unable to send " "alert to TheHive with message ='{}'".format(message), type="ERROR", severity="ERROR" ) except Exception: Event.objects.create( message="[Rule][send_thehive_message()] Unable to send alert " "to TheHive with message ='{}'".format(message), type="ERROR", severity="ERROR")
def alert(self, matches): connection_details = self.rule['hive_connection'] api = TheHiveApi( '{hive_host}:{hive_port}'.format(**connection_details), connection_details.get('hive_apikey',''), proxies=connection_details.get('hive_proxies', {'http': '', 'https': ''}), cert=connection_details.get('hive_verify', False)) for match in matches: context = {'rule': self.rule, 'match': match} artifacts = [] for mapping in self.rule.get('hive_observable_data_mapping', []): for observable_type, match_data_key in mapping.iteritems(): try: match_data_keys = re.findall(r'\{match\[([^\]]*)\]', match_data_key) if all([True for k in match_data_keys if k in context['match']]): artifacts.append(AlertArtifact(dataType=observable_type, data=match_data_key.format(**context))) except KeyError: raise KeyError('\nformat string\n{}\nmatch data\n{}'.format(match_data_key, context)) alert_config = { 'artifacts': artifacts, 'sourceRef': str(uuid.uuid4())[0:6], 'title': '{rule[name]}'.format(**context) } alert_config.update(self.rule.get('hive_alert_config', {})) for alert_config_field, alert_config_value in alert_config.iteritems(): if alert_config_field == 'customFields': custom_fields = CustomFieldHelper() for cf_key, cf_value in alert_config_value.iteritems(): try: func = getattr(custom_fields, 'add_{}'.format(cf_value['type'])) except AttributeError: raise Exception('unsupported custom field type {}'.format(cf_value['type'])) value = cf_value['value'].format(**context) func(cf_key, value) alert_config[alert_config_field] = custom_fields.build() elif isinstance(alert_config_value, basestring): alert_config[alert_config_field] = alert_config_value.format(**context) elif isinstance(alert_config_value, (list, tuple)): formatted_list = [] for element in alert_config_value: try: formatted_list.append(element.format(**context)) except: formatted_list.append(element) alert_config[alert_config_field] = formatted_list alert = Alert(**alert_config) response = api.create_alert(alert) if response.status_code != 201: raise Exception('alert not successfully created in TheHive\n{}'.format(response.text))
def send_to_thehive(self, alert_config): connection_details = self.rule['hive_connection'] api = TheHiveApi( '{hive_host}:{hive_port}'.format(**connection_details), connection_details.get('hive_apikey', ''), proxies=connection_details.get('hive_proxies', {'http': '', 'https': ''}), cert=connection_details.get('hive_verify', False)) alert = Alert(**alert_config) response = api.create_alert(alert) if response.status_code != 201: raise Exception('alert not successfully created in TheHive\n{}'.format(response.text))
def sendHiveAlert(title, tlp, tags, description, sourceRef, artifact_string): hive_url = parser.get('hive', 'hive_url') hive_key = parser.get('hive', 'hive_key') hive_verifycert = parser.get('hive', 'hive_verifycert') tlp = int(parser.get('hive', 'hive_tlp')) # Check if verifying cert if 'False' in hive_verifycert: hiveapi = TheHiveApi(hive_url, hive_key, cert=False) else: hiveapi = TheHiveApi(hive_url, hive_key, cert=True) newtags = tags.strip('][').replace("'","").split(', ') artifacts = json.loads(artifact_string) #print(newtags) # Build alert hivealert = Alert( title= title, tlp=tlp, tags=newtags, description=description, type='external', source='SecurityOnion', sourceRef=sourceRef, artifacts=artifacts ) # Send it off response = hiveapi.create_alert(hivealert) if response.status_code == 201: print(json.dumps(response.json(), indent=4, sort_keys=True)) print('') id = response.json()['id'] # If running standalone / eval tell ES that we sent the alert #es_type = 'doc' #es_index = index #es_headers = {'Content-Type' : 'application/json'} #es_data = '{"script" : {"source": "ctx._source.tags.add(params.tag)","lang": "painless","params" : {"tag" : "Sent to TheHive"}}}' #update_es_event = requests.post(es_url + '/' + es_index + '/' + es_type + '/' + esid + '/_update', headers=es_headers, data=es_data) #print(update_es_event.content) else: print('ko: {}/{}'.format(response.status_code, response.text)) sys.exit(0) # Redirect to TheHive instance return redirect(hive_url + '/index.html#!/alert/list')
def create_thehive_alerts(config, alerts): """ :param config: TheHive config :type config: dict :param alerts: List of alerts :type alerts: list :return: create TH alert """ thapi = TheHiveApi(config.get('url', None), config.get('key'), config.get('password', None), config.get('proxies')) response = thapi.create_alert(alerts) if response.status_code == 201: print(json.dumps(response.json(), indent=4, sort_keys=True)) else: print('ko: {}/{}'.format(response.status_code, response.text))
def prepare_alert(subject, indicatorlevel, emailbody, alerttype, tag): hive_address = ''.join(settings.stored_hive_address[0]) hive_api = ''.join(settings.stored_api_key[0]) #Define the connection to thehive installation (including the generated API key). api = TheHiveApi(hive_address, hive_api, None, {'http': '', 'https': ''}) # Prepare the sample Alert print("Preparing the alert for", alerttype) sourceRef = str(uuid.uuid4())[0:6] alert = Alert(title=subject, tlp=indicatorlevel, tags=[tag], description=emailbody, type=alerttype, source='instance1', sourceRef=sourceRef, artifacts="") # Create the Alert print('Create Alert') print('-----------------------------') id = None response = api.create_alert(alert) if response.status_code == 201: print("Alert created sucessfully!") # print(json.dumps(response.json(), indent=4, sort_keys=True)) # print('') id = response.json()['id'] else: print("Unable to create alert") # print('ko: {}/{}'.format(response.status_code, response.text)) sys.exit(0) # Get all the details of the created alert print('Get created alert {}'.format(id)) print('-----------------------------') response = api.get_alert(id) if response.status_code == requests.codes.ok: # print(json.dumps(response.json(), indent=4, sort_keys=True)) print('') else: print("Error retreiving the alert!") print('ko: {}/{}'.format(response.status_code, response.text))
def alert(self, matches): connection_details = self.rule['hive_connection'] api = TheHiveApi( '{hive_host}:{hive_port}'.format(**connection_details), '{hive_username}'.format(**connection_details), '{hive_password}'.format(**connection_details), connection_details.get('hive_proxies', {'http': '', 'https': ''})) for match in matches: context = {'rule': self.rule, 'match': match} artifacts = [] for mapping in self.rule.get('hive_observable_data_mapping', []): for observable_type, match_data_key in mapping.iteritems(): try: artifacts.append(AlertArtifact(dataType=observable_type, data=match_data_key.format(**context))) except KeyError: raise KeyError('\nformat string\n{}\nmatch data\n{}'.format(match_data_key, context)) alert_config = { 'artifacts': artifacts, 'sourceRef': str(uuid.uuid4())[0:6], 'title': '{rule[index]}_{rule[name]}'.format(**context) } alert_config.update(self.rule.get('hive_alert_config', {})) for alert_config_field, alert_config_value in alert_config.iteritems(): if isinstance(alert_config_value, basestring): alert_config[alert_config_field] = alert_config_value.format(**context) elif isinstance(alert_config_value, (list, tuple)): formatted_list = [] for element in alert_config_value: try: formatted_list.append(element.format(**context)) except: formatted_list.append(element) alert_config[alert_config_field] = formatted_list alert = Alert(**alert_config) response = api.create_alert(alert) if response.status_code != 201: raise Exception('alert not successfully created in TheHive\n{}'.format(response.text))
def create_th_alerts(config, alerts): """ :param config: TheHive config :type config: dict :param alerts: List of alerts :type alerts: list :return: create TH alert """ thapi = TheHiveApi(config.get('url', None), config.get('key'), config.get('password', None), config.get('proxies')) for a in alerts: response = thapi.create_alert(a) logging.debug('API TheHive - status code: {}'.format( response.status_code)) if response.status_code > 299: logging.debug('API TheHive - raw error output: {}'.format( response.raw.read()))
def create_alert(): # Get request JSON content = request.get_json() # Configure logging logging.basicConfig( filename=app.config['LOG_FILE'], filemode='a', format='%(asctime)s - graylog2thehive - %(levelname)s - %(message)s', level=logging.INFO) logging.info(json.dumps(content)) # Configure API api = TheHiveApi(app.config['HIVE_URL'], app.config['API_KEY']) # Configure artifacts artifacts = [] # Configure tags tags = ['graylog'] # Build description body and tags list description = 'Alert Condition: \n' + content['check_result'][ 'triggered_condition']['title'] + '\n\nMatching messages:\n\n' tags = ['graylog'] for message in content['check_result']['matching_messages']: description = description + "\n\n---\n\n**Source:** " + message[ 'source'] + "\n\n**Log URL:** " + app.config[ 'GRAYLOG_URL'] + "/messages/" + message[ 'index'] + "/" + message['id'] + "\n\n" message_flattened = flatten_dict(message) for key in message_flattened.keys(): if key != "message" and key != "source": description = description + "\n**" + key + ":** " + json.dumps( message_flattened[key], ensure_ascii=False) + "\n" # Use any IPs, hashes, URLs, filenames, etc here in place of src_ip and dst_ip to include them as artifacts/observables in your alert if key == "cmd_url2": artifacts.append( AlertArtifact(dataType='url', tags=[key], data=message_flattened[key])) if key == "dest_ip": artifacts.append( AlertArtifact(dataType='ip', tags=[key], data=message_flattened[key])) if key == "MD5" or key == "SHA256": artifacts.append( AlertArtifact(dataType='hash', tags=[key], data=message_flattened[key])) description = description + '\n\n**Raw Message:** \n\n```\n' + json.dumps( message) + '\n```\n---\n' # Prepare alert sourceRef = str(uuid.uuid4())[0:6] alert = Alert(title="Graylog Alert: " + content['stream']['title'] + " - " + content['check_result']['result_description'], tlp=2, tags=tags, description=content['check_result']['result_description'], type='external', source='graylog', artifacts=artifacts, sourceRef=sourceRef) # Create the alert print('Create Alert') print('-----------------------------') id = None response = api.create_alert(alert) if response.status_code == 201: logging.info(json.dumps(response.json(), indent=4, sort_keys=True)) print(json.dumps(response.json(), indent=4, sort_keys=True)) print('') id = response.json()['id'] else: print('ko: {}/{}'.format(response.status_code, response.text)) sys.exit(0) return content['check_result']['result_description']
def createGRRFlow(esid, flow_name): search = getHits(esid) tlp = int(parser.get('hive', 'hive_tlp')) # Check if verifying cert if 'False' in hive_verifycert: hiveapi = TheHiveApi(hive_url, hive_key, cert=False) else: hiveapi = TheHiveApi(hive_url, hive_key, cert=True) grr_url = parser.get('grr', 'grr_url') grr_user = parser.get('grr', 'grr_user') grr_pass = parser.get('grr', 'grr_pass') grrapi = api.InitHttp(api_endpoint=grr_url, auth=(grr_user, grr_pass)) base64string = '%s:%s' % (grr_user, grr_pass) base64string = base64.b64encode(bytes(base64string, "utf-8")) authheader = "Basic %s" % base64string index_response = requests.get(grr_url, auth=HTTPBasicAuth(grr_user, grr_pass)) csrf_token = index_response.cookies.get("csrftoken") headers = { "Authorization": authheader, "x-csrftoken": csrf_token, "x-requested-with": "XMLHttpRequest" } cookies = {"csrftoken": csrf_token} for result in search['hits']['hits']: result = result['_source'] message = result['message'] description = str(message) info = description if 'source_ip' in result: source_ip = result['source_ip'] if 'destination_ip' in result: destination_ip = result['destination_ip'] for ip in source_ip, destination_ip: search_result = grrapi.SearchClients(ip) grr_result = {} client_id = '' for client in search_result: # Get client id client_id = client.client_id client_last_seen_at = client.data.last_seen_at grr_result[client_id] = client_last_seen_at #flow_name = "ListProcesses" if client_id is None: pass # Process flow and get flow id flow_id = listProcessFlow(client_id, grr_url, headers, cookies, grr_user, grr_pass) # Get status status = checkFlowStatus(client_id, grr_url, flow_id, headers, cookies, grr_user, grr_pass) # Keep checking to see if complete while status != "terminated": time.sleep(15) print( "Flow not yet completed..watiing 15 secs before attempting to check status again..." ) status = checkFlowStatus(client_id, grr_url, flow_id, headers, cookies, grr_user, grr_pass) # If terminated, run the download if status == "terminated": downloadFlowResults(client_id, grr_url, flow_id, headers, cookies, grr_user, grr_pass) #print("Done!") # Run flow via API client #flow_obj = grrapi.Client(client_id) #flow_obj.CreateFlow(name=flow_name) title = "Test Alert with GRR Flow" description = str(message) sourceRef = str(uuid.uuid4())[0:6] tags = ["SecurityOnion", "GRR"] artifacts = [] id = None filepath = "/tmp/soctopus/" + client_id + ".zip" artifacts.append( AlertArtifact(dataType='file', data=str(filepath))) # Build alert hivealert = Alert(title=title, tlp=tlp, tags=tags, description=description, type='external', source='SecurityOnion', sourceRef=sourceRef, artifacts=artifacts) # Send it off response = hiveapi.create_alert(hivealert) if client_id: # Redirect to GRR instance return redirect(grr_url + '/#/clients/' + client_id + '/flows') else: return "No matches found for source or destination ip"
def submitTheHive(message): ''' Create a new case in TheHive based on the email Return 'TRUE' is successfully processed otherwise 'FALSE' ''' global log # Decode email msg = email.message_from_bytes(message) decode = email.header.decode_header(msg['From'])[0] if decode[1] is not None: fromField = decode[0].decode(decode[1]) else: fromField = str(decode[0]) decode = email.header.decode_header(msg['Subject'])[0] if decode[1] is not None: subjectField = decode[0].decode(decode[1]) else: subjectField = str(decode[0]) log.info("From: %s Subject: %s" % (fromField, subjectField)) attachments = [] observables = [] # Extract SMTP headers and search for observables parser = HeaderParser() headers = parser.parsestr(msg.as_string()) headers_string = '' i = 0 while i < len(headers.keys()): headers_string = headers_string + headers.keys( )[i] + ': ' + headers.values()[i] + '\n' i += 1 # Temporary disabled # observables = searchObservables(headers_string, observables) body = '' for part in msg.walk(): if part.get_content_type() == "text/plain": try: body = part.get_payload(decode=True).decode() except UnicodeDecodeError: body = part.get_payload(decode=True).decode('ISO-8859-1') observables.extend(searchObservables(body, observables)) elif part.get_content_type() == "text/html": try: html = part.get_payload(decode=True).decode() except UnicodeDecodeError: html = part.get_payload(decode=True).decode('ISO-8859-1') observables.extend(searchObservables(html, observables)) else: # Extract MIME parts filename = part.get_filename() mimetype = part.get_content_type() if filename and mimetype: if mimetype in config['caseFiles'] or not config['caseFiles']: log.info("Found attachment: %s (%s)" % (filename, mimetype)) # Decode the attachment and save it in a temporary file charset = part.get_content_charset() if charset is None: charset = chardet.detect(bytes(part))['encoding'] fd, path = tempfile.mkstemp(prefix=slugify(filename) + "_") try: with os.fdopen(fd, 'w+b') as tmp: tmp.write(part.get_payload(decode=1)) attachments.append(path) except OSerror as e: log.error("Cannot dump attachment to %s: %s" % (path, e.errno)) return False # Cleanup observables (remove duplicates) new_observables = [] for o in observables: if not {'type': o['type'], 'value': o['value']} in new_observables: # Is the observable whitelisted? if isWhitelisted(o['value']): log.debug('Skipping whitelisted observable: %s' % o['value']) else: new_observables.append({ 'type': o['type'], 'value': o['value'] }) log.debug('Found observable %s: %s' % (o['type'], o['value'])) else: log.info('Ignoring duplicate observable: %s' % o['value']) log.info("Removed duplicate observables: %d -> %d" % (len(observables), len(new_observables))) observables = new_observables if config['thehiveAuthMethod'] == 'APIKey': api = TheHiveApi(config['thehiveURL'], config['thehiveAPIKey'], None, { 'http': '', 'https': '' }) else: api = TheHiveApi(config['thehiveURL'], config['thehiveUser'], config['thehivePassword'], { 'http': '', 'https': '' }) # Search for interesting keywords in subjectField: log.debug("Searching for %s in '%s'" % (config['alertKeywords'], subjectField)) if re.match(config['alertKeywords'], subjectField, flags=0): # # Add observables found in the mail body # artifacts = [] if config['thehiveObservables'] and len(observables) > 0: for o in observables: artifacts.append( AlertArtifact(dataType=o['type'], data=o['value'])) # # Prepare tags - add alert keywords found to the list of tags # tags = list(config['alertTags']) match = re.findall(config['alertKeywords'], subjectField) for m in match: tags.append(m) # # Prepare the alert # sourceRef = str(uuid.uuid4())[0:6] alert = Alert(title=subjectField.replace('[ALERT]', ''), tlp=int(config['alertTLP']), tags=tags, description=body, type='external', source=fromField, sourceRef=sourceRef, artifacts=artifacts) # Create the Alert id = None response = api.create_alert(alert) if response.status_code == 201: log.info('Created alert %s' % response.json()['sourceRef']) else: log.error('Cannot create alert: %s (%s)' % (response.status_code, response.text)) return False else: # Prepare the sample case tasks = [] for task in config['caseTasks']: tasks.append(CaseTask(title=task)) # Prepare the custom fields customFields = CustomFieldHelper()\ .add_string('from', fromField)\ .add_string('attachment', str(attachments))\ .build() # If a case template is specified, use it instead of the tasks if len(config['caseTemplate']) > 0: case = Case(title=subjectField, tlp=int(config['caseTLP']), flag=False, tags=config['caseTags'], description=body, template=config['caseTemplate'], customFields=customFields) else:
def submitTheHive(message): '''Create a new case in TheHive based on the email''' # Decode email msg = email.message_from_bytes(message) decode = email.header.decode_header(msg['From'])[0] fromField = str(decode[0]) decode = email.header.decode_header(msg['Subject'])[0] subjectField = str(decode[0]) if args.verbose: print("[INFO] From: %s Subject: %s" % (fromField, subjectField)) attachments = [] body = '' for part in msg.walk(): if part.get_content_type() == "text/plain": body = part.get_payload(decode=True).decode() else: # Extract MIME parts filename = part.get_filename() mimetype = part.get_content_type() if filename and mimetype: if mimetype in config['caseFiles'] or not config['caseFiles']: print("[INFO] Found attachment: %s (%s)" % (filename, mimetype)) # Decode the attachment and save it in a temporary file charset = part.get_content_charset() if charset is None: charset = chardet.detect(bytes(part))['encoding'] fd, path = tempfile.mkstemp(prefix=slugify(filename) + "_") try: with os.fdopen(fd, 'w+b') as tmp: tmp.write(part.get_payload(decode=1)) attachments.append(path) except OSError as e: print("[ERROR] Cannot dump attachment to %s: %s" % (path, e.errno)) api = TheHiveApi(config['thehiveURL'], config['thehiveUser'], config['thehivePassword'], { 'http': '', 'https': '' }) if '[ALERT]' in subjectField: # Prepare the alert sourceRef = str(uuid.uuid4())[0:6] alert = Alert(title=subjectField.replace('[ALERT]', ''), tlp=int(config['alertTLP']), tags=config['alertTags'], description=body, type='external', source=fromField, sourceRef=sourceRef) # Create the Alert id = None response = api.create_alert(alert) if response.status_code == 201: if args.verbose: print('[INFO] Created alert %s' % response.json()['sourceRef']) else: print('[ERROR] Cannot create alert: %s (%s)' % (response.status_code, response.text)) sys.exit(0) else: # Prepare the sample case tasks = [] for task in config['caseTasks']: tasks.append(CaseTask(title=task)) # Prepare the custom fields customFields = CustomFieldHelper()\ .add_string('from', fromField)\ .add_string('attachment', str(attachments))\ .build() case = Case(title=subjectField, tlp=int(config['caseTLP']), flag=False, tags=config['caseTags'], description=body, tasks=tasks, customFields=customFields) # Create the case id = None response = api.create_case(case) if response.status_code == 201: newID = response.json()['id'] if args.verbose: print('[INFO] Created case %s' % response.json()['caseId']) if len(attachments) > 0: for path in attachments: observable = CaseObservable( dataType='file', data=[path], tlp=int(config['caseTLP']), ioc=False, tags=config['caseTags'], message='Created by imap2thehive.py') response = api.create_case_observable(newID, observable) if response.status_code == 201: if args.verbose: print('[INFO] Added observable %s to case ID %s' % (path, newID)) os.unlink(path) else: print('[ERROR] Cannot add observable: %s - %s (%s)' % (path, response.status_code, response.text)) sys.exit(0) else: print('[ERROR] Cannot create case: %s (%s)' % (response.status_code, response.text)) sys.exit(0) return
class TheHive(AppBase): """ An example of a Walkoff App. Inherit from the AppBase class to have Redis, logging, and console logging set up behind the scenes. """ __version__ = "1.0.0" app_name = "thehive" def __init__(self, redis, logger, console_logger=None): """ Each app should have this __init__ to set up Redis and logging. :param redis: :param logger: :param console_logger: """ super().__init__(redis, logger, console_logger) # async def run_analyzer(self, apikey, url, title_query): # self.thehive = TheHiveApi(url, apikey, cert=False) # response = self.thehive.find_cases(query=String("title:'%s'" % title_query), range='all', sort=[]) # return response.text async def search_cases(self, apikey, url, title_query): self.thehive = TheHiveApi(url, apikey, cert=False) response = self.thehive.find_cases(query=ContainsString( "title", title_query), range="all", sort=[]) return response.text async def search_query(self, apikey, url, search_for, custom_query): self.thehive = TheHiveApi(url, apikey, cert=False) try: query = json.loads(custom_query) except: raise IOError("Invalid JSON payload received.") if search_for == "alert": response = self.thehive.find_alerts(query=query, range="all", sort=[]) else: response = self.thehive.find_cases(query=query, range="all", sort=[]) if response.status_code == 200: return response.text else: raise IOError(response.text) async def add_observable(self, apikey, url, case_id, data, datatype, tags): self.thehive = TheHiveApi(url, apikey, cert=False) if tags: if ", " in tags: tags = tags.split(", ") elif "," in tags: tags = tags.split(",") else: tags = [tags] else: tags = [] item = thehive4py.models.CaseObservable( dataType=datatype, data=data, tlp=1, ioc=False, sighted=False, tags=["Shuffle"], message="Created by shuffle", ) return self.thehive.create_case_observable(case_id, item).text async def search_alerts(self, apikey, url, title_query, search_range="0-25"): self.thehive = TheHiveApi(url, apikey, cert=False) # Could be "all" too if search_range == "": search_range = "0-25" response = self.thehive.find_alerts(query=ContainsString( "title", title_query), range=search_range, sort=[]) return response.text async def create_case(self, apikey, url, title, description="", tlp=1, severity=1, tags=""): self.thehive = TheHiveApi(url, apikey, cert=False) if tags: if ", " in tags: tags = tags.split(", ") elif "," in tags: tags = tags.split(",") else: tags = [tags] else: tags = [] # Wutface fix if not tlp: tlp = 1 if not severity: severity = 1 if isinstance(tlp, str): if not tlp.isdigit(): return "TLP needs to be a number from 0-2, not %s" % tlp tlp = int(tlp) if isinstance(severity, str): if not severity.isdigit(): return "Severity needs to be a number from 0-2, not %s" % tlp severity = int(severity) if tlp > 3 or tlp < 0: return "TLP needs to be a number from 0-3, not %d" % tlp if severity > 2 or severity < 0: return "Severity needs to be a number from 0-2, not %d" % tlp case = thehive4py.models.Case( title=title, tlp=tlp, severity=severity, tags=tags, description=description, ) try: ret = self.thehive.create_case(case) return ret.text except requests.exceptions.ConnectionError as e: return "ConnectionError: %s" % e async def create_alert( self, apikey, url, type, source, sourceref, title, description="", tlp=1, severity=1, tags="", ): self.thehive = TheHiveApi(url, apikey, cert=False) if tags: if ", " in tags: tags = tags.split(", ") elif "," in tags: tags = tags.split(",") else: tags = [tags] else: tags = [] # Wutface fix if not tlp: tlp = 1 if not severity: severity = 1 if isinstance(tlp, str): if not tlp.isdigit(): return "TLP needs to be a number from 0-3, not %s" % tlp tlp = int(tlp) if isinstance(severity, str): if not severity.isdigit(): return "Severity needs to be a number from 1-3, not %s" % severity severity = int(severity) if tlp > 3 or tlp < 0: return "TLP needs to be a number from 0-3, not %d" % tlp if severity > 3 or severity < 1: return "Severity needs to be a number from 1-3, not %d" % severity alert = thehive4py.models.Alert( title=title, tlp=tlp, severity=severity, tags=tags, description=description, type=type, source=source, sourceRef=sourceref, ) try: ret = self.thehive.create_alert(alert) return ret.text except requests.exceptions.ConnectionError as e: return "ConnectionError: %s" % e async def create_alert_artifact(self, apikey, url, alert_id, dataType, data, message=None, tlp="2", ioc="False", sighted="False", ignoreSimilarity="False", tags=None): self.thehive = TheHiveApi(url, apikey, cert=False, version=4) if tlp: tlp = int(tlp) else: tlp = 2 ioc = ioc.lower().strip() == "true" sighted = sighted.lower().strip() == "true" ignoreSimilarity = ignoreSimilarity.lower().strip() == "true" if tags: tags = [x.strip() for x in tags.split(",")] else: tags = [] alert_artifact = thehive4py.models.AlertArtifact( dataType=dataType, data=data, message=message, tlp=tlp, ioc=ioc, sighted=sighted, ignoreSimilarity=ignoreSimilarity, tags=tags) try: ret = self.thehive.create_alert_artifact(alert_id, alert_artifact) except requests.exceptions.ConnectionError as e: return "ConnectionError: %s" % e if ret.status_code > 299: raise ConnectionError(ret.text) return ret.text # Gets an item based on input. E.g. field_type = Alert async def get_item(self, apikey, url, field_type, cur_id): self.thehive = TheHiveApi(url, apikey, cert=False) newstr = "" ret = "" if field_type.lower() == "alert": ret = self.thehive.get_alert(cur_id + "?similarity=1") elif field_type.lower() == "case": ret = self.thehive.get_case(cur_id) elif field_type.lower() == "case_observables": ret = self.thehive.get_case_observables(cur_id) elif field_type.lower() == "case_task": ret = self.thehive.get_case_task(cur_id) elif field_type.lower() == "case_tasks": ret = self.thehive.get_case_tasks(cur_id) elif field_type.lower() == "case_template": ret = self.thehive.get_case_tasks(cur_id) elif field_type.lower() == "linked_cases": ret = self.thehive.get_linked_cases(cur_id) elif field_type.lower() == "task_log": ret = self.thehive.get_task_log(cur_id) elif field_type.lower() == "task_logs": ret = self.thehive.get_task_logs(cur_id) else: return ( "%s is not implemented. See https://github.com/frikky/shuffle-apps for more info." % field_type) return ret.text async def close_alert(self, apikey, url, alert_id): self.thehive = TheHiveApi(url, apikey, cert=False) return self.thehive.mark_alert_as_read(alert_id).text async def reopen_alert(self, apikey, url, alert_id): self.thehive = TheHiveApi(url, apikey, cert=False) return self.thehive.mark_alert_as_unread(alert_id).text async def create_case_from_alert(self, apikey, url, alert_id, case_template=None): self.thehive = TheHiveApi(url, apikey, cert=False) response = self.thehive.promote_alert_to_case( alert_id=alert_id, case_template=case_template) return response.text async def merge_alert_into_case(self, apikey, url, alert_id, case_id): self.thehive = TheHiveApi(url, apikey, cert=False) req = url + f"/api/alert/{alert_id}/merge/{case_id}" ret = requests.post(req, auth=self.thehive.auth) return ret.text # Not sure what the data should be async def update_field(self, apikey, url, field_type, cur_id, field, data): # This is kinda silly but.. if field_type.lower() == "alert": newdata = {} if data.startswith("%s"): ticket = self.thehive.get_alert(cur_id) if ticket.status_code != 200: pass newdata[field] = "%s%s" % (ticket.json()[field], data[2:]) else: newdata[field] = data # Bleh url = "%s/api/alert/%s" % (url, cur_id) if field == "status": if data == "New" or data == "Updated": url = "%s/markAsUnread" % url elif data == "Ignored": url = "%s/markAsRead" % url ret = requests.post( url, headers={ "Content-Type": "application/json", "Authorization": "Bearer %s" % apikey, }, ) else: ret = requests.patch( url, headers={ "Content-Type": "application/json", "Authorization": "Bearer %s" % apikey, }, json=newdata, ) return str(ret.status_code) else: return ( "%s is not implemented. See https://github.com/frikky/walkoff-integrations for more info." % field_type) # https://github.com/TheHive-Project/TheHiveDocs/tree/master/api/connectors/cortex async def run_analyzer(self, apikey, url, cortex_id, analyzer_id, artifact_id): self.thehive = TheHiveApi(url, apikey, cert=False) return self.thehive.run_analyzer(cortex_id, artifact_id, analyzer_id).text # Creates a task log in TheHive with file async def create_task_log(self, apikey, url, task_id, message, filedata={}): if filedata["success"] == False: return "No file to upload. Skipping message." headers = { "Authorization": "Bearer %s" % apikey, } files = {} if len(filedata["data"]) > 0: files = { "attachment": (filedata["filename"], filedata["data"]), } data = {"_json": """{"message": "%s"}""" % message} response = requests.post( "%s/api/case/task/%s/log" % (url, task_id), headers=headers, files=files, data=data, ) return response.text # Creates an observable as a file in a case async def create_case_file_observable(self, apikey, url, case_id, tags, filedata): if filedata["success"] == False: return "No file to upload. Skipping message." headers = { "Authorization": "Bearer %s" % apikey, } if tags: if ", " in tags: tags = tags.split(", ") elif "," in tags: tags = tags.split(",") else: tags = [tags] files = {} if len(filedata["data"]) > 0: files = { "attachment": (filedata["filename"], filedata["data"]), } outerarray = {"dataType": "file", "tags": tags} data = {"_json": """%s""" % json.dumps(outerarray)} response = requests.post( "%s/api/case/%s/artifact" % (url, case_id), headers=headers, files=files, data=data, ) return response.text
def create_alert(): # Get request data body = request.form.get('body-plain') headers = request.form.get('message-headers') subject = request.form.get('subject') sender = request.form.get('sender') recipient = request.form.get('recipient') # Configure logging logging.basicConfig(filename=app.config['LOG_FILE'], filemode='a', format='%(asctime)s - mailgun2thehive - %(levelname)s - %(message)s', level=logging.INFO) logging.info(json.dumps(subject)) # Configure API api = TheHiveApi(app.config['HIVE_URL'], app.config['API_KEY']) # Configure artifacts artifacts = [] # Get attachments, if any for key in request.files: file = request.files[key] logging.info(key) file.save(key) artifacts.append(AlertArtifact(dataType='file', tags=[key], data=key)) # Tags list tags=['mailgun'] # Prepare alert sourceRef = str(uuid.uuid4())[0:6] alert = Alert(title="Mailgun - " + sender + " - " + subject, tlp=2, tags=tags, description="**Email body:**\n\n"+body+"\n\n**Message Headers:**\n\n"+headers, type='external', source='mailgun', artifacts=artifacts, sourceRef=sourceRef) # Create the alert print('Create Alert') print('-----------------------------') id = None response = api.create_alert(alert) if response.status_code == 201: logging.info(json.dumps(response.json(), indent=4, sort_keys=True)) print(json.dumps(response.json(), indent=4, sort_keys=True)) print('') id = response.json()['id'] else: print('ko: {}/{}'.format(response.status_code, response.text)) sys.exit(0) # Delete attachments, if any for key in request.files: os.remove(key) # Return return "Hive Alert Created"
def send_thehive_message(rule, message, asset, description): mess = "[Rule] Rule '{}' TheHive alert creation (asset={})".format( rule, asset) Event.objects.create(message=mess, type="CREATE", severity="DEBUG") AuditLog.objects.create(message=mess, scope='rule', type='rule_send_thehive', request_context=inspect.stack()) thehive_apikey = Setting.objects.get(key="alerts.endpoint.thehive.apikey") thehive_url = Setting.objects.get(key="alerts.endpoint.thehive.url") thehive_user = Setting.objects.get(key="alerts.endpoint.thehive.user") alert_message = "[Alert][Rule={}]{}".format(rule.title, message) api = TheHiveApi(thehive_url.value, thehive_apikey.value) sourceRef = str(uuid.uuid4())[0:6] # Severity 1 is the lower severity for TheHive rule_severity = 1 if rule.severity == "Medium": rule_severity = 2 elif rule.severity in ["High", "Critical"]: rule_severity = 3 tlp = 0 if asset.criticity == "low": tlp = 1 elif asset.criticity == "medium": tlp = 2 elif asset.criticity == "high": tlp = 3 if asset: artifacts = [THAlertArtifact(dataType=asset.type, data=asset.value)] try: alert = THAlert(title=alert_message, tlp=tlp, severity=rule_severity, tags=['src:PatrOwl'], description=description, type='external', source=thehive_user.value, sourceRef=sourceRef, artifacts=artifacts) response = api.create_alert(alert) if response.status_code == 201: alert_id = response.json()['id'] # todo: track theHive alerts Event.objects.create( message="[Rule][send_thehive_message()] " "Alert sent to TheHive with message '{}' (alert id: {})". format(message, alert_id), type="DEBUG", severity="DEBUG") else: return_value = "" if "errors" in json.loads(response.text): # Limit length to 40 characters return_value = json.loads( response.text)["errors"][0]["message"][:40] Event.objects.create( message="[Rule][send_thehive_message()] Unable to send " "alert to TheHive (status_code={}, return_value='{}')". format(response.status_code, return_value), type="ERROR", severity="ERROR") except Exception as e: Event.objects.create( message="[Rule][send_thehive_message()] Unable to send alert " "to TheHive with (error={}, message ='{}')".format(e, message), type="ERROR", severity="ERROR")
def submitTheHive(emailFilePath): ''' Create a new case in TheHive based on the email Return 'TRUE' is successfully processed otherwise 'FALSE' ''' global log mailName, mailType = os.path.splitext(emailFilePath) if mailType == ".msg": fromField, subjectField, observables, body, attachments = readMsg(emailFilePath) elif mailType == ".eml": fromField, subjectField, observables, body, attachments = readEml(emailFilePath) # Cleanup observables (remove duplicates) new_observables = [] for o in observables: if not {'type': o['type'], 'value': o['value'] } in new_observables: # Is the observable whitelisted? if isWhitelisted(o['value']): log.debug('Skipping whitelisted observable: %s' % o['value']) else: new_observables.append({ 'type': o['type'], 'value': o['value'] }) log.debug('Found observable %s: %s' % (o['type'], o['value'])) else: log.info('Ignoring duplicate observable: %s' % o['value']) log.info("Removed duplicate observables: %d -> %d" % (len(observables), len(new_observables))) observables = new_observables api = TheHiveApi(config['thehiveURL'], config['thehiveApiKey']) # Search for interesting keywords in subjectField: log.debug("Searching for %s in '%s'" % (config['alertKeywords'], subjectField)) if re.match(config['alertKeywords'], subjectField, flags=0): # # Add observables found in the mail body # artifacts = [] if config['thehiveObservables'] and len(observables) > 0: for o in observables: artifacts.append(AlertArtifact(dataType=o['type'], data=o['value'])) # # Prepare tags - add alert keywords found to the list of tags # tags = list(config['alertTags']) match = re.findall(config['alertKeywords'], subjectField) for m in match: tags.append(m) # # Prepare the alert # sourceRef = str(uuid.uuid4())[0:6] alert = Alert(title=subjectField.replace('[ALERT]', ''), tlp = int(config['alertTLP']), tags = tags, description = body, type = 'external', source = fromField, sourceRef = sourceRef, artifacts = artifacts) # Create the Alert id = None response = api.create_alert(alert) if response.status_code == 201: log.info('Created alert %s' % response.json()['sourceRef']) else: log.error('Cannot create alert: %s (%s)' % (response.status_code, response.text)) return False else: # Prepare the sample case tasks = [] for task in config['caseTasks']: tasks.append(CaseTask(title=task)) # Prepare the custom fields customFields = CustomFieldHelper()\ .add_string('from', fromField)\ .add_string('attachment', str(attachments))\ .build() # If a case template is specified, use it instead of the tasks if len(config['caseTemplate']) > 0: case = Case(title=subjectField, tlp = int(config['caseTLP']), flag = False, tags = config['caseTags'], description = body, template = config['caseTemplate'], customFields = customFields) else:
def submitTheHive(message): ''' Create a new case in TheHive based on the email Return 'TRUE' is successfully processed otherwise 'FALSE' ''' # Decode email msg = email.message_from_bytes(message) # gets full content of the email decode = email.header.decode_header(msg['From'])[0] fromField = str(decode[0]) decode = email.header.decode_header(msg['Subject'])[0] subjectField = str(decode[0]) if args.verbose: print("[INFO] From: %s Subject: %s" % (fromField, subjectField)) attachments = [] observables = [] body = '' bodyMessage = '' for part in msg.walk(): if part.get_content_type() == "text/plain": body = part.get_payload(decode=True).decode() bodyMessage += body observables = searchObservables( body, observables ) # searches the body of the email for supplied observables elif part.get_content_type( ) == "text/html": # if email is html based will search throuh html source code if args.verbose: print("[INFO] Searching for observable in HTML code") html = part.get_payload(decode=True).decode() observables = searchObservables(html, observables) elif part.get_content_type( ) == "application/vnd.ms-excel": #ONLY WORKS FOR .CSV body = part.get_payload(decode=True).decode('UTF-8') observables = searchObservables(body, observables) else: # Extract MIME parts filename = part.get_filename() mimetype = part.get_content_type() if filename and mimetype: if mimetype in config['caseFiles'] or not config['caseFiles']: print("[INFO] Found attachment: %s (%s)" % (filename, mimetype)) # Decode the attachment and save it in a temporary file charset = part.get_content_charset() if charset is None: charset = chardet.detect(bytes(part))['encoding'] fd, path = tempfile.mkstemp(prefix=slugify(filename) + "_") try: with os.fdopen(fd, 'w+b') as tmp: tmp.write(part.get_payload(decode=1)) attachments.append(path) except OSError as e: print("[ERROR] Cannot dump attachment to %s: %s" % (path, e.errno)) return False api = TheHiveApi(config['thehiveURL'], config['thehiveUser'], config['thehivePassword'], { 'http': '', 'https': '' }) # if '[ALERT]' in subjectField: if re.match(config['alertKeywords'], subjectField, flags=0): # # Add observables found in the mail body # artifacts = [] if config['thehiveObservables'] and len(observables) > 0: print("t1") for o in observables: print("t2") artifacts.append( AlertArtifact(dataType=o['type'], data=o['value'])) # # Prepare tags - add alert keywords found to the list of tags # tags = config['alertTags'] match = re.findall(config['alertKeywords'], subjectField) for m in match: tags.append(m) # # Prepare the alert # sourceRef = str(uuid.uuid4())[0:6] alert = Alert( title=subjectField.replace('[ALERT]', ''), tlp=int( config['alertTLP'] ), #setting it blank since custom template allows default color, set it back to tlp = int for conf value tags=tags, description=body, type='external', source=fromField, sourceRef=sourceRef, artifacts=artifacts) # Create the Alert id = None response = api.create_alert(alert) if response.status_code == 201: if args.verbose: print('[INFO] Created alert %s' % response.json()['sourceRef']) else: print('[ERROR] Cannot create alert: %s (%s)' % (response.status_code, response.text)) return False else: # Prepare the sample case tasks = [] for task in config['caseTasks']: tasks.append(CaseTask(title=task)) # Prepare the custom fields customFields = CustomFieldHelper() \ .add_string('from', fromField) \ .add_string('attachment', str(attachments)) \ .build() # If a case template is specified, use it instead of the tasks m = 1 if m == 1: templates = [] for task in config['caseTemplates']: templates.append(task) temptouse = config['caseTemplate'] descrip = re.compile('-"(.+)"') name = re.compile('(.+)-"') for x in templates: z = descrip.search(x) tempVar = name.search(x) searchVar = z.group(1) tempVar = tempVar.group(1) if searchVar in subjectField: print( x ) #if 2 template names in subject, take the latest defined temptouse = tempVar print("TEMPLATE", temptouse) if body: testerVar = False print("body") try: albert = re.compile('Albert Incident #: (\d+)') m = albert.search(body) albertId = m.group(1) print(albertId) customFields = CustomFieldHelper() \ .add_string('from', fromField) \ .add_string('attachment', str(attachments)) \ .add_string('albertId', albertId) \ .build() print(customFields) except: print("albert id doesnt exist") if "Update" in subjectField: #update code testerVar = True print("UPDATE") #INTIAL try: findBodyInfo = re.compile('---((?:.+[\r\n]+)+)---') except: print( "Unable to update, unable to find two '---'s, exiting.." ) sys.exit(0) m = findBodyInfo.search(body) bigGroup = m.group(1) caseId = parseBody("Case Id", bigGroup) print("caseid", caseId) try: caseId = int(caseId) except: print("invalid case id") updateACase(caseId, bigGroup, fromField, attachments) id = None if testerVar == True: print("g") sys.exit(0) #end update code caseTags = [] for tag in config['caseTags']: descripFound = descrip.search(tag) nameFound = name.search(tag) descripFound = descripFound.group(1) nameFound = nameFound.group(1) if descripFound == 'always': caseTags.append(nameFound) elif descripFound in bodyMessage: caseTags.append(nameFound) try: # # Add observables found in the mail body # artifacts = [] if config['thehiveObservables'] and len(observables) > 0: for o in observables: artifacts.append( AlertArtifact(dataType=o['type'], data=o['value'])) # # Prepare tags - add alert keywords found to the list of tags # tags = config['alertTags'] match = re.findall(config['alertKeywords'], subjectField) for m in match: tags.append(m) # # Prepare the alert # sourceRef = str(uuid.uuid4())[0:6] alert = Alert( title=subjectField, tlp=int( config['alertTLP'] ), #setting it blank since custom template allows default color, set it back to tlp = int for conf value tags=caseTags, description=body, type='external', source=fromField, sourceRef=sourceRef, customFields=customFields, severity=None, artifacts=artifacts) except FileExistsError: print( "Error with creating alert, wrong template name or tags?") else: print("") # Create the alert response = api.create_alert(alert) print("Alert being created..") if response.status_code == 201: newID = response.json()['id'] if args.verbose: print('[INFO] Created alert %s' % response.json()['caseId']) if len(attachments) > 0: for path in attachments: observable = CaseObservable( dataType='file', data=[path], tlp=int(config['caseTLP']), ioc=False, tags=config['caseTags'], message='Found as email attachment') response = api.create_case_observable(newID, observable) if response.status_code == 201: if args.verbose: print('[INFO] Added observable %s to case ID %s' % (path, newID)) os.unlink(path) else: print('[WARNING] Cannot add observable: %s - %s (%s)' % (path, response.status_code, response.text)) # # Add observables found in the mail body # if config['thehiveObservables'] and len(observables) > 0: for o in observables: observable = CaseObservable( dataType=o['type'], data=o['value'], tlp=int(config['caseTLP']), ioc=False, tags=caseTags, #switched to custom tags message='Found in the email body') response = api.create_case_observable(newID, observable) if response.status_code == 201: if args.verbose: print( '[INFO] Added observable %s: %s to case ID %s' % (o['type'], o['value'], newID)) else: print( '[WARNING] Cannot add observable %s: %s - %s (%s)' % (o['type'], o['value'], response.status_code, response.text)) else: print('[ERROR] Cannot create case: %s (%s)' % (response.status_code, response.text)) return False return True
class HiveManagement: def __init__( self, config_file='C:\\automation-hunting\\the-hive\\conf\\thehive-provider.yaml' ): self.hive_url = None self.api_key = None self.alert_tags = None self.source = None self.alert_type = None self.case_tags = None self.ioc_tags = None if not self.get_config_data(config_file): raise Exception('Invalid Configuration File') self.api = TheHiveApi(self.hive_url, self.api_key) def get_config_data(self, yaml_file): with open(yaml_file, 'r') as ymlfile: cfg = yaml.load(ymlfile, Loader=yaml.FullLoader) valid = False if self.validate_cfg_yml(cfg): self.hive_url = cfg['hive']['hive_url'] self.api_key = cfg['hive']['api_key'] self.alert_tags = cfg['hive']['alert_tags'] self.source = cfg['hive']['source'] self.alert_type = cfg['hive']['alert_type'] self.case_tags = cfg['hive']['case_tags'] self.ioc_tags = cfg['hive']['ioc_tags'] valid = True return valid @staticmethod def validate_cfg_yml(cfg): if 'hive' not in cfg: print('Not main') return False else: if 'hive_url' not in cfg['hive'] or 'api_key' not in cfg['hive']: return False return True def create_alarm(self, title, source_ref=None, description='N/A', alert_type='external', source='LogRhythm', iocs=None, additional_fields=None, additional_tags=None, tlp=TLP.AMBER, pap=PAP.AMBER, severity=HiveSeverity.MEDIUM): if source_ref is None: source_ref = str(uuid.uuid4())[0:6] alert_tags = self.alert_tags.copy() if additional_tags is not None: for additional_tag in additional_tags: alert_tags.append(additional_tag) custom_fields_helper = CustomFieldHelper() if additional_fields is not None: for field in additional_fields: custom_fields_helper.add_string(field['name'], field['value']) custom_fields = custom_fields_helper.build() artifacts = list() if iocs is not None: for ioc in iocs: artifacts.append( AlertArtifact(dataType=ioc['type'].value, data=ioc['value'])) hive_alert = Alert(title=title, tlp=tlp.value, tags=alert_tags, description=description, type=alert_type, source=source, sourceRef=source_ref, pap=pap.value, artifacts=artifacts, customFields=custom_fields, severity=severity.value) response = self.api.create_alert(hive_alert) if response.status_code == 201: print('Alerta Creada Exitosamente') print(json.dumps(response.json(), indent=4, sort_keys=True)) else: print('Error') print(response.text) return response.json() def create_case(self, title, tasks=None, tlp=TLP.AMBER, pap=PAP.AMBER, severity=HiveSeverity.MEDIUM, additional_fields=None, additional_tags=None, flag=False, description='N/A'): case_tags = self.case_tags.copy() if additional_tags is not None: for additional_tag in additional_tags: case_tags.append(additional_tag) custom_fields_helper = CustomFieldHelper() if additional_fields is not None: for field in additional_fields: custom_fields_helper.add_string(field['name'], field['value']) custom_fields = custom_fields_helper.build() new_tasks = list() if tasks is not None: for task in tasks: new_tasks.append(CaseTask(title=task)) hive_case = Case(title=title, tlp=tlp.value, pap=pap.value, description=description, tags=case_tags, severity=severity.value, flag=flag, customFields=custom_fields, tasks=new_tasks) response = self.api.create_case(hive_case) if response.status_code == 201: print('Caso Creada Exitosamente') print(json.dumps(response.json(), indent=4, sort_keys=True)) else: print('Error') print(response.text) return response.json() def create_case_observable(self, data_type: HiveDataType, value: list, tlp=TLP.AMBER, ioc=True, additional_tags=None, description='LogRhythm IoC'): ioc_tags = self.ioc_tags.copy() if additional_tags is not None: for additional_tag in additional_tags: ioc_tags.append(additional_tag) hive_observable = CaseObservable(data_type=data_type.value, data=value, tlp=tlp.value, ioc=ioc, tags=ioc_tags, message=description) return hive_observable def add_observable_to_case(self, case_id, observable: CaseObservable): response = self.api.create_case_observable(case_id, observable) if response.status_code == 201: print('Observable successfully added to the case') print(json.dumps(response.json(), indent=4, sort_keys=True)) else: print('Error') print(response.text) def search_case(self, title=None, tlp: TLP = None, pap: PAP = None, severity: HiveSeverity = None, or_operator=False): if title is None and tlp is None and pap is None and severity is None: print('Can\'t search without a filter') return None operators = list() if title is not None: operators.append(String('title: ' + urllib.parse.quote(title))) if tlp is not None: operators.append(Gte('tlp', tlp.value)) if pap is not None: operators.append(Gte('pap', pap.value)) if severity is not None: operators.append(Gte('severity', severity.value)) if len(operators) == 1: query = operators[0] else: if or_operator: query = Or(operators) else: query = And(operators) response = self.api.find_cases(query=query, range='all', sort=[]) if response.status_code == 200: print('Busqueda correcta') print(json.dumps(response.json(), indent=4, sort_keys=True)) else: print('Error') print(response.text) return response.json() def promote_alert(self, alert_id): response = self.api.promote_alert_to_case(alert_id) if response.status_code == 201: print('Correct Promotion') print(json.dumps(response.json(), indent=4, sort_keys=True)) else: print('Error') print(response.text) return response.json()
class TheHiveRequests: def __init__(self, config, ReportConfig): #Define the main logger self.logger = logging.getLogger("QTHI") self.config = config #Load config for reports self.ReportConfig = ReportConfig #Timestamp self.current_time = int(round(time.time() * 1000)) #Assign The Hive API class self.thapi = TheHiveApi(self.config.get('url', None), self.config.get('key'), self.config.get('password', None), self.config.get('proxies'), self.config.get('verify')) #Allow stringed output when needed def __repr__(self, alert): return str(alert.__dict__) #Function to prepare an alert object that is required to perform Alert based action within The Hive def prepare_alert(self, content, alert_type): """ convert a QRadar alert into a TheHive alert :param content: QRadar Alert :type content: dict :return: Thehive alert :rtype: thehive4py.models Alerts """ #Defined tags that should be present in the alert case_tags = ["src:QRadar"] ''' CHANGE NOT DONE ''' ''' Added APR and RESPC alert type. Still needs a case template and argument adjustment (severity, source etc). Importing the Alert function arguments from config file Please do not forget the change on line 290''' if alert_type in ["lse", "apr", "respc"]: case_tags.append(alert_type) #Build alert as object defined in TheHive4Py models alert = Alert(title=content.get("title"), tlp=2, tags=case_tags, severity=int(content.get('severity', self.ReportConfig[alert_type]['severity'])), description=content.get('description'), type='{} report'.format(alert_type.upper()), source='QRadar', caseTemplate=self.ReportConfig[alert_type]['case_template'], sourceRef="qrpt-{}-{}".format(alert_type, str(self.current_time))) self.logger.info("Alert built for {}".format(self.ReportConfig[alert_type]['description'])) self.logger.debug(str(alert)) return alert #Function to check if the alert that has been created contains new/different data in comparison to the alert that is present def check_if_updated(self, current_a, new_a): for item in sorted(new_a): #Skip values that are not required for the compare if item is "date": continue #Artifacts require special attention if item is "artifacts": #If the array is of different size an update is required if not len(current_a[item]) == len(new_a[item]): self.logger.info("Length mismatch detected: old length:%s, new length: %s" % (len(current_a[item]),len(new_a[item]))) return True #loop through the newly created alert array to extract the artifacts and add them so a separate variable for i in range(len(new_a[item])): vars_current_artifacts = current_a[item][i] vars_new_artifacts = vars(new_a[item][i]) #For each artifact loop through the attributes to check for differences for attribute in vars_new_artifacts: if vars_current_artifacts[attribute] != vars_new_artifacts[attribute]: self.logger.debug("Change detected for %s, new value: %s" % (vars_current_artifacts[attribute],vars_new_artifacts[attribute])) self.logger.debug("old: %s, new: %s" % (vars_current_artifacts,vars_new_artifacts)) return True continue if item is "tags": #loop through the newly created alert array to extract the tags and add them so a separate variable for i in range(len(new_a[item])): i = int(i) vars_current_tags = current_a[item][i] vars_new_tags = new_a[item][i] #For each tag loop through the new attributes to check for missing tags. The chances are zero to none that a tag will be removed, so this check is skipped for tag in vars_new_tags: if not tag in vars_current_tags: self.logger.debug("Change detected for %s, new value: %s" % (vars_current_tags,str(tag))) return True continue #Match other items of the new alert to the current alert (string based) if str(current_a[item]) != str(new_a[item]): self.logger.debug("Change detected for %s, new value: %s" % (item,str(new_a[item]))) return True return False #Function to actually create a new alert in The Hive def create_th_alerts(self, alerts, alert_type): """ :param alerts: List of alerts :type alerts: list :return: create TH alert """ # CHANGE if alert_type in ["lse", "apr", "respc"]: #Loop through all alerts to be created for alert in alerts: self.logger.info("Creating alert {} for {} report".format(self.current_time, alert_type.upper())) response = self.thapi.create_alert(alert) #Create a default action for standard alerts else: self.logger.info("Creating default alert") alert = alerts response = self.thapi.create_alert(alert) #Check output to handle errors self.logger.debug('API TheHive - status code: {}'.format( response.status_code)) if response.status_code > 299: self.logger.error('API TheHive - raw error output: {}'.format( response.raw.read())) else: self.logger.debug('Posted alert %s' % self.__repr__(alert))
class TheHive(AppBase): """ An example of a Walkoff App. Inherit from the AppBase class to have Redis, logging, and console logging set up behind the scenes. """ __version__ = "1.0.0" app_name = "thehive" def __init__(self, redis, logger, console_logger=None): """ Each app should have this __init__ to set up Redis and logging. :param redis: :param logger: :param console_logger: """ super().__init__(redis, logger, console_logger) #async def run_analyzer(self, apikey, url, title_query): # self.thehive = TheHiveApi(url, apikey) # response = self.thehive.find_cases(query=String("title:'%s'" % title_query), range='all', sort=[]) # return response.text async def search_cases(self, apikey, url, title_query): self.thehive = TheHiveApi(url, apikey) response = self.thehive.find_cases(query=String("title:'%s'" % title_query), range='all', sort=[]) return response.text async def add_observable(self, apikey, url, case_id, data, datatype, tags): self.thehive = TheHiveApi(url, apikey) if tags: if ", " in tags: tags = tags.split(", ") elif "," in tags: tags = tags.split(",") else: tags = [] else: tags = [] item = thehive4py.models.CaseObservable( dataType=datatype, data=data, tlp=1, ioc=False, sighted=False, tags=["Shuffle"], message="Created by shuffle", ) return self.thehive.create_case_observable(case_id, item).text async def search_alerts(self, apikey, url, title_query, search_range="0-25"): self.thehive = TheHiveApi(url, apikey) # Could be "all" too if search_range == "": search_range = "0-25" response = self.thehive.find_alerts(query=String("title:'%s'" % title_query), range=search_range, sort=[]) return response.text async def create_case(self, apikey, url, title, description="", tlp=1, severity=1, tags=""): self.thehive = TheHiveApi(url, apikey) if tags: if ", " in tags: tags = tags.split(", ") elif "," in tags: tags = tags.split(",") else: tags = [] else: tags = [] # Wutface fix if not tlp: tlp = 1 if not severity: severity = 1 if isinstance(tlp, str): if not tlp.isdigit(): return "TLP needs to be a number from 0-2, not %s" % tlp tlp = int(tlp) if isinstance(severity, str): if not severity.isdigit(): return "Severity needs to be a number from 0-2, not %s" % tlp severity = int(severity) if tlp > 3 or tlp < 0: return "TLP needs to be a number from 0-3, not %d" % tlp if severity > 2 or severity < 0: return "Severity needs to be a number from 0-2, not %d" % tlp case = thehive4py.models.Case( title=title, tlp=tlp, severity=severity, tags=tags, description=description, ) try: ret = self.thehive.create_case(case) return ret.text except requests.exceptions.ConnectionError as e: return "ConnectionError: %s" % e async def create_alert(self, apikey, url, type, source, sourceref, title, description="", tlp=1, severity=1, tags=""): self.thehive = TheHiveApi(url, apikey) if tags: if ", " in tags: tags = tags.split(", ") elif "," in tags: tags = tags.split(",") else: tags = [] else: tags = [] # Wutface fix if not tlp: tlp = 1 if not severity: severity = 1 if isinstance(tlp, str): if not tlp.isdigit(): return "TLP needs to be a number from 0-2, not %s" % tlp tlp = int(tlp) if isinstance(severity, str): if not severity.isdigit(): return "Severity needs to be a number from 0-2, not %s" % tlp severity = int(severity) if tlp > 2 or tlp < 0: return "TLP needs to be a number from 0-2, not %d" % tlp if severity > 2 or severity < 0: return "Severity needs to be a number from 0-2, not %d" % tlp alert = thehive4py.models.Alert( title=title, tlp=tlp, severity=severity, tags=tags, description=description, type=type, source=source, sourceRef=sourceref, ) try: ret = self.thehive.create_alert(alert) return ret.text except requests.exceptions.ConnectionError as e: return "ConnectionError: %s" % e # Gets an item based on input. E.g. field_type = Alert async def get_item(self, apikey, url, field_type, cur_id): self.thehive = TheHiveApi(url, apikey) newstr = "" ret = "" if field_type.lower() == "alert": ret = self.thehive.get_alert(cur_id + "?similarity=1") elif field_type.lower() == "case": ret = self.thehive.get_case(cur_id) elif field_type.lower() == "case_observables": ret = self.thehive.get_case_observables(cur_id) elif field_type.lower() == "case_task": ret = self.thehive.get_case_task(cur_id) elif field_type.lower() == "case_tasks": ret = self.thehive.get_case_tasks(cur_id) elif field_type.lower() == "case_template": ret = self.thehive.get_case_tasks(cur_id) elif field_type.lower() == "linked_cases": ret = self.thehive.get_linked_cases(cur_id) elif field_type.lower() == "task_log": ret = self.thehive.get_task_log(cur_id) elif field_type.lower() == "task_logs": ret = self.thehive.get_task_logs(cur_id) else: return "%s is not implemented. See https://github.com/frikky/shuffle-apps for more info." % field_type return ret.text async def close_alert(self, apikey, url, alert_id): self.thehive = TheHiveApi(url, apikey) return self.thehive.mark_alert_as_read(alert_id).text async def reopen_alert(self, apikey, url, alert_id): self.thehive = TheHiveApi(url, apikey) return self.thehive.mark_alert_as_unread(alert_id).text async def create_case_from_alert(self, apikey, url, alert_id, case_template=None): self.thehive = TheHiveApi(url, apikey) response = self.thehive.promote_alert_to_case(alert_id=alert_id, case_template=case_template) return response.text async def merge_alert_into_case(self, apikey, url, alert_id, case_id): self.thehive = TheHiveApi(url, apikey) req = url + f"/api/alert/{alert_id}/merge/{case_id}" ret = requests.post(req, auth=self.thehive.auth) return ret.text # Not sure what the data should be async def update_field(self, apikey, url, field_type, cur_id, field, data): # This is kinda silly but.. if field_type.lower() == "alert": newdata = {} if data.startswith("%s"): ticket = self.thehive.get_alert(cur_id) if ticket.status_code != 200: pass newdata[field] = "%s%s" % (ticket.json()[field], data[2:]) else: newdata[field] = data # Bleh url = "%s/api/alert/%s" % (url, cur_id) if field == "status": if data == "New" or data == "Updated": url = "%s/markAsUnread" % url elif data == "Ignored": url = "%s/markAsRead" % url ret = requests.post( url, headers={ 'Content-Type': 'application/json', 'Authorization': 'Bearer %s' % apikey } ) else: ret = requests.patch( url, headers={ 'Content-Type': 'application/json', 'Authorization': 'Bearer %s' % apikey }, json=newdata, ) return str(ret.status_code) else: return "%s is not implemented. See https://github.com/frikky/walkoff-integrations for more info." % field_type # https://github.com/TheHive-Project/TheHiveDocs/tree/master/api/connectors/cortex async def run_analyzer(self, apikey, url, cortex_id, analyzer_id, artifact_id): self.thehive = TheHiveApi(url, apikey) return self.thehive.run_analyzer(cortex_id, artifact_id, analyzer_id).text
def submitTheHive(message): ''' Create a new case in TheHive based on the email Return 'TRUE' is successfully processed otherwise 'FALSE' ''' global log # Decode email msg = email.message_from_bytes(message) decode = email.header.decode_header(msg['From'])[0] if decode[1] is not None: fromField = decode[0].decode(decode[1]) else: fromField = str(decode[0]) decode = email.header.decode_header(msg['Subject'])[0] if decode[1] is not None: subjectField = decode[0].decode(decode[1]) else: subjectField = str(decode[0]) log.info("From: %s Subject: %s" % (fromField, subjectField)) attachments = [] observables = [] # Extract SMTP headers and search for observables parser = HeaderParser() headers = parser.parsestr(msg.as_string()) headers_string = '' i = 0 while i < len(headers.keys()): headers_string = headers_string + headers.keys()[i] + ': ' + headers.values()[i] + '\n' i+=1 # Temporary disabled # observables = searchObservables(headers_string, observables) body = '' for part in msg.walk(): if part.get_content_type() == "text/plain": try: body = part.get_payload(decode=True).decode() except UnicodeDecodeError: body = part.get_payload(decode=True).decode('ISO-8859-1') observables.extend(searchObservables(body, observables)) elif part.get_content_type() == "text/html": try: html = part.get_payload(decode=True).decode() except UnicodeDecodeError: html = part.get_payload(decode=True).decode('ISO-8859-1') observables.extend(searchObservables(html, observables)) else: # Extract MIME parts filename = part.get_filename() mimetype = part.get_content_type() if filename and mimetype: if mimetype in config['caseFiles'] or not config['caseFiles']: log.info("Found attachment: %s (%s)" % (filename, mimetype)) # Decode the attachment and save it in a temporary file charset = part.get_content_charset() if charset is None: charset = chardet.detect(bytes(part))['encoding'] # Get filename extension to not break TheHive analysers (see Github #11) fname, fextension = os.path.splitext(filename) fd, path = tempfile.mkstemp(prefix=slugify(fname) + "_", suffix=fextension) try: with os.fdopen(fd, 'w+b') as tmp: tmp.write(part.get_payload(decode=1)) attachments.append(path) except OSerror as e: log.error("Cannot dump attachment to %s: %s" % (path,e.errno)) return False # Cleanup observables (remove duplicates) new_observables = [] for o in observables: if not {'type': o['type'], 'value': o['value'] } in new_observables: # Is the observable whitelisted? if isWhitelisted(o['value']): log.debug('Skipping whitelisted observable: %s' % o['value']) else: new_observables.append({ 'type': o['type'], 'value': o['value'] }) log.debug('Found observable %s: %s' % (o['type'], o['value'])) else: log.info('Ignoring duplicate observable: %s' % o['value']) log.info("Removed duplicate observables: %d -> %d" % (len(observables), len(new_observables))) observables = new_observables api = TheHiveApi(config['thehiveURL'], config['thehiveUser'], config['thehivePassword'], {'http': '', 'https': ''}) # Search for interesting keywords in subjectField: log.debug("Searching for %s in '%s'" % (config['alertKeywords'], subjectField)) if re.match(config['alertKeywords'], subjectField, flags=0): # # Add observables found in the mail body # artifacts = [] if config['thehiveObservables'] and len(observables) > 0: for o in observables: artifacts.append(AlertArtifact(dataType=o['type'], data=o['value'])) # # Prepare tags - add alert keywords found to the list of tags # tags = list(config['alertTags']) match = re.findall(config['alertKeywords'], subjectField) for m in match: tags.append(m) # # Prepare the alert # sourceRef = str(uuid.uuid4())[0:6] alert = Alert(title=subjectField.replace('[ALERT]', ''), tlp = int(config['alertTLP']), tags = tags, description = body, type = 'external', source = fromField, sourceRef = sourceRef, artifacts = artifacts) # Create the Alert id = None response = api.create_alert(alert) if response.status_code == 201: log.info('Created alert %s' % response.json()['sourceRef']) else: log.error('Cannot create alert: %s (%s)' % (response.status_code, response.text)) return False else: # Prepare the sample case tasks = [] for task in config['caseTasks']: tasks.append(CaseTask(title=task)) # Prepare the custom fields customFields = CustomFieldHelper()\ .add_string('from', fromField)\ .add_string('attachment', str(attachments))\ .build() # If a case template is specified, use it instead of the tasks if len(config['caseTemplate']) > 0: case = Case(title=subjectField, tlp = int(config['caseTLP']), flag = False, tags = config['caseTags'], description = body, template = config['caseTemplate'], customFields = customFields) else: case = Case(title = subjectField, tlp = int(config['caseTLP']), flag = False, tags = config['caseTags'], description = body, tasks = tasks, customFields = customFields) # Create the case id = None response = api.create_case(case) if response.status_code == 201: newID = response.json()['id'] log.info('Created case %s' % response.json()['caseId']) if len(attachments) > 0: for path in attachments: observable = CaseObservable(dataType='file', data = [path], tlp = int(config['caseTLP']), ioc = False, tags = config['caseTags'], message = 'Found as email attachment' ) response = api.create_case_observable(newID, observable) if response.status_code == 201: log.info('Added observable %s to case ID %s' % (path, newID)) os.unlink(path) else: log.warning('Cannot add observable: %s - %s (%s)' % (path, response.status_code, response.text)) # # Add observables found in the mail body # if config['thehiveObservables'] and len(observables) > 0: for o in observables: observable = CaseObservable( dataType = o['type'], data = o['value'], tlp = int(config['caseTLP']), ioc = False, tags = config['caseTags'], message = 'Found in the email body' ) response = api.create_case_observable(newID, observable) if response.status_code == 201: log.info('Added observable %s: %s to case ID %s' % (o['type'], o['value'], newID)) else: log.warning('Cannot add observable %s: %s - %s (%s)' % (o['type'], o['value'], response.status_code, response.text)) else: log.error('Cannot create case: %s (%s)' % (response.status_code, response.text)) return False return True
def create_alert(): # Get request data content = request.get_json() # Configure logging logging.basicConfig(filename=app.config['LOG_FILE'], filemode='a', format='%(asctime)s - canaries2thehive - %(levelname)s - %(message)s', level=logging.INFO) logging.info(json.dumps(content)) # Configure artifacts artifacts = [] artifacts.append(AlertArtifact(dataType='ip', tags=[], data=content['CanaryIP'])) artifacts.append(AlertArtifact(dataType='ip', tags=[], data=content['SourceIP'])) artifacts.append(AlertArtifact(dataType='fqdn', tags=[], data=content['CanaryName'])) # Tags list tags = ['canary'] # Parse fields and create description description = "" alert_flattened = flatten_dict(content) for key in alert_flattened.keys(): if (alert_flattened[key] != ""): description = description+"\n**"+key+":** "+json.dumps(alert_flattened[key])+"\n" # Prepare alert sourceRef = str(uuid.uuid4())[0:6] alert = Alert(title="Canary Triggered - %s by %s on %s (%s)" % (content["Description"], content["SourceIP"], content["CanaryName"], content["CanaryIP"]), tlp=2, tags=tags, description=description, type='external', source='canary', artifacts=artifacts, sourceRef=sourceRef) # Get API keys with open(app.root_path + '/keys.json', 'r') as file: keys_file = file.read() keys = json.loads(keys_file) # Loop through organisations in TheHive for org, key in keys.items(): # Configure API logging.info(app.config['HIVE_URL']) api = TheHiveApi(app.config['HIVE_URL'], key) # Create the alert logging.info('Create Alert') logging.info('-----------------------------') id = None response = api.create_alert(alert) if response.status_code == 201: logging.info(json.dumps(response.json(), indent=4, sort_keys=True)) id = response.json()['id'] else: logging.info('ko: {}/{}'.format(response.status_code, response.text)) sys.exit(0) # Return return "Hive Alerts Created"
# Prepare the sample Alert sourceRef = str(uuid.uuid4())[0:6] alert = Alert(title='<CUSTOMIZE THE TITLE>', tlp=2, tags=['<add tag here>'], description='<add some description>', type='notification', source='Email Server', sourceRef=sourceRef, artifacts=artifacts) # Create the Alert print('Create Alert') print('-----------------------------') id = None response = api.create_alert(alert) if response.status_code == 201: print(json.dumps(response.json(), indent=4, sort_keys=True)) print('') id = response.json()['id'] else: print('ko: {}/{}'.format(response.status_code, response.text)) sys.exit(0) # Get all the details of the created alert print('Get created alert {}'.format(id)) print('-----------------------------') response = api.get_alert(id) if response.status_code == requests.codes.ok: print(json.dumps(response.json(), indent=4, sort_keys=True))
class TheHive(AppBase): """ An example of a Walkoff App. Inherit from the AppBase class to have Redis, logging, and console logging set up behind the scenes. """ __version__ = "1.1.0" app_name = "thehive" def __init__(self, redis, logger, console_logger=None): """ Each app should have this __init__ to set up Redis and logging. :param redis: :param logger: :param console_logger: """ super().__init__(redis, logger, console_logger) # async def run_analyzer(self, apikey, url, title_query): # self.thehive = TheHiveApi(url, apikey, cert=False) # response = self.thehive.find_cases(query=String("title:'%s'" % title_query), range='all', sort=[]) # return response.text def __connect_thehive(self, url, apikey, organisation): if organisation: self.thehive = TheHiveApi(url, apikey, cert=False, organisation=organisation) else: self.thehive = TheHiveApi(url, apikey, cert=False) async def search_case_title(self, apikey, url, organisation, title_query): self.__connect_thehive(url, apikey, organisation) response = self.thehive.find_cases(query=ContainsString( "title", title_query), range="all", sort=[]) return response.text async def custom_search(self, apikey, url, organisation, search_for, custom_query, range="all"): self.__connect_thehive(url, apikey, organisation) try: custom_query = json.loads(custom_query) except: # raise IOError("Invalid JSON payload received.") pass if search_for == "alert": response = self.thehive.find_alerts(query=custom_query, range="all", sort=[]) else: response = self.thehive.find_cases(query=custom_query, range="all", sort=[]) if (response.status_code == 200 or response.status_code == 201 or response.status_code == 202): return response.text else: raise IOError(response.text) async def add_case_artifact( self, apikey, url, organisation, case_id, data, datatype, tags=None, tlp=None, ioc=None, sighted=None, description="", ): self.__connect_thehive(url, apikey, organisation) tlp = int(tlp) if tlp else 2 ioc = True if ioc.lower() == "true" else False sighted = True if sighted.lower() == "true" else False if not description: description = "Created by shuffle" tags = (tags.split(", ") if ", " in tags else tags.split(",") if "," in tags else []) item = thehive4py.models.CaseObservable( dataType=datatype, data=data, tlp=tlp, ioc=ioc, sighted=sighted, tags=tags, message=description, ) return self.thehive.create_case_observable(case_id, item).text async def search_alert_title(self, apikey, url, organisation, title_query, search_range="0-25"): self.__connect_thehive(url, apikey, organisation) # Could be "all" too if search_range == "": search_range = "0-25" response = self.thehive.find_alerts(query=ContainsString( "title", title_query), range=search_range, sort=[]) return response.text async def create_case( self, apikey, url, organisation, template, title, description="", tlp=1, severity=1, tags="", ): self.__connect_thehive(url, apikey, organisation) if tags: if ", " in tags: tags = tags.split(", ") elif "," in tags: tags = tags.split(",") else: tags = [tags] else: tags = [] # Wutface fix if not tlp: tlp = 1 if not severity: severity = 1 if isinstance(tlp, str): if not tlp.isdigit(): return "TLP needs to be a number from 0-2, not %s" % tlp tlp = int(tlp) if isinstance(severity, str): if not severity.isdigit(): return "Severity needs to be a number from 0-2, not %s" % tlp severity = int(severity) if tlp > 3 or tlp < 0: return "TLP needs to be a number from 0-3, not %d" % tlp if severity > 2 or severity < 0: return "Severity needs to be a number from 0-2, not %d" % tlp Casetemplate = template if template else None case = thehive4py.models.Case( title=title, tlp=tlp, severity=severity, tags=tags, description=description, template=Casetemplate, ) try: ret = self.thehive.create_case(case) return ret.text except requests.exceptions.ConnectionError as e: return "ConnectionError: %s" % e async def create_alert( self, apikey, url, organisation, type, source, sourceref, title, description="", tlp=1, severity=1, tags="", artifacts="", ): self.__connect_thehive(url, apikey, organisation) if tags: if ", " in tags: tags = tags.split(", ") elif "," in tags: tags = tags.split(",") else: tags = [tags] else: tags = [] # Wutface fix if not tlp: tlp = 1 if not severity: severity = 1 if isinstance(tlp, str): if not tlp.isdigit(): return "TLP needs to be a number from 0-3, not %s" % tlp tlp = int(tlp) if isinstance(severity, str): if not severity.isdigit(): return "Severity needs to be a number from 1-3, not %s" % severity severity = int(severity) if tlp > 3 or tlp < 0: return "TLP needs to be a number from 0-3, not %d" % tlp if severity > 3 or severity < 1: return "Severity needs to be a number from 1-3, not %d" % severity all_artifacts = [] if artifacts != "": # print("ARTIFACTS: %s" % artifacts) if isinstance(artifacts, str): # print("ITS A STRING!") try: artifacts = json.loads(artifacts) except: print("[ERROR] Error in parsing artifacts!") # print("ART HERE: %s" % artifacts) # print("ART: %s" % type(artifacts)) if isinstance(artifacts, list): print("ITS A LIST!") for item in artifacts: print("ITEM: %s" % item) try: artifact = thehive4py.models.AlertArtifact( dataType=item["data_type"], data=item["data"], ) try: artifact["message"] = item["message"] except: pass if item["data_type"] == "ip": try: if item["is_private_ip"]: message += " IP is private." except: pass all_artifacts.append(artifact) except KeyError as e: print("Error in artifacts: %s" % e) alert = thehive4py.models.Alert( title=title, tlp=tlp, severity=severity, tags=tags, description=description, type=type, source=source, sourceRef=sourceref, artifacts=all_artifacts, ) try: ret = self.thehive.create_alert(alert) return ret.text except requests.exceptions.ConnectionError as e: return "ConnectionError: %s" % e async def create_alert_artifact( self, apikey, url, organisation, alert_id, dataType, data, message=None, tlp="2", ioc="False", sighted="False", ignoreSimilarity="False", tags=None, ): self.__connect_thehive(url, apikey, organisation, version=4) if tlp: tlp = int(tlp) else: tlp = 2 ioc = ioc.lower().strip() == "true" sighted = sighted.lower().strip() == "true" ignoreSimilarity = ignoreSimilarity.lower().strip() == "true" if tags: tags = [x.strip() for x in tags.split(",")] else: tags = [] alert_artifact = thehive4py.models.AlertArtifact( dataType=dataType, data=data, message=message, tlp=tlp, ioc=ioc, sighted=sighted, ignoreSimilarity=ignoreSimilarity, tags=tags, ) try: ret = self.thehive.create_alert_artifact(alert_id, alert_artifact) except requests.exceptions.ConnectionError as e: return "ConnectionError: %s" % e if ret.status_code > 299: raise ConnectionError(ret.text) return ret.text # Gets an item based on input. E.g. field_type = Alert async def get_item(self, apikey, url, organisation, field_type, cur_id): self.__connect_thehive(url, apikey, organisation) newstr = "" ret = "" if field_type.lower() == "alert": ret = self.thehive.get_alert(cur_id + "?similarity=1") elif field_type.lower() == "case": ret = self.thehive.get_case(cur_id) elif field_type.lower() == "case_observables": ret = self.thehive.get_case_observables(cur_id) elif field_type.lower() == "case_task": ret = self.thehive.get_case_task(cur_id) elif field_type.lower() == "case_tasks": ret = self.thehive.get_case_tasks(cur_id) elif field_type.lower() == "case_template": ret = self.thehive.get_case_tasks(cur_id) elif field_type.lower() == "linked_cases": ret = self.thehive.get_linked_cases(cur_id) elif field_type.lower() == "task_log": ret = self.thehive.get_task_log(cur_id) elif field_type.lower() == "task_logs": ret = self.thehive.get_task_logs(cur_id) else: return ( "%s is not implemented. See https://github.com/frikky/shuffle-apps for more info." % field_type) return ret.text async def close_alert(self, apikey, url, organisation, alert_id): self.__connect_thehive(url, apikey, organisation) return self.thehive.mark_alert_as_read(alert_id).text async def reopen_alert(self, apikey, url, organisation, alert_id): self.__connect_thehive(url, apikey, organisation) return self.thehive.mark_alert_as_unread(alert_id).text async def create_case_from_alert(self, apikey, url, organisation, alert_id, case_template=None): self.__connect_thehive(url, apikey, organisation) response = self.thehive.promote_alert_to_case( alert_id=alert_id, case_template=case_template) return response.text async def merge_alert_into_case(self, apikey, url, organisation, alert_id, case_id): self.__connect_thehive(url, apikey, organisation) req = url + f"/api/alert/{alert_id}/merge/{case_id}" ret = requests.post(req, auth=self.thehive.auth) return ret.text # Not sure what the data should be async def update_field(self, apikey, url, organisation, field_type, cur_id, field, data): # This is kinda silly but.. if field_type.lower() == "alert": newdata = {} if data.startswith("%s"): ticket = self.thehive.get_alert(cur_id) if ticket.status_code != 200: pass newdata[field] = "%s%s" % (ticket.json()[field], data[2:]) else: newdata[field] = data # Bleh url = "%s/api/alert/%s" % (url, cur_id) if field == "status": if data == "New" or data == "Updated": url = "%s/markAsUnread" % url elif data == "Ignored": url = "%s/markAsRead" % url ret = requests.post( url, headers={ "Content-Type": "application/json", "Authorization": "Bearer %s" % apikey, }, ) else: ret = requests.patch( url, headers={ "Content-Type": "application/json", "Authorization": "Bearer %s" % apikey, }, json=newdata, ) return str(ret.status_code) else: return ( "%s is not implemented. See https://github.com/frikky/walkoff-integrations for more info." % field_type) # https://github.com/TheHive-Project/TheHiveDocs/tree/master/api/connectors/cortex async def delete_alert_artifact(self, apikey, url, organisation, artifact_id): self.__connect_thehive(url, apikey, organisation, version=4) return self.thehive.delete_alert_artifact(artifact_id).text # https://github.com/TheHive-Project/TheHiveDocs/tree/master/api/connectors/cortex async def run_analyzer(self, apikey, url, organisation, cortex_id, analyzer_id, artifact_id): self.__connect_thehive(url, apikey, organisation) return self.thehive.run_analyzer(cortex_id, artifact_id, analyzer_id).text # Creates a task log in TheHive with file async def create_task_log(self, apikey, url, organisation, task_id, message, filedata={}): if filedata["success"] == False: return "No file to upload. Skipping message." headers = { "Authorization": "Bearer %s" % apikey, } files = {} if len(filedata["data"]) > 0: files = { "attachment": (filedata["filename"], filedata["data"]), } data = {"_json": """{"message": "%s"}""" % message} response = requests.post( "%s/api/case/task/%s/log" % (url, task_id), headers=headers, files=files, data=data, ) return response.text # Creates an observable as a file in a case async def create_case_file_observable(self, apikey, url, organisation, case_id, tags, filedata): if filedata["success"] == False: return "No file to upload. Skipping message." headers = { "Authorization": "Bearer %s" % apikey, } if tags: if ", " in tags: tags = tags.split(", ") elif "," in tags: tags = tags.split(",") else: tags = [tags] files = {} if len(filedata["data"]) > 0: files = { "attachment": (filedata["filename"], filedata["data"]), } outerarray = {"dataType": "file", "tags": tags} data = {"_json": """%s""" % json.dumps(outerarray)} response = requests.post( "%s/api/case/%s/artifact" % (url, case_id), headers=headers, files=files, data=data, verify=False, ) return response.text # Get all artifacts of a given case async def get_case_artifacts( self, apikey, url, organisation, case_id, dataType, ): self.__connect_thehive(url, apikey, organisation) query = And(Eq("dataType", dataType)) if dataType else {} # Call the API response = self.thehive.get_case_observables( case_id, query=query, sort=["-startDate", "+ioc"], range="all") # Display the result if response.status_code == 200: # Get response data list = response.json() # Display response data return (json.dumps(list, indent=4, sort_keys=True) if list else json.dumps( { "status": 200, "message": "No observable results" }, indent=4, sort_keys=True, )) else: return f"Failure: {response.status_code}/{response.text}" async def close_case( self, apikey, url, organisation, id, resolution_status="", impact_status="", summary="", ): self.__connect_thehive(url, apikey, organisation) case = self.thehive.case(id) case.status = "Resolved" case.summary = summary case.resolutionStatus = resolution_status case.impactStatus = impact_status result = self.thehive.update_case( case, fields=[ "status", "summary", "resolutionStatus", "impactStatus", ], ) return json.dumps(result.json(), indent=4, sort_keys=True) # Update TheHive Case async def update_case( self, apikey, url, organisation, id, title="", description="", severity=None, owner="", flag=None, tlp=None, pap=None, tags="", status="", custom_fields=None, custom_json=None, ): self.__connect_thehive(url, apikey, organisation) # Get current case data and update fields if new data exists case = self.thehive.get_case(id).json() print(case) case_title = title if title else case["title"] case_description = description if description else case["description"] case_severity = int(severity) if severity else case["severity"] case_owner = owner if owner else case["owner"] case_flag = ((False if flag.lower() == "false" else True) if flag else case["flag"]) case_tlp = int(tlp) if tlp else case["tlp"] case_pap = int(pap) if pap else case["pap"] case_tags = tags.split(",") if tags else case["tags"] case_tags = tags.split(",") if tags else case["tags"] case_status = status if status else case["status"] case_customFields = case["customFields"] # Prepare the customfields customfields = CustomFieldHelper() if case_customFields: for key, value in case_customFields.items(): if list(value)[0] == "integer": customfields.add_integer(key, list(value.items())[0][1]) elif list(value)[0] == "string": customfields.add_string(key, list(value.items())[0][1]) elif list(value)[0] == "boolean": customfields.add_boolean(key, list(value.items())[0][1]) elif list(value)[0] == "float": customfields.add_float(key, list(value.items())[0][1]) else: print( f'The value type "{value}" of the field {key} is not suported by the function.' ) custom_fields = json.loads(custom_fields) if custom_fields else {} for key, value in custom_fields.items(): if type(value) == int: customfields.add_integer(key, value) elif type(value) == str: customfields.add_string(key, value) elif type(value) == bool: customfields.add_boolean(key, value) elif type(value) == float: customfields.add_float(key, value) else: print( f'The value type "{value}" of the field {key} is not suported by the function.' ) customfields = customfields.build() custom_json = json.loads(custom_json) if custom_json else {} # Prepare the fields to be updated case = Case( id=id, title=case_title, description=case_description, severity=case_severity, owner=case_owner, flag=case_flag, tlp=case_tlp, pap=case_pap, tags=case_tags, status=case_status, customFields=customfields, json=custom_json, ) # resolutionStatus=case_resolutionStatus, result = self.thehive.update_case( case, fields=[ "title", "description", "severity", "owner", "flag", "tlp", "pap", "tags", "customFields", "status", ], ) return json.dumps(result.json(), indent=4, sort_keys=True) # Get TheHive Organisations async def get_organisations( self, apikey, url, organisation, ): headers = { "Authorization": f"Bearer {apikey}", "Content-Type": "application/json", } response = requests.get( f"{url}/api/organisation", headers=headers, verify=False, ) return response.text # Create TheHive Organisation async def create_organisation( self, apikey, url, organisation, name, description, ): headers = { "Authorization": f"Bearer {apikey}", "Content-Type": "application/json", } data = {"name": f"{name}", "description": f"{description}"} response = requests.post( f"{url}/api/organisation", headers=headers, json=data, verify=False, ) return response.text # Create User in TheHive async def create_user( self, apikey, url, organisation, login, name, profile, ): headers = { "Authorization": f"Bearer {apikey}", "Content-Type": "application/json", } data = { "login": f"{login}", "name": f"{name}", "profile": f"{profile}", "organisation": f"{organisation}", } response = requests.post( f"{url}/api/v1/user", headers=headers, json=data, verify=False, ) return response.text
def submitTheHive(message): '''Create a new case in TheHive based on the email''' # Decode email msg = email.message_from_bytes(message) decode = email.header.decode_header(msg['From'])[0] fromField = str(decode[0]) decode = email.header.decode_header(msg['Subject'])[0] subjectField = str(decode[0]) if args.verbose: print("[INFO] From: %s Subject: %s" % (fromField, subjectField)) attachments = [] body = '' for part in msg.walk(): if part.get_content_type() == "text/plain": body = part.get_payload(decode=True).decode() else: filename = part.get_filename() if filename: print("[INFO] Found attachment: %s" % filename) attachments.append(filename) api = TheHiveApi(config['thehiveURL'], config['thehiveUser'], config['thehivePassword'], { 'http': '', 'https': '' }) if '[ALERT]' in subjectField: # Prepare the alert sourceRef = str(uuid.uuid4())[0:6] alert = Alert(title=subjectField.replace('[ALERT]', ''), tlp=int(config['alertTLP']), tags=config['alertTags'], description=body, type='external', source=fromField, sourceRef=sourceRef) # Create the Alert id = None response = api.create_alert(alert) if response.status_code == 201: if args.verbose: print('[INFO] Created alert %s' % response.json()['sourceRef']) else: print('[ERROR] Cannot create alert: %s (%s)' % (response.status_code, response.text)) sys.exit(0) else: # Prepare the sample case tasks = [] for task in config['caseTasks']: tasks.append(CaseTask(title=task)) # Prepare the custom fields customFields = CustomFieldHelper()\ .add_string('from', fromField)\ .add_string('attachment', str(attachments))\ .build() case = Case(title=subjectField, tlp=int(config['caseTLP']), flag=False, tags=config['caseTags'], description=body, tasks=tasks, customFields=customFields) # Create the case id = None response = api.create_case(case) if response.status_code == 201: if args.verbose: print('[INFO] Created case %s' % response.json()['caseId']) else: print('[ERROR] Cannot create case: %s (%s)' % (response.status_code, response.text)) sys.exit(0) return