From 8a95a000eefd49181b3f71946aa8a79ed80ddd55 Mon Sep 17 00:00:00 2001 From: Jesse Hedden Date: Fri, 29 May 2020 17:21:20 -0700 Subject: [PATCH 01/16] initial commit. not a working product. need to create a class to manage the MISP event and TruStar client --- REQUIREMENTS | 1 + misp_modules/modules/expansion/__init__.py | 4 +- .../modules/expansion/trustar_enrich.py | 63 +++++++++++++++++++ .../modules/import_mod/trustar_import.py | 0 4 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 misp_modules/modules/expansion/trustar_enrich.py create mode 100644 misp_modules/modules/import_mod/trustar_import.py diff --git a/REQUIREMENTS b/REQUIREMENTS index e749db9..73b002a 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -95,6 +95,7 @@ sparqlwrapper==1.8.5 stix2-patterns==1.3.0 tabulate==0.8.7 tornado==6.0.4 +trustar==0.3.28 url-normalize==1.4.1 urlarchiver==0.2 urllib3==1.25.8 diff --git a/misp_modules/modules/expansion/__init__.py b/misp_modules/modules/expansion/__init__.py index 82264fa..c05804b 100644 --- a/misp_modules/modules/expansion/__init__.py +++ b/misp_modules/modules/expansion/__init__.py @@ -1,6 +1,7 @@ from . import _vmray # noqa import os import sys + sys.path.append('{}/lib'.format('/'.join((os.path.realpath(__file__)).split('/')[:-3]))) __all__ = ['cuckoo_submit', 'vmray_submit', 'bgpranking', 'circl_passivedns', 'circl_passivessl', @@ -16,4 +17,5 @@ __all__ = ['cuckoo_submit', 'vmray_submit', 'bgpranking', 'circl_passivedns', 'c 'ods_enrich', 'odt_enrich', 'joesandbox_submit', 'joesandbox_query', 'urlhaus', 'virustotal_public', 'apiosintds', 'urlscan', 'securitytrails', 'apivoid', 'assemblyline_submit', 'assemblyline_query', 'ransomcoindb', - 'lastline_query', 'lastline_submit', 'sophoslabs_intelix', 'cytomic_orion', 'censys_enrich'] + 'lastline_query', 'lastline_submit', 'sophoslabs_intelix', 'cytomic_orion', 'censys_enrich', + 'trustar_enrich'] diff --git a/misp_modules/modules/expansion/trustar_enrich.py b/misp_modules/modules/expansion/trustar_enrich.py new file mode 100644 index 0000000..e786ff3 --- /dev/null +++ b/misp_modules/modules/expansion/trustar_enrich.py @@ -0,0 +1,63 @@ +import json +from pymisp import MISPAttribute, MISPEvent, MISPObject +from trustar import TruStar + +misperrors = {'error': "Error"} +mispattributes = {'input': ["btc", "domain","email-src", "filename", "hostname", "ip-src", "ip-dst", "malware-type", "md5", "sha1", "sha256", "url"], 'format': 'misp_standard'} + +moduleinfo = {'version': "0.1", 'author': "Jesse Hedden", + 'description': "Enrich data with TruSTAR", + 'module-type': ["hover", "expansion"]} + +moduleconfig = ["api_key", "api_secret", "enclave_ids"] + + +def get_results(misp_event): + event = json.loads(misp_event.to_json()) + results = {key: event[key] for key in ('Attribute', 'Object')} + return {'results': results} + +def parse_indicator_summary(attribute, summary): + misp_event = MISPEvent() + misp_attribute = MISPAttribute().from_dict(**attribute) + misp_event.add_attribute(**misp_attribute) + + mapping = {'value': 'text', 'reportId': 'text', 'enclaveId': 'text', 'description': 'text'} + + for item in summary.get('items'): + trustar_obj = MISPObject(attribute.value) + for key, attribute_type in mapping.items(): + trustar_obj.add_attribute(key, attribute_type=attribute_type, value=item[key]) + trustar_obj.add_reference(misp_attribute.uuid, 'associated-to') + misp_event.add_object(**trustar_obj) + + return misp_event + + +def handler(q=False): + + if q is False: + return False + + request = json.loads(q) + config = request.get('config', {}) + if not config.get('api_key') or not config.get('api_secret'): + misperrors['error'] = "Your TruSTAR API key and secret are required for indicator enrichment." + return misperrors + + enclave_ids = [enclave_id for enclave_id in config.get('enclave_ids', "").split(',')] + ts_client = TruStar(config={'user_api_key': config.get('api_key'), 'user_api_secret': config.get('api_secret'), 'enclave_ids': enclave_ids}) + attribute = request.get('attribute') + + summary = ts_client.get_indicator_summaries(attribute) + + misp_event = parse_indicator_summary(attribute, summary) + return get_results(misp_event) + +def introspection(): + return mispattributes + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo + diff --git a/misp_modules/modules/import_mod/trustar_import.py b/misp_modules/modules/import_mod/trustar_import.py new file mode 100644 index 0000000..e69de29 From 67bdb38fc8d1e36f6e3005478be2e6498be31fd9 Mon Sep 17 00:00:00 2001 From: Jesse Hedden Date: Fri, 29 May 2020 17:41:13 -0700 Subject: [PATCH 02/16] WIP: initial push --- misp_modules/modules/import_mod/__init__.py | 1 + misp_modules/modules/import_mod/trustar_import.py | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/misp_modules/modules/import_mod/__init__.py b/misp_modules/modules/import_mod/__init__.py index fbad911..45e3359 100644 --- a/misp_modules/modules/import_mod/__init__.py +++ b/misp_modules/modules/import_mod/__init__.py @@ -15,4 +15,5 @@ __all__ = [ 'threatanalyzer_import', 'csvimport', 'joe_import', + 'trustar_import', ] diff --git a/misp_modules/modules/import_mod/trustar_import.py b/misp_modules/modules/import_mod/trustar_import.py index e69de29..2c55be2 100644 --- a/misp_modules/modules/import_mod/trustar_import.py +++ b/misp_modules/modules/import_mod/trustar_import.py @@ -0,0 +1,7 @@ +import base64 +import json + +from trustar import TruStar + +misp_errors = {'error': "Error"} + From 341a569de54c56ddb927bc80a67f0319824db6f4 Mon Sep 17 00:00:00 2001 From: Jesse Hedden Date: Sun, 21 Jun 2020 19:52:17 -0700 Subject: [PATCH 03/16] ready for code review --- .../modules/expansion/trustar_enrich.py | 113 ++++++++++++------ 1 file changed, 76 insertions(+), 37 deletions(-) diff --git a/misp_modules/modules/expansion/trustar_enrich.py b/misp_modules/modules/expansion/trustar_enrich.py index e786ff3..38f5d16 100644 --- a/misp_modules/modules/expansion/trustar_enrich.py +++ b/misp_modules/modules/expansion/trustar_enrich.py @@ -3,61 +3,100 @@ from pymisp import MISPAttribute, MISPEvent, MISPObject from trustar import TruStar misperrors = {'error': "Error"} -mispattributes = {'input': ["btc", "domain","email-src", "filename", "hostname", "ip-src", "ip-dst", "malware-type", "md5", "sha1", "sha256", "url"], 'format': 'misp_standard'} +mispattributes = { + 'input': ["btc", "domain", "email-src", "filename", "hostname", "ip-src", "ip-dst", "malware-type", "md5", "sha1", + "sha256", "url"], 'format': 'misp_standard'} moduleinfo = {'version': "0.1", 'author': "Jesse Hedden", 'description': "Enrich data with TruSTAR", 'module-type': ["hover", "expansion"]} -moduleconfig = ["api_key", "api_secret", "enclave_ids"] +moduleconfig = ["user_api_key", "user_api_secret", "enclave_ids"] -def get_results(misp_event): - event = json.loads(misp_event.to_json()) - results = {key: event[key] for key in ('Attribute', 'Object')} - return {'results': results} +class TruSTARParser: + ENTITY_TYPE_MAPPINGS = { + 'BITCOIN_ADDRESS': "btc", + 'CIDR_BLOCK': "ip-src", + 'CVE': "vulnerability", + 'URL': "url", + 'EMAIL_ADDRESS': "email-src", + 'SOFTWARE': "filename", + 'IP': "ip-src", + 'MALWARE': "malware-type", + 'MD5': "md5", + 'REGISTRY_KEY': "regkey", + 'SHA1': "sha1", + 'SHA256': "sha256" + } -def parse_indicator_summary(attribute, summary): - misp_event = MISPEvent() - misp_attribute = MISPAttribute().from_dict(**attribute) - misp_event.add_attribute(**misp_attribute) + REPORT_BASE_URL = "https://station.trustar.co/constellation/reports/{}" - mapping = {'value': 'text', 'reportId': 'text', 'enclaveId': 'text', 'description': 'text'} + def __init__(self, attribute, config): + config['enclave_ids'] = config.get('enclave_ids', "").split(',') + self.ts_client = TruStar(config=config) - for item in summary.get('items'): - trustar_obj = MISPObject(attribute.value) - for key, attribute_type in mapping.items(): - trustar_obj.add_attribute(key, attribute_type=attribute_type, value=item[key]) - trustar_obj.add_reference(misp_attribute.uuid, 'associated-to') - misp_event.add_object(**trustar_obj) + self.misp_event = MISPEvent() + self.misp_attribute = MISPAttribute() + self.misp_attribute.from_dict(**attribute) + self.misp_event.add_attribute(**self.misp_attribute) - return misp_event + def get_results(self): + event = json.loads(self.misp_event.to_json()) + results = {key: event[key] for key in ('Attribute', 'Object') if (key in event and event[key])} + return {'results': results} + def generate_trustar_links(self, entity_value): + """ + Generates links to TruSTAR reports if they exist. -def handler(q=False): + :param entity_value: Value of entity. + """ + report_links = list() + trustar_reports = self.ts_client.search_reports(entity_value) + for report in trustar_reports: + report_links.append(self.REPORT_BASE_URL.format(report.id)) - if q is False: - return False + return report_links - request = json.loads(q) - config = request.get('config', {}) - if not config.get('api_key') or not config.get('api_secret'): - misperrors['error'] = "Your TruSTAR API key and secret are required for indicator enrichment." - return misperrors + def parse_indicator_summary(self, attribute, summaries): - enclave_ids = [enclave_id for enclave_id in config.get('enclave_ids', "").split(',')] - ts_client = TruStar(config={'user_api_key': config.get('api_key'), 'user_api_secret': config.get('api_secret'), 'enclave_ids': enclave_ids}) - attribute = request.get('attribute') + for summary in summaries: + trustar_obj = MISPObject('trustar_report') + summary_dict = summary.to_dict() + summary_type = summary_dict.get('type') + summary_value = summary_dict.get('value') + if summary_type in self.ENTITY_TYPE_MAPPINGS: + trustar_obj.add_attribute(summary_type, attribute_type=self.ENTITY_TYPE_MAPPINGS[summary_type], + value=summary_value) + trustar_obj.add_attribute("INDICATOR_SUMMARY", attribute_type="text", + value=json.dumps(summary_dict, sort_keys=True, indent=4)) + report_links = self.generate_trustar_links(summary_value) + for link in report_links: + trustar_obj.add_attribute("REPORT_LINK", attribute_type="link", value=link) + self.misp_event.add_object(**trustar_obj) - summary = ts_client.get_indicator_summaries(attribute) + def handler(q=False): - misp_event = parse_indicator_summary(attribute, summary) - return get_results(misp_event) + if q is False: + return False -def introspection(): - return mispattributes + request = json.loads(q) -def version(): - moduleinfo['config'] = moduleconfig - return moduleinfo + config = request.get('config', {}) + if not config.get('user_api_key') or not config.get('user_api_secret'): + misperrors['error'] = "Your TruSTAR API key and secret are required for indicator enrichment." + return misperrors + attribute = request['attribute'] + trustar_parser = TruSTARParser(attribute, config) + summaries = trustar_parser.ts_client.get_indicator_summaries([attribute['value']]) + trustar_parser.parse_indicator_summary(attribute, summaries) + return trustar_parser.get_results() + + def introspection(): + return mispattributes + + def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo From 68b4fbba0960fc732180302ceecc4c726d7d2083 Mon Sep 17 00:00:00 2001 From: Jesse Hedden Date: Mon, 22 Jun 2020 12:15:28 -0700 Subject: [PATCH 04/16] added client metatag to trustar client --- doc/expansion/trustar_enrich.json | 8 ++++++++ misp_modules/modules/expansion/trustar_enrich.py | 4 ++++ 2 files changed, 12 insertions(+) create mode 100644 doc/expansion/trustar_enrich.json diff --git a/doc/expansion/trustar_enrich.json b/doc/expansion/trustar_enrich.json new file mode 100644 index 0000000..d2f26bd --- /dev/null +++ b/doc/expansion/trustar_enrich.json @@ -0,0 +1,8 @@ +{ + "description": "Module to get information from ThreatMiner.", + "logo": "logos/threatminer.png", + "input": "A MISP attribute included in the following list:\n- hostname\n- domain\n- ip-src\n- ip-dst\n- md5\n- sha1\n- sha256\n- sha512", + "output": "MISP attributes mapped from the result of the query on ThreatMiner, included in the following list:\n- domain\n- ip-src\n- ip-dst\n- text\n- md5\n- sha1\n- sha256\n- sha512\n- ssdeep\n- authentihash\n- filename\n- whois-registrant-email\n- url\n- link", + "references": ["https://www.threatminer.org/"], + "features": "This module takes a MISP attribute as input and queries ThreatMiner with it.\n\nThe result of this query is then parsed and some data is mapped into MISP attributes in order to enrich the input attribute." +} diff --git a/misp_modules/modules/expansion/trustar_enrich.py b/misp_modules/modules/expansion/trustar_enrich.py index 38f5d16..db589fc 100644 --- a/misp_modules/modules/expansion/trustar_enrich.py +++ b/misp_modules/modules/expansion/trustar_enrich.py @@ -1,4 +1,5 @@ import json +import pymisp from pymisp import MISPAttribute, MISPEvent, MISPObject from trustar import TruStar @@ -32,8 +33,11 @@ class TruSTARParser: REPORT_BASE_URL = "https://station.trustar.co/constellation/reports/{}" + CLIENT_METATAG = "TruSTAR-MISP-{}".format(pymisp.__version__) + def __init__(self, attribute, config): config['enclave_ids'] = config.get('enclave_ids', "").split(',') + config['client_metatag'] = self.CLIENT_METATAG self.ts_client = TruStar(config=config) self.misp_event = MISPEvent() From 859bd19e24f7e2f5ad82cd4f514ba559322d7869 Mon Sep 17 00:00:00 2001 From: Jesse Hedden Date: Mon, 22 Jun 2020 12:57:37 -0700 Subject: [PATCH 05/16] added module documentation --- doc/README.md | 29 +++++++++++++++++++++++++++++ doc/expansion/trustar_enrich.json | 12 ++++++------ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/doc/README.md b/doc/README.md index 37cb2c9..cb28526 100644 --- a/doc/README.md +++ b/doc/README.md @@ -1168,6 +1168,35 @@ Module to get information from ThreatMiner. ----- +#### [trustar_enrich](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/trustar_enrich.py) + + + +Module to get enrich indicators with TruSTAR. +- **features**: +>This module enriches MISP attributes with scoring and metadata from TruSTAR. +> +>The TruSTAR indicator summary is appended to the attributes along with links to any associated reports. +- **input**: +>Any of the following MISP attributes: +>- btc +>- domain +>- email-src +>- filename +>- hostname +>- ip-src +>- ip-dst +>- md5 +>- sha1 +>- sha256 +>- url +- **output**: +>MISP attributes enriched with indicator summary data from the TruSTAR API. Data includes a severity level score and additional source and scoring info. +- **references**: +>https://docs.trustar.co/api/v13/indicators/get_indicator_summaries.html + +----- + #### [urlhaus](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/urlhaus.py) diff --git a/doc/expansion/trustar_enrich.json b/doc/expansion/trustar_enrich.json index d2f26bd..294419d 100644 --- a/doc/expansion/trustar_enrich.json +++ b/doc/expansion/trustar_enrich.json @@ -1,8 +1,8 @@ { - "description": "Module to get information from ThreatMiner.", - "logo": "logos/threatminer.png", - "input": "A MISP attribute included in the following list:\n- hostname\n- domain\n- ip-src\n- ip-dst\n- md5\n- sha1\n- sha256\n- sha512", - "output": "MISP attributes mapped from the result of the query on ThreatMiner, included in the following list:\n- domain\n- ip-src\n- ip-dst\n- text\n- md5\n- sha1\n- sha256\n- sha512\n- ssdeep\n- authentihash\n- filename\n- whois-registrant-email\n- url\n- link", - "references": ["https://www.threatminer.org/"], - "features": "This module takes a MISP attribute as input and queries ThreatMiner with it.\n\nThe result of this query is then parsed and some data is mapped into MISP attributes in order to enrich the input attribute." + "description": "Module to get enrich indicators with TruSTAR.", + "logo": "logos/trustar.png", + "input": "Any of the following MISP attributes:\n- btc\n- domain\n- email-src\n- filename\n- hostname\n- ip-src\n- ip-dst\n- md5\n- sha1\n- sha256\n- url", + "output": "MISP attributes enriched with indicator summary data from the TruSTAR API. Data includes a severity level score and additional source and scoring info.", + "references": ["https://docs.trustar.co/api/v13/indicators/get_indicator_summaries.html"], + "features": "This module enriches MISP attributes with scoring and metadata from TruSTAR.\n\nThe TruSTAR indicator summary is appended to the attributes along with links to any associated reports." } From f3b27ca9c03d2fec4b55c4247e353f8a81721608 Mon Sep 17 00:00:00 2001 From: Jesse Hedden Date: Mon, 22 Jun 2020 12:58:10 -0700 Subject: [PATCH 06/16] updated client metatag and version --- misp_modules/modules/expansion/trustar_enrich.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/trustar_enrich.py b/misp_modules/modules/expansion/trustar_enrich.py index db589fc..73854f3 100644 --- a/misp_modules/modules/expansion/trustar_enrich.py +++ b/misp_modules/modules/expansion/trustar_enrich.py @@ -33,11 +33,13 @@ class TruSTARParser: REPORT_BASE_URL = "https://station.trustar.co/constellation/reports/{}" - CLIENT_METATAG = "TruSTAR-MISP-{}".format(pymisp.__version__) + CLIENT_METATAG = "misp-v2" + CLIENT_VERSION = "{}".format(pymisp.__version__) def __init__(self, attribute, config): config['enclave_ids'] = config.get('enclave_ids', "").split(',') config['client_metatag'] = self.CLIENT_METATAG + config['client_version'] = self.CLIENT_VERSION self.ts_client = TruStar(config=config) self.misp_event = MISPEvent() From 8e8c580a83bb64e230338a2275ed892aa7d2cef9 Mon Sep 17 00:00:00 2001 From: Jesse Hedden Date: Mon, 22 Jun 2020 12:58:32 -0700 Subject: [PATCH 07/16] uploaded TruSTAR logo --- doc/logos/trustar.png | Bin 0 -> 37780 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/logos/trustar.png diff --git a/doc/logos/trustar.png b/doc/logos/trustar.png new file mode 100644 index 0000000000000000000000000000000000000000..d4ac52138cbbab144bf22732e0ebdbc81d48be39 GIT binary patch literal 37780 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 z z(e67(>fQO=lRw@r@(9<*%MsTlPJpMV4I?qoaWI{pro^Dne$%pShRN@Ye_K%g@L2zzH5_-=Uuw7qOL z_2ehf`*teqlyJ;B=~R=HeY(ZnF7#(wTok&;lsYpt1e$s#56ZqIplYn38k@w4to}>pXys^r5+S7b{PEO8V%q=%jg~C+D2|(Bt zM~Vc^PT=|HpD&3-B2y?tKZTyvPxeQh*I#In((2n>;zFBKTxfNO`ZhPF*r{C6JoT(F zBP{jHiiv(1QBjx?5&g2|K)N&fB|RJG(E4_l%eDQn#~wSWvMER&1WZK$!fvYSg0XrK z;9h-GQ`2Y)-;XCTsUX20AB)N38w1Xvbv_b$5_|rv^SOAc9hrz{_KDS#B-6-rQIr{x ziZY|^BF>+AY0(;oL)uHs_Y(7c6jI-to11$Oz4658;%fU5fUw)I&QW582&`VcI;W+j zrGk2o<5}z}*$P@6ui|2xBd00o6wM^^O#w+X)AuI&PM@}Ye$UUrbG%V0mjs+XMQ)lb za_D<5eb3K`HPfnY`-tB@7IBJ_?9I;3-Zy*p>}G|k3dw+gtq4HaZB^YUtQG`lPW=E9 zr(q;g!%6Um(R(=MJB;4L+d`t0MBd9{-yC#^M!!SQNPXdt&^s*gryPoyYw(`OCFFEh zOiFjlf<6U}Ko@!GEZ`j?9t&pB0)bTiWAr{wT#nKEIB_{f{}dgkx#Q>PI9?zC0tyj; zuqy--$$&sS1W3%JB}r?<1nE0nC!#cIi&xW)L@#~vZ`vJ?j!Sgh!JFpLb(gNCJOpiKLT|bK(;>wqTB}9S z?qtGAo`4={3zzfKC;#Say)Id#kyx{6r_uKg@xJss7D;!D2D+|}Y_BKV>nIC;vxv6~ zH-9czu;4VE!}KzOffop9K>)(81!d#`0tph}e*j*1;f35#C{#$dvgj95@3xSh^B3_a z9m}O-9(sF8G(Gg2#)``x{@%^8_H%3koe$CZAb--&Abk(f_m0o2j`4Xq*G$oedV0QH z$Dj1Oo{rafJf8X={pd$c^c#XrK=1+qBN6!jVFqddJlry&00000NkvXXu0mjf{(_nZ literal 0 HcmV?d00001 From f13233d04c74b2e4644be2edf2a278edf13122ae Mon Sep 17 00:00:00 2001 From: Jesse Hedden Date: Mon, 22 Jun 2020 13:47:25 -0700 Subject: [PATCH 08/16] added comments and increased page size to max for get_indicator_summaries --- misp_modules/modules/expansion/trustar_enrich.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/expansion/trustar_enrich.py b/misp_modules/modules/expansion/trustar_enrich.py index 73854f3..4e8d916 100644 --- a/misp_modules/modules/expansion/trustar_enrich.py +++ b/misp_modules/modules/expansion/trustar_enrich.py @@ -48,6 +48,9 @@ class TruSTARParser: self.misp_event.add_attribute(**self.misp_attribute) def get_results(self): + """ + Returns the MISP Event enriched with TruSTAR indicator summary data. + """ event = json.loads(self.misp_event.to_json()) results = {key: event[key] for key in ('Attribute', 'Object') if (key in event and event[key])} return {'results': results} @@ -65,7 +68,14 @@ class TruSTARParser: return report_links - def parse_indicator_summary(self, attribute, summaries): + def parse_indicator_summary(self, summaries): + """ + Converts a response from the TruSTAR /1.3/indicators/summaries endpoint + a MISP trustar_report object and adds the summary data and links as attributes. + + :param summaries: A TruSTAR Python SDK Page.generator object for generating + indicator summaries pages. + """ for summary in summaries: trustar_obj = MISPObject('trustar_report') @@ -96,7 +106,7 @@ class TruSTARParser: attribute = request['attribute'] trustar_parser = TruSTARParser(attribute, config) - summaries = trustar_parser.ts_client.get_indicator_summaries([attribute['value']]) + summaries = trustar_parser.ts_client.get_indicator_summaries([attribute['value']], page_size=100) trustar_parser.parse_indicator_summary(attribute, summaries) return trustar_parser.get_results() From b9d191686f5f0d2b802aca7f2d8a6022dfe7f37b Mon Sep 17 00:00:00 2001 From: Jesse Hedden Date: Mon, 22 Jun 2020 14:54:37 -0700 Subject: [PATCH 09/16] added try/except for TruSTAR API errors and additional comments --- misp_modules/modules/expansion/trustar_enrich.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/trustar_enrich.py b/misp_modules/modules/expansion/trustar_enrich.py index 4e8d916..1edc0f8 100644 --- a/misp_modules/modules/expansion/trustar_enrich.py +++ b/misp_modules/modules/expansion/trustar_enrich.py @@ -14,6 +14,8 @@ moduleinfo = {'version': "0.1", 'author': "Jesse Hedden", moduleconfig = ["user_api_key", "user_api_secret", "enclave_ids"] +MAX_PAGE_SIZE = 100 # Max allowable page size returned from /1.3/indicators/summaries endpoint + class TruSTARParser: ENTITY_TYPE_MAPPINGS = { @@ -93,6 +95,12 @@ class TruSTARParser: self.misp_event.add_object(**trustar_obj) def handler(q=False): + """ + MISP handler function. A user's API key and secret will be retrieved from the MISP + request and used to create a TruSTAR API client. If enclave IDs are provided, only + those enclaves will be queried for data. Otherwise, all of the enclaves a user has + access to will be queried. + """ if q is False: return False @@ -106,7 +114,13 @@ class TruSTARParser: attribute = request['attribute'] trustar_parser = TruSTARParser(attribute, config) - summaries = trustar_parser.ts_client.get_indicator_summaries([attribute['value']], page_size=100) + + try: + summaries = trustar_parser.ts_client.get_indicator_summaries([attribute['value']], page_size=MAX_PAGE_SIZE) + except Exception as e: + misperrors['error'] = "Unable to retrieve TruSTAR summary data: {}".format(e) + return misperrors + trustar_parser.parse_indicator_summary(attribute, summaries) return trustar_parser.get_results() From b60d142d32fcd3ca5c8e90256469005a67d383f1 Mon Sep 17 00:00:00 2001 From: Jesse Hedden Date: Mon, 22 Jun 2020 15:06:39 -0700 Subject: [PATCH 10/16] removed extra parameter --- misp_modules/modules/expansion/trustar_enrich.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/trustar_enrich.py b/misp_modules/modules/expansion/trustar_enrich.py index 1edc0f8..f163b85 100644 --- a/misp_modules/modules/expansion/trustar_enrich.py +++ b/misp_modules/modules/expansion/trustar_enrich.py @@ -121,7 +121,7 @@ class TruSTARParser: misperrors['error'] = "Unable to retrieve TruSTAR summary data: {}".format(e) return misperrors - trustar_parser.parse_indicator_summary(attribute, summaries) + trustar_parser.parse_indicator_summary(summaries) return trustar_parser.get_results() def introspection(): From b188d2da4e3f5c09755811aab567989c6a8567e8 Mon Sep 17 00:00:00 2001 From: Jesse Hedden Date: Wed, 24 Jun 2020 17:47:41 -0700 Subject: [PATCH 11/16] added strip to remove potential whitespace --- misp_modules/modules/expansion/trustar_enrich.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/trustar_enrich.py b/misp_modules/modules/expansion/trustar_enrich.py index f163b85..974a3f5 100644 --- a/misp_modules/modules/expansion/trustar_enrich.py +++ b/misp_modules/modules/expansion/trustar_enrich.py @@ -39,7 +39,7 @@ class TruSTARParser: CLIENT_VERSION = "{}".format(pymisp.__version__) def __init__(self, attribute, config): - config['enclave_ids'] = config.get('enclave_ids', "").split(',') + config['enclave_ids'] = config.get('enclave_ids', "").strip().split(',') config['client_metatag'] = self.CLIENT_METATAG config['client_version'] = self.CLIENT_VERSION self.ts_client = TruStar(config=config) From 61fbb30e1c975f2487fc22b24b1eb1d979ad553b Mon Sep 17 00:00:00 2001 From: Jesse Hedden Date: Thu, 25 Jun 2020 10:54:34 -0700 Subject: [PATCH 12/16] fixed metatag; convert summaries generator to list for error handling --- misp_modules/modules/expansion/trustar_enrich.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/misp_modules/modules/expansion/trustar_enrich.py b/misp_modules/modules/expansion/trustar_enrich.py index 974a3f5..ca5c886 100644 --- a/misp_modules/modules/expansion/trustar_enrich.py +++ b/misp_modules/modules/expansion/trustar_enrich.py @@ -14,7 +14,7 @@ moduleinfo = {'version': "0.1", 'author': "Jesse Hedden", moduleconfig = ["user_api_key", "user_api_secret", "enclave_ids"] -MAX_PAGE_SIZE = 100 # Max allowable page size returned from /1.3/indicators/summaries endpoint +MAX_PAGE_SIZE = 100 # Max allowable page size returned from /1.3/indicators/summaries endpoint class TruSTARParser: @@ -35,13 +35,11 @@ class TruSTARParser: REPORT_BASE_URL = "https://station.trustar.co/constellation/reports/{}" - CLIENT_METATAG = "misp-v2" - CLIENT_VERSION = "{}".format(pymisp.__version__) + CLIENT_METATAG = "MISP-{}".format(pymisp.__version__) def __init__(self, attribute, config): config['enclave_ids'] = config.get('enclave_ids', "").strip().split(',') config['client_metatag'] = self.CLIENT_METATAG - config['client_version'] = self.CLIENT_VERSION self.ts_client = TruStar(config=config) self.misp_event = MISPEvent() @@ -81,14 +79,13 @@ class TruSTARParser: for summary in summaries: trustar_obj = MISPObject('trustar_report') - summary_dict = summary.to_dict() - summary_type = summary_dict.get('type') - summary_value = summary_dict.get('value') + summary_type = summary.type + summary_value = summary.value if summary_type in self.ENTITY_TYPE_MAPPINGS: trustar_obj.add_attribute(summary_type, attribute_type=self.ENTITY_TYPE_MAPPINGS[summary_type], value=summary_value) trustar_obj.add_attribute("INDICATOR_SUMMARY", attribute_type="text", - value=json.dumps(summary_dict, sort_keys=True, indent=4)) + value=json.dumps(summary.to_dict(), sort_keys=True, indent=4)) report_links = self.generate_trustar_links(summary_value) for link in report_links: trustar_obj.add_attribute("REPORT_LINK", attribute_type="link", value=link) @@ -116,7 +113,8 @@ class TruSTARParser: trustar_parser = TruSTARParser(attribute, config) try: - summaries = trustar_parser.ts_client.get_indicator_summaries([attribute['value']], page_size=MAX_PAGE_SIZE) + summaries = list( + trustar_parser.ts_client.get_indicator_summaries([attribute['value']], page_size=MAX_PAGE_SIZE)) except Exception as e: misperrors['error'] = "Unable to retrieve TruSTAR summary data: {}".format(e) return misperrors From 2d31b4e037ee2b496ec23a8c328632f0975873c5 Mon Sep 17 00:00:00 2001 From: Jesse Hedden Date: Thu, 25 Jun 2020 13:10:50 -0700 Subject: [PATCH 13/16] fixed incorrect attribute name --- misp_modules/modules/expansion/trustar_enrich.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/misp_modules/modules/expansion/trustar_enrich.py b/misp_modules/modules/expansion/trustar_enrich.py index ca5c886..50b3d55 100644 --- a/misp_modules/modules/expansion/trustar_enrich.py +++ b/misp_modules/modules/expansion/trustar_enrich.py @@ -79,14 +79,14 @@ class TruSTARParser: for summary in summaries: trustar_obj = MISPObject('trustar_report') - summary_type = summary.type - summary_value = summary.value + indicator_type = summary.indicator_type + indicator_value = summary.value if summary_type in self.ENTITY_TYPE_MAPPINGS: - trustar_obj.add_attribute(summary_type, attribute_type=self.ENTITY_TYPE_MAPPINGS[summary_type], - value=summary_value) + trustar_obj.add_attribute(indicator_type, attribute_type=self.ENTITY_TYPE_MAPPINGS[indicator_type], + value=indicator_value) trustar_obj.add_attribute("INDICATOR_SUMMARY", attribute_type="text", value=json.dumps(summary.to_dict(), sort_keys=True, indent=4)) - report_links = self.generate_trustar_links(summary_value) + report_links = self.generate_trustar_links(indicator_value) for link in report_links: trustar_obj.add_attribute("REPORT_LINK", attribute_type="link", value=link) self.misp_event.add_object(**trustar_obj) From 9e1bc5681b0cbdf28ab8d9f9b88e0e7d828c6cfc Mon Sep 17 00:00:00 2001 From: Jesse Hedden Date: Thu, 25 Jun 2020 15:22:54 -0700 Subject: [PATCH 14/16] fixed indent --- .../modules/expansion/trustar_enrich.py | 61 ++++++++++--------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/misp_modules/modules/expansion/trustar_enrich.py b/misp_modules/modules/expansion/trustar_enrich.py index 50b3d55..48b4895 100644 --- a/misp_modules/modules/expansion/trustar_enrich.py +++ b/misp_modules/modules/expansion/trustar_enrich.py @@ -91,40 +91,43 @@ class TruSTARParser: trustar_obj.add_attribute("REPORT_LINK", attribute_type="link", value=link) self.misp_event.add_object(**trustar_obj) - def handler(q=False): - """ - MISP handler function. A user's API key and secret will be retrieved from the MISP - request and used to create a TruSTAR API client. If enclave IDs are provided, only - those enclaves will be queried for data. Otherwise, all of the enclaves a user has - access to will be queried. - """ - if q is False: - return False +def handler(q=False): + """ + MISP handler function. A user's API key and secret will be retrieved from the MISP + request and used to create a TruSTAR API client. If enclave IDs are provided, only + those enclaves will be queried for data. Otherwise, all of the enclaves a user has + access to will be queried. + """ - request = json.loads(q) + if q is False: + return False - config = request.get('config', {}) - if not config.get('user_api_key') or not config.get('user_api_secret'): - misperrors['error'] = "Your TruSTAR API key and secret are required for indicator enrichment." - return misperrors + request = json.loads(q) - attribute = request['attribute'] - trustar_parser = TruSTARParser(attribute, config) + config = request.get('config', {}) + if not config.get('user_api_key') or not config.get('user_api_secret'): + misperrors['error'] = "Your TruSTAR API key and secret are required for indicator enrichment." + return misperrors - try: - summaries = list( - trustar_parser.ts_client.get_indicator_summaries([attribute['value']], page_size=MAX_PAGE_SIZE)) - except Exception as e: - misperrors['error'] = "Unable to retrieve TruSTAR summary data: {}".format(e) - return misperrors + attribute = request['attribute'] + trustar_parser = TruSTARParser(attribute, config) - trustar_parser.parse_indicator_summary(summaries) - return trustar_parser.get_results() + try: + summaries = list( + trustar_parser.ts_client.get_indicator_summaries([attribute['value']], page_size=MAX_PAGE_SIZE)) + except Exception as e: + misperrors['error'] = "Unable to retrieve TruSTAR summary data: {}".format(e) + return misperrors - def introspection(): - return mispattributes + trustar_parser.parse_indicator_summary(summaries) + return trustar_parser.get_results() - def version(): - moduleinfo['config'] = moduleconfig - return moduleinfo + +def introspection(): + return mispattributes + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo From a91d50b5073ec8890d3eec1c704d6e4626584bc9 Mon Sep 17 00:00:00 2001 From: Jesse Hedden Date: Sat, 27 Jun 2020 17:29:01 -0700 Subject: [PATCH 15/16] corrected variable name --- misp_modules/modules/expansion/trustar_enrich.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/trustar_enrich.py b/misp_modules/modules/expansion/trustar_enrich.py index 48b4895..efe7c53 100644 --- a/misp_modules/modules/expansion/trustar_enrich.py +++ b/misp_modules/modules/expansion/trustar_enrich.py @@ -81,7 +81,7 @@ class TruSTARParser: trustar_obj = MISPObject('trustar_report') indicator_type = summary.indicator_type indicator_value = summary.value - if summary_type in self.ENTITY_TYPE_MAPPINGS: + if indicator_type in self.ENTITY_TYPE_MAPPINGS: trustar_obj.add_attribute(indicator_type, attribute_type=self.ENTITY_TYPE_MAPPINGS[indicator_type], value=indicator_value) trustar_obj.add_attribute("INDICATOR_SUMMARY", attribute_type="text", From a70558945a1124c70875a5d9f217a55439828ea9 Mon Sep 17 00:00:00 2001 From: Jesse Hedden Date: Sat, 27 Jun 2020 17:46:51 -0700 Subject: [PATCH 16/16] removed obsolete file --- misp_modules/modules/import_mod/trustar_import.py | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 misp_modules/modules/import_mod/trustar_import.py diff --git a/misp_modules/modules/import_mod/trustar_import.py b/misp_modules/modules/import_mod/trustar_import.py deleted file mode 100644 index 2c55be2..0000000 --- a/misp_modules/modules/import_mod/trustar_import.py +++ /dev/null @@ -1,7 +0,0 @@ -import base64 -import json - -from trustar import TruStar - -misp_errors = {'error': "Error"} -