class Mail2MISP(): def __init__(self, misp_url, misp_key, verifycert, config, offline=False, urlsonly=False): self.offline = offline if not self.offline: self.misp = ExpandedPyMISP(misp_url, misp_key, verifycert, debug=config.debug) self.config = config self.urlsonly = urlsonly if not hasattr(self.config, 'enable_dns'): setattr(self.config, 'enable_dns', True) if self.urlsonly is False: setattr(self.config, 'enable_dns', False) self.debug = self.config.debug self.config_from_email_body = {} # Init Faup self.f = Faup() self.sightings_to_add = [] def load_email(self, pseudofile): self.pseudofile = pseudofile self.original_mail = message_from_bytes(self.pseudofile.getvalue(), policy=policy.default) self.subject = self.original_mail.get('Subject') try: self.sender = self.original_mail.get('From') except: self.sender = "<unknown sender>" # Remove words from subject for removeword in self.config.removelist: self.subject = re.sub(removeword, "", self.subject).strip() # Initialize the MISP event self.misp_event = MISPEvent() self.misp_event.info = f'{self.config.email_subject_prefix} - {self.subject}' self.misp_event.distribution = self.config.default_distribution self.misp_event.threat_level_id = self.config.default_threat_level self.misp_event.analysis = self.config.default_analysis def sighting(self, value, source): if self.offline: raise Exception('The script is running in offline mode, ') '''Add a sighting''' s = MISPSighting() s.from_dict(value=value, source=source) self.misp.add_sighting(s) def _find_inline_forward(self): '''Does the body contains a forwarded email?''' for identifier in self.config.forward_identifiers: if identifier in self.clean_email_body: self.clean_email_body, fw_email = self.clean_email_body.split( identifier) return self.forwarded_email( pseudofile=BytesIO(fw_email.encode())) def _find_attached_forward(self): forwarded_emails = [] for attachment in self.original_mail.iter_attachments(): attachment_content = attachment.get_content() # Search for email forwarded as attachment # I could have more than one, attaching everything. if isinstance(attachment_content, message.EmailMessage): forwarded_emails.append( self.forwarded_email( pseudofile=BytesIO(attachment_content.as_bytes()))) else: if isinstance(attachment_content, str): attachment_content = attachment_content.encode() filename = attachment.get_filename() if not filename: filename = 'missing_filename' if self.config_from_email_body.get( 'attachment' ) == self.config.m2m_benign_attachment_keyword: # Attach sane file self.misp_event.add_attribute( 'attachment', value=filename, data=BytesIO(attachment_content)) else: f_object, main_object, sections = make_binary_objects( pseudofile=BytesIO(attachment_content), filename=filename, standalone=False) self.misp_event.add_object(f_object) if main_object: self.misp_event.add_object(main_object) [ self.misp_event.add_object(section) for section in sections ] return forwarded_emails def email_from_spamtrap(self): '''The email comes from a spamtrap and should be attached as-is.''' raw_body = self.original_mail.get_body(preferencelist=('html', 'plain')) if raw_body: self.clean_email_body = html.unescape( raw_body.get_payload(decode=True).decode( 'utf8', 'surrogateescape')) else: self.clean_email_body = '' return self.forwarded_email(self.pseudofile) def forwarded_email(self, pseudofile: BytesIO): '''Extracts all possible indicators out of an email and create a MISP event out of it. * Gets all relevant Headers * Attach the body * Create MISP file objects (uses lief if possible) * Set all references ''' email_object = EMailObject(pseudofile=pseudofile, attach_original_mail=True, standalone=False) if email_object.attachments: # Create file objects for the attachments for attachment_name, attachment in email_object.attachments: if not attachment_name: attachment_name = 'NameMissing.txt' if self.config_from_email_body.get( 'attachment' ) == self.config.m2m_benign_attachment_keyword: a = self.misp_event.add_attribute('attachment', value=attachment_name, data=attachment) email_object.add_reference(a.uuid, 'related-to', 'Email attachment') else: f_object, main_object, sections = make_binary_objects( pseudofile=attachment, filename=attachment_name, standalone=False) if self.config.vt_key: try: vt_object = VTReportObject( self.config.vt_key, f_object.get_attributes_by_relation( 'sha256')[0].value, standalone=False) self.misp_event.add_object(vt_object) f_object.add_reference(vt_object.uuid, 'analysed-with') except InvalidMISPObject as e: print(e) pass self.misp_event.add_object(f_object) if main_object: self.misp_event.add_object(main_object) for section in sections: self.misp_event.add_object(section) email_object.add_reference(f_object.uuid, 'related-to', 'Email attachment') self.process_body_iocs(email_object) if self.config.spamtrap or self.config.attach_original_mail or self.config_from_email_body.get( 'attach_original_mail'): self.misp_event.add_object(email_object) return email_object def process_email_body(self): mail_as_bytes = self.original_mail.get_body( preferencelist=('html', 'plain')).get_payload(decode=True) if mail_as_bytes: self.clean_email_body = html.unescape( mail_as_bytes.decode('utf8', 'surrogateescape')) # Check if there are config lines in the body & convert them to a python dictionary: # <config.body_config_prefix>:<key>:<value> => {<key>: <value>} self.config_from_email_body = { k.strip(): v.strip() for k, v in re.findall( f'{self.config.body_config_prefix}:(.*):(.*)', self.clean_email_body) } if self.config_from_email_body: # ... remove the config lines from the body self.clean_email_body = re.sub( rf'^{self.config.body_config_prefix}.*\n?', '', html.unescape( self.original_mail.get_body( preferencelist=('html', 'plain')).get_payload( decode=True).decode('utf8', 'surrogateescape')), flags=re.MULTILINE) # Check if autopublish key is present and valid if self.config_from_email_body.get( 'm2mkey') == self.config.m2m_key: if self.config_from_email_body.get('distribution') is not None: self.misp_event.distribution = self.config_from_email_body.get( 'distribution') if self.config_from_email_body.get('threat_level') is not None: self.misp_event.threat_level_id = self.config_from_email_body.get( 'threat_level') if self.config_from_email_body.get('analysis') is not None: self.misp_event.analysis = self.config_from_email_body.get( 'analysis') if self.config_from_email_body.get('publish'): self.misp_event.publish() self._find_inline_forward() else: self.clean_email_body = '' self._find_attached_forward() def process_body_iocs(self, email_object=None): if email_object: body = html.unescape( email_object.email.get_body( preferencelist=('html', 'plain')).get_payload(decode=True).decode( 'utf8', 'surrogateescape')) else: body = self.clean_email_body # Cleanup body content # Depending on the source of the mail, there is some cleanup to do. Ignore lines in body of message for ignoreline in self.config.ignorelist: body = re.sub(rf'^{ignoreline}.*\n?', '', body, flags=re.MULTILINE) # Remove everything after the stopword from the body body = body.split(self.config.stopword, 1)[0] # Add tags to the event if keywords are found in the mail for tag in self.config.tlptags: for alternativetag in self.config.tlptags[tag]: if alternativetag in body.lower(): self.misp_event.add_tag(tag) # Prepare extraction of IOCs # Refang email data body = refang(body) # Extract and add hashes contains_hash = False for h in set(re.findall(hashmarker.MD5_REGEX, body)): contains_hash = True attribute = self.misp_event.add_attribute( 'md5', h, enforceWarninglist=self.config.enforcewarninglist) if email_object: email_object.add_reference(attribute.uuid, 'contains') if self.config.sighting: self.sightings_to_add.append((h, self.config.sighting_source)) for h in set(re.findall(hashmarker.SHA1_REGEX, body)): contains_hash = True attribute = self.misp_event.add_attribute( 'sha1', h, enforceWarninglist=self.config.enforcewarninglist) if email_object: email_object.add_reference(attribute.uuid, 'contains') if self.config.sighting: self.sightings_to_add.append((h, self.config.sighting_source)) for h in set(re.findall(hashmarker.SHA256_REGEX, body)): contains_hash = True attribute = self.misp_event.add_attribute( 'sha256', h, enforceWarninglist=self.config.enforcewarninglist) if email_object: email_object.add_reference(attribute.uuid, 'contains') if self.config.sighting: self.sightings_to_add.append((h, self.config.sighting_source)) if contains_hash: [ self.misp_event.add_tag(tag) for tag in self.config.hash_only_tags ] # # Extract network IOCs urllist = [] urllist += re.findall(urlmarker.WEB_URL_REGEX, body) urllist += re.findall(urlmarker.IP_REGEX, body) if self.debug: syslog.syslog(str(urllist)) hostname_processed = [] # Add IOCs and expanded information to MISP for entry in set(urllist): ids_flag = True self.f.decode(entry) domainname = self.f.get_domain() if domainname in self.config.excludelist: # Ignore the entry continue hostname = self.f.get_host() scheme = self.f.get_scheme() if scheme: scheme = scheme resource_path = self.f.get_resource_path() if resource_path: resource_path = resource_path if self.debug: syslog.syslog(domainname) if domainname in self.config.internallist and self.urlsonly is False: # Add link to internal reference unless in urlsonly mode attribute = self.misp_event.add_attribute( 'link', entry, category='Internal reference', to_ids=False, enforceWarninglist=False) if email_object: email_object.add_reference(attribute.uuid, 'contains') elif domainname in self.config.externallist or self.urlsonly is False: # External analysis attribute = self.misp_event.add_attribute( 'link', entry, category='External analysis', to_ids=False, enforceWarninglist=False) if email_object: email_object.add_reference(attribute.uuid, 'contains') elif domainname in self.config.externallist or self.urlsonly: # External analysis if self.urlsonly: comment = self.subject + " (from: " + self.sender + ")" else: comment = "" attribute = self.misp.add_attribute( self.urlsonly, { "type": 'link', "value": entry, "category": 'External analysis', "to_ids": False, "comment": comment }) for tag in self.config.tlptags: for alternativetag in self.config.tlptags[tag]: if alternativetag in self.subject.lower(): self.misp.tag(attribute["uuid"], tag) new_subject = comment.replace(alternativetag, '') self.misp.change_comment(attribute["uuid"], new_subject) else: # The URL is probably an indicator. comment = "" if (domainname in self.config.noidsflaglist) or ( hostname in self.config.noidsflaglist): ids_flag = False comment = "Known host (mostly for connectivity test or IP lookup)" if self.debug: syslog.syslog(str(entry)) if scheme: if is_ip(hostname): attribute = self.misp_event.add_attribute( 'url', entry, to_ids=False, enforceWarninglist=self.config.enforcewarninglist) if email_object: email_object.add_reference(attribute.uuid, 'contains') else: if resource_path: # URL has path, ignore warning list attribute = self.misp_event.add_attribute( 'url', entry, to_ids=ids_flag, enforceWarninglist=False, comment=comment) if email_object: email_object.add_reference( attribute.uuid, 'contains') else: # URL has no path attribute = self.misp_event.add_attribute( 'url', entry, to_ids=ids_flag, enforceWarninglist=self.config. enforcewarninglist, comment=comment) if email_object: email_object.add_reference( attribute.uuid, 'contains') if self.config.sighting: self.sightings_to_add.append( (entry, self.config.sighting_source)) if hostname in hostname_processed: # Hostname already processed. continue hostname_processed.append(hostname) if self.config.sighting: self.sightings_to_add.append( (hostname, self.config.sighting_source)) if self.debug: syslog.syslog(hostname) comment = '' port = self.f.get_port() if port: port = port comment = f'on port: {port}' if is_ip(hostname): attribute = self.misp_event.add_attribute( 'ip-dst', hostname, to_ids=ids_flag, enforceWarninglist=self.config.enforcewarninglist, comment=comment) if email_object: email_object.add_reference(attribute.uuid, 'contains') else: related_ips = [] if HAS_DNS and self.config.enable_dns: try: syslog.syslog(hostname) for rdata in dns.resolver.query(hostname, 'A'): if self.debug: syslog.syslog(str(rdata)) related_ips.append(rdata.to_text()) except Exception as e: if self.debug: syslog.syslog(str(e)) if related_ips: hip = MISPObject(name='ip-port') hip.add_attribute( 'hostname', value=hostname, to_ids=ids_flag, enforceWarninglist=self.config.enforcewarninglist, comment=comment) for ip in set(related_ips): hip.add_attribute('ip', type='ip-dst', value=ip, to_ids=False, enforceWarninglist=self.config. enforcewarninglist) self.misp_event.add_object(hip) if email_object: email_object.add_reference(hip.uuid, 'contains') else: if self.urlsonly is False: attribute = self.misp_event.add_attribute( 'hostname', value=hostname, to_ids=ids_flag, enforceWarninglist=self.config. enforcewarninglist, comment=comment) if email_object: email_object.add_reference(attribute.uuid, 'contains') def add_event(self): '''Add event on the remote MISP instance.''' # Add additional tags depending on others tags = [] for tag in [t.name for t in self.misp_event.tags]: if self.config.dependingtags.get(tag): tags += self.config.dependingtags.get(tag) # Add additional tags according to configuration for malware in self.config.malwaretags: if malware.lower() in self.subject.lower(): tags += self.config.malwaretags.get(malware) if tags: [self.misp_event.add_tag(tag) for tag in tags] has_tlp_tag = False for tag in [t.name for t in self.misp_event.tags]: if tag.lower().startswith('tlp'): has_tlp_tag = True if not has_tlp_tag: self.misp_event.add_tag(self.config.tlptag_default) if self.offline: return self.misp_event.to_json() event = self.misp.add_event(self.misp_event, pythonify=True) if self.config.sighting: for value, source in self.sightings_to_add: self.sighting(value, source) return event
class Output(cowrie.core.output.Output): """ MISP Upload Plugin for Cowrie. This Plugin creates a new event for unseen file uploads or adds sightings for previously seen files. The decision is done by searching for the SHA 256 sum in all matching attributes. """ @ignore_warnings def start(self): """ Start output plugin """ misp_url = CowrieConfig().get('output_misp', 'base_url') misp_key = CowrieConfig().get('output_misp', 'api_key') misp_verifycert = ("true" == CowrieConfig().get('output_misp', 'verify_cert').lower()) self.misp_api = PyMISP(url=misp_url, key=misp_key, ssl=misp_verifycert, debug=False) self.is_python2 = sys.version_info[0] < 3 self.debug = CowrieConfig().getboolean('output_misp', 'debug', fallback=False) self.publish = CowrieConfig().getboolean('output_misp', 'publish_event', fallback=False) def stop(self): """ Stop output plugin """ pass def write(self, entry): """ Push file download to MISP """ if entry['eventid'] == 'cowrie.session.file_download': file_sha_attrib = self.find_attribute("sha256", entry["shasum"]) if file_sha_attrib: # file is known, add sighting! if self.debug: log.msg("File known, add sighting") self.add_sighting(entry, file_sha_attrib) else: # file is unknown, new event with upload if self.debug: log.msg("File unknwon, add new event") self.create_new_event(entry) @ignore_warnings def find_attribute(self, attribute_type, searchterm): """ Returns a matching attribute or None if nothing was found. """ result = self.misp_api.search( controller="attributes", type_attribute=attribute_type, value=searchterm ) # legacy PyMISP returns the Attribute wrapped in a response if self.is_python2: result = result["response"] if result["Attribute"]: return result["Attribute"][0] else: return None @ignore_warnings def create_new_event(self, entry): if self.is_python2: self.misp_api.upload_sample( entry["shasum"], entry["outfile"], None, distribution=1, info="File uploaded to Cowrie ({})".format(entry["sensor"]), analysis=0, threat_level_id=2 ) else: attribute = MISPAttribute() attribute.type = "malware-sample" attribute.value = entry["shasum"] attribute.data = Path(entry["outfile"]) attribute.comment = "File uploaded to Cowrie ({})".format(entry["sensor"]) attribute.expand = "binary" event = MISPEvent() event.info = "File uploaded to Cowrie ({})".format(entry["sensor"]) event.attributes = [attribute] event.run_expansions() if self.publish: event.publish() result = self.misp_api.add_event(event) if self.debug: log.msg("Event creation result: \n%s" % result) @ignore_warnings def add_sighting(self, entry, attribute): if self.is_python2: self.misp_api.sighting( uuid=attribute["uuid"], source="{} (Cowrie)".format(entry["sensor"]) ) else: sighting = MISPSighting() sighting.source = "{} (Cowrie)".format(entry["sensor"]) self.misp_api.add_sighting(sighting, attribute)
class EDRMISP(): def __init__(self): self.misp = ExpandedPyMISP(misp_url, misp_key, misp_verify) self.tags = self.misp.tags() self.attributes = [] self.found = False if args.region == 'EU': self.edr = 'https://api.soc.eu-central-1.mcafee.com' elif args.region == 'US': self.edr = 'https://api.soc.mcafee.com' elif args.region == 'SY': self.edr = 'https://soc.ap-southeast-2.mcafee.com' self.verify = True self.session = requests.Session() user = args.user pw = args.password self.creds = (user, pw) def edr_auth(self): r = self.session.get(self.edr + '/identity/v1/login', auth=self.creds) res = r.json() if r.status_code == 200: token = res['AuthorizationToken'] self.headers = {'Authorization': 'Bearer {}'.format(token)} print('AUTHENTICATION: Successfully authenticated.') else: print('ERROR: Something went wrong during the authentication') sys.exit() def edr_search(self, hash): payload = { "projections": [{ "name": "HostInfo", "outputs": ["hostname", "ip_address"] }, { "name": "Files", "outputs": ["name", "md5", "status", "full_name"] }], "condition": { "or": [{ "and": [{ "name": "Files", "output": "md5", "op": "EQUALS", "value": str(hash) }] }] } } res = self.session.post(self.edr + '/active-response/api/v1/searches', headers=self.headers, json=payload) if res.status_code == 200: queryId = res.json()['id'] print('SEARCH: MVISION EDR search got started successfully') else: print('ERROR: Could not find the query ID.') sys.exit() return queryId def edr_status(self, queryId): status = False res = self.session.get( self.edr + '/active-response/api/v1/searches/{}/status'.format(str(queryId)), headers=self.headers) if res.status_code == 200: if res.json()['status'] == 'FINISHED': status = True else: print('STATUS: Search still in process. Status: {}'.format( res.json()['status'])) return status def edr_result(self, queryId, eventid, attr_id, attr_uuid): hostnames = [] res = self.session.get( self.edr + '/active-response/api/v1/searches/{}/results'.format(str(queryId)), headers=self.headers) if res.status_code == 200: if res.json()['items'] != []: total = res.json()['totalItems'] print('SUCCESS: Found {0} Host(s) with this hash.'.format( str(total))) for item in res.json()['items']: hostname = item['output']['HostInfo|hostname'] ip = item['output']['HostInfo|ip_address'] status = item['output']['Files|status'] full_name = item['output']['Files|full_name'] md5 = item['output']['Files|md5'] finding = 'Hostname: {0} | IP: {1} | Status: {2} | Location: {3} | MD5: {4}'\ .format(hostname, ip, status, full_name, md5) hostnames.append(hostname) self.found = True self.add_sighting(attr_id, hostname) self.add_attribute(eventid, finding) self.update_attribute(attr_id, hostnames, attr_uuid) else: print('SUCCESS: No System found containing files with hash.') else: print('ERROR: Something went wrong to retrieve the results.') sys.exit() def edr_run_search(self, eventid, hash, attr_id, attr_uuid): self.edr_auth() queryid = self.edr_search(hash) while self.edr_status(queryid) is False: time.sleep(10) self.edr_result(queryid, eventid, attr_id, attr_uuid) def add_attribute(self, eventid, finding): attr = {"value": finding, "type": "target-machine"} self.misp.add_attribute(eventid, attr) def update_attribute(self, attr_id, comment, attr_uuid): data = {"comment": str(comment)} self.misp.update_attribute(data, attr_id) self.misp.tag(attr_uuid, misp_ntag) def add_sighting(self, attr_id, hostname): sight = { "values": "MVISION EDR", "id": attr_id, "source": "Target: {0}".format(hostname) } self.misp.add_sighting(sight) def main(self): try: events = self.misp.search(tags=misp_tag) if events: for event in events: eventid = str(event['Event']['id']) for attributes in event['Event']['Attribute']: if attributes['type'] == 'md5': print( 'STATUS: Found MD5 {0} in Event {1}. Trying to lookup Endpoint with MVISION EDR.' .format(str(attributes['value']), eventid)) self.edr_run_search(eventid, attributes['value'], attributes['id'], attributes['uuid']) for objects in event['Event']['Object']: for attributes in objects['Attribute']: if attributes['type'] == 'md5': print( 'STATUS: Found MD5 {0} in Event {1}. Trying to lookup Endpoint with MVISION EDR.' .format(str(attributes['value']), eventid)) self.edr_run_search(eventid, attributes['value'], attributes['id'], attributes['uuid']) self.misp.untag(event['Event']['uuid'], misp_tag) if self.found is True: self.misp.tag(event['Event']['uuid'], misp_ntag) except Exception as error: exc_type, exc_obj, exc_tb = sys.exc_info() print( 'ERROR: Error in {location}.{funct_name}() - line {line_no} : {error}' .format(location=__name__, funct_name=sys._getframe().f_code.co_name, line_no=exc_tb.tb_lineno, error=str(error)))
class EDRMISP(): def __init__(self): self.config = DxlClientConfig.create_dxl_config_from_file(dxl_config) self.misp = ExpandedPyMISP(misp_url, misp_key, misp_verify) self.tags = self.misp.tags() self.attributes = [] self.found = False def add_attribute(self, eventid, finding): attr = { "value": finding, "type": "target-machine" } self.misp.add_attribute(eventid, attr) def update_attribute(self, attr_id, comment, attr_uuid): data = { "comment": str(comment) } self.misp.update_attribute(data, attr_id) self.misp.tag(attr_uuid, misp_ntag) def add_sighting(self, attr_id, hostname): sight = { "values": "MVISION EDR", "id": attr_id, "source": "Target: {0}".format(hostname) } self.misp.add_sighting(sight) def edr_search(self, eventid, hash, attr_id, attr_uuid): hostnames = [] with DxlClient(self.config) as client: client.connect() marclient = MarClient(client) results_context = \ marclient.search( projections=[{ "name": "HostInfo", "outputs": ["hostname", "ip_address"] }, { "name": "Files", "outputs": ["name", "md5", "status", "full_name"] }], conditions={ "or": [{ "and": [{ "name": "Files", "output": "md5", "op": "EQUALS", "value": hash }] }] } ) if results_context.has_results: results = results_context.get_results() total = results['totalItems'] print('SUCCESS: Found {0} Host(s) with hash {1}.'.format(str(total), hash)) for item in results['items']: hostname = item['output']['HostInfo|hostname'] ip = item['output']['HostInfo|ip_address'] status = item['output']['Files|status'] full_name = item['output']['Files|full_name'] md5 = item['output']['Files|md5'] finding = 'Hostname: {0} | IP: {1} | Status: {2} | Location: {3} | MD5: {4}'\ .format(hostname, ip, status, full_name, md5) hostnames.append(hostname) self.found = True self.add_sighting(attr_id, hostname) self.add_attribute(eventid, finding) self.update_attribute(attr_id, hostnames, attr_uuid) else: print('SUCCESS: No System found containing files with hash {0}'.format(hash)) def main(self): try: events = self.misp.search(tags=misp_tag) if events: for event in events: eventid = str(event['Event']['id']) for attributes in event['Event']['Attribute']: if attributes['type'] == 'md5': print('STATUS: Found MD5 {0} in Event {1}. Trying to lookup Endpoint with MVISION EDR.' .format(str(attributes['value']), eventid)) self.edr_search(eventid, attributes['value'], attributes['id'], attributes['uuid']) for objects in event['Event']['Object']: for attributes in objects['Attribute']: if attributes['type'] == 'md5': print('STATUS: Found MD5 {0} in Event {1}. Trying to lookup Endpoint with MVISION EDR.' .format(str(attributes['value']), eventid)) self.edr_search(eventid, attributes['value'], attributes['id'], attributes['uuid']) self.misp.untag(event['Event']['uuid'], misp_tag) if self.found is True: self.misp.tag(event['Event']['uuid'], misp_ntag) except Exception as error: exc_type, exc_obj, exc_tb = sys.exc_info() print('ERROR: Error in {location}.{funct_name}() - line {line_no} : {error}' .format(location=__name__, funct_name=sys._getframe().f_code.co_name, line_no=exc_tb.tb_lineno, error=str(error)))
class Output(cowrie.core.output.Output): """ MISP Upload Plugin for Cowrie. This Plugin creates a new event for unseen file uploads or adds sightings for previously seen files. The decision is done by searching for the SHA 256 sum in all matching attributes. """ @ignore_warnings def start(self): """ Start output plugin """ misp_url = CowrieConfig.get("output_misp", "base_url") misp_key = CowrieConfig.get("output_misp", "api_key") misp_verifycert = ("true" == CowrieConfig.get("output_misp", "verify_cert").lower()) self.misp_api = PyMISP(url=misp_url, key=misp_key, ssl=misp_verifycert, debug=False) self.debug = CowrieConfig.getboolean("output_misp", "debug", fallback=False) self.publish = CowrieConfig.getboolean("output_misp", "publish_event", fallback=False) def stop(self): """ Stop output plugin """ pass def write(self, entry): """ Push file download to MISP """ if entry["eventid"] == "cowrie.session.file_download": file_sha_attrib = self.find_attribute("sha256", entry["shasum"]) if file_sha_attrib: # file is known, add sighting! if self.debug: log.msg("File known, add sighting") self.add_sighting(entry, file_sha_attrib) else: # file is unknown, new event with upload if self.debug: log.msg("File unknwon, add new event") self.create_new_event(entry) @ignore_warnings def find_attribute(self, attribute_type, searchterm): """ Returns a matching attribute or None if nothing was found. """ result = self.misp_api.search(controller="attributes", type_attribute=attribute_type, value=searchterm) if result["Attribute"]: return result["Attribute"][0] else: return None @ignore_warnings def create_new_event(self, entry): attribute = MISPAttribute() attribute.type = "malware-sample" attribute.value = entry["shasum"] attribute.data = Path(entry["outfile"]) attribute.comment = "File uploaded to Cowrie ({})".format( entry["sensor"]) attribute.expand = "binary" event = MISPEvent() event.info = "File uploaded to Cowrie ({})".format(entry["sensor"]) event.attributes = [attribute] event.run_expansions() if self.publish: event.publish() result = self.misp_api.add_event(event) if self.debug: log.msg("Event creation result: \n%s" % result) @ignore_warnings def add_sighting(self, entry, attribute): sighting = MISPSighting() sighting.source = "{} (Cowrie)".format(entry["sensor"]) self.misp_api.add_sighting(sighting, attribute)
class MispHandler: def __init__(self, config, logger): self.logger = logger MISP_KEY = config['MISP_KEY'] MISP_URL = config['MISP_URL'] MISP_VERIFYCERT = config['MISP_VERIFYCERT'] self.config = config self.misp = ExpandedPyMISP(MISP_URL, MISP_KEY, MISP_VERIFYCERT) self.orgc = MISPOrganisation() self.orgc.name = config['MISP_ORG_NAME'] self.orgc.id = config['MISP_ORG_ID'] self.orgc.uuid = config['MISP_ORG_UUID'] self.tags = config['tags'] self.galaxy_synonyms = {} self.enabled_clusters = self.config['galaxies'] self.galaxy_tags = {} self._init_galaxies() def get_event_id(self, event): try: return event['Event']['id'] except KeyError: return event.id except TypeError: return event.id def get_attributes(self, event): try: attributes = event['Event']['Attribute'] except KeyError: attributes = event.attributes return attributes def add_sigthing(self, id): sighting = MISPSighting() self.misp.add_sighting(sighting, id) def get_event(self, malware_type, feed_tag): malware_tag = "malware:" + malware_type.lower() res = self.misp.search(tags=[feed_tag], controller='events', pythonify=True) for event in res: for tag in event.tags: if tag['name'].lower() == malware_tag.lower(): return event return None def _init_galaxies(self): i = 1 cont = True for cluster in self.enabled_clusters: self.galaxy_tags[cluster] = [] while cont: g = self.misp.get_galaxy(i) try: galaxy_cluster = g['Galaxy']['name'] except KeyError: cont = False continue if galaxy_cluster.lower() in self.enabled_clusters: elements = g['GalaxyCluster'] for element in elements: self.galaxy_tags[galaxy_cluster.lower()].append(element['tag_name']) for inner_element in element['GalaxyElement']: if inner_element['key'] == 'synonyms': if not element['tag_name'] in self.galaxy_synonyms: self.galaxy_synonyms[element['tag_name']] = [] self.galaxy_synonyms[element['tag_name']].append(inner_element['value']) i = i + 1 def get_galaxies(self, malware_tag): res = [] for cluster in self.enabled_clusters: for galaxy_tag in self.galaxy_tags[cluster]: malware = malware_tag.split(':')[1].lower().replace(" ", "") galaxy_value = galaxy_tag.split('"')[1].lower().replace(" ", "") if malware == galaxy_value: res.append(galaxy_tag) break else: if galaxy_tag in self.galaxy_synonyms: for synonym in self.galaxy_synonyms[galaxy_tag]: galaxy_value = synonym.lower().replace(" ", "") if malware == galaxy_value: res.append(galaxy_tag) break return res def get_file_taxonomy(self, ft): if ft == 'exe': return 'file-type:type="peexe"' elif ft == 'dll': return 'file-type:type="pedll"' elif ft == 'zip': return 'file-type:type="zip"' elif ft == 'apk': return 'file-type:type="android"' elif ft == 'rar': return 'file-type:type="rar"' elif ft == 'xls': return 'file-type:type="xls"' elif ft == 'xlsx': return 'file-type:type="xlsx"' elif ft == 'doc': return 'file-type:type="doc"' elif ft == 'docx': return 'file-type:type="docx"' elif ft == '7z' or ft == '7zip': return 'file-type:type="7zip"' elif ft == 'gz' or ft == 'gzip': return 'file-type:type="gzip"' file_types = self.misp.get_taxonomy(52)['entries'] for file_type in file_types: if ft == file_type['tag'].split('"')[1].strip(): return file_type['tag'] print("Unknown Filetype: " + ft) return '' def new_misp_event(self, malware_type, feed_tag, event_info, additional_tags=[], info_cred='admirality-scale:information-credibility="2"'): malware_tag = "malware:" + malware_type.lower() misp_event_obj = MISPEvent() misp_event_obj.info = event_info misp_event_obj.add_tag(feed_tag) misp_event_obj.add_tag(info_cred) if len(malware_tag) > 0: misp_event_obj.add_tag(malware_tag.lower()) for tag in self.config['tags']: misp_event_obj.add_tag(tag) for tag in additional_tags: misp_event_obj.add_tag(tag) misp_event_obj.orgc = self.orgc galaxies = self.get_galaxies(malware_tag) for galaxy in galaxies: misp_event_obj.add_tag(galaxy) misp_event = self.misp.add_event(misp_event_obj) return misp_event
class MISPApi: def __init__(self, api_url: str, api_key: str, verify_cert: bool = True): self.pymisp = ExpandedPyMISP(api_url, api_key, verify_cert) def search(self, controller: str = 'attributes', **kwargs): return self.pymisp.search(controller, **kwargs) def searchall(self, value: str, controller: str = 'attributes') -> List[Any]: result = self.pymisp.search(controller, value=value, searchall=True) assert isinstance(result, dict) # Please mypy return result.get('Attribute', []) def search_sightings(self, context_id: str, context: str = 'attribute', source: Optional[str] = None): return self.pymisp.search_sightings(context=context, context_id=context_id, source=source) def org_name_id_mapping(self): pass def attr_search(self, attr: Attr) -> List[Any]: result = [] for typ in attr.search_types: result += self.search(type=typ.value, value=attr.value).get('Attribute', []) return result def domain_name_search( self, domain_name: str, searchall: bool = False, publish_timestamp: Optional[datetime] = None, limit: Optional[int] = None, ) -> List[Any]: result = self.search(type='domain', value=domain_name, searchall=searchall, publish_timestamp=publish_timestamp, limit=limit) return result.get('Attribute', []) def url_search(self, url: str, searchall: bool = False) -> List[Any]: result = self.search(type='url', value=url, searchall=searchall) return result.get('Attribute', []) def sighting_lookup(self, attribute_id: str, source: Optional[str] = None) -> List[Any]: result = self.search_sightings(context_id=attribute_id, source=source) return [item['Sighting'] for item in result] def _get_report_category(self, attr: Attr) -> EventCategory: if any(True for t in attr.report_types if t in [AttrType.DOMAIN, AttrType.URL, AttrType.IP_SRC]): return EventCategory.NETWORK_ACTIVITY elif any(True for t in attr.report_types if t in [AttrType.MD5, AttrType.SHA1]): return EventCategory.PAYLOAD_DELIVERY else: raise NotImplementedError( f'EventCategory for {attr.report_types} not implemented') def add_event( self, attr_items: List[Attr], info: str, tags: List, comment: str, to_ids: bool, reference: Optional[str], ts: Optional[int] = None, published: Optional[bool] = False, ): attrs = [] for item in attr_items: category = self._get_report_category(item) for report_type in item.report_types: report_attr = MISPAttribute() report_attr.from_dict( type=report_type.value, category=category.value, to_ids=to_ids, value=item.value, comment=comment, timestamp=ts, ) attrs.append(report_attr) if reference: reference_attr = MISPAttribute() reference_attr.from_dict( type='text', category=EventCategory.INTERNAL_REFERENCE.value, value=reference, disable_correlation=True, comment=comment, timestamp=ts, ) attrs.append(reference_attr) event = MISPEvent() event.from_dict(info=info, Attribute=attrs, Tag=tags, date=date.today(), published=published, threat_level_id=2) logger.debug(event) return self.pymisp.add_event(event) def add_sighting(self, attr: Attr, sighting_type: str, source: str) -> MISPSighting: sighting = MISPSighting() sighting['value'] = attr.value sighting['type'] = sighting_type sighting['source'] = source res = self.pymisp.add_sighting(sighting, pythonify=True) assert isinstance(res, MISPSighting) # please mypy return res def remove_sighting( self, attr: Attr, sighting_type: str, source: str, date_from: Optional[datetime] = None, date_to: Optional[datetime] = None, ): sightings: List[MISPSighting] = [] for item in self.attr_search(attr): res = self.pymisp.search_sightings( context='attribute', context_id=item['id'], type_sighting=sighting_type, source=source, date_from=date_from, date_to=date_to, pythonify=True, ) # Can't get mypy to understand that sighting contains a MISPSighting sightings.extend([ d['sighting'] for d in res if d.get('sighting') is not None ]) # type: ignore # Please mypy for sighting in sightings: self.pymisp.delete_sighting(sighting)