misp-modules 2.4.196__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. misp_modules/__init__.py +379 -0
  2. misp_modules/helpers/__init__.py +1 -0
  3. misp_modules/helpers/cache.py +84 -0
  4. misp_modules/lib/__init__.py +4 -0
  5. misp_modules/lib/_vmray/__init__.py +0 -0
  6. misp_modules/lib/_vmray/parser.py +1417 -0
  7. misp_modules/lib/_vmray/rest_api.py +148 -0
  8. misp_modules/lib/cof2misp/LICENSE-2.0.txt +202 -0
  9. misp_modules/lib/cof2misp/__init__.py +0 -0
  10. misp_modules/lib/cof2misp/cof.py +165 -0
  11. misp_modules/lib/joe_mapping.py +114 -0
  12. misp_modules/lib/joe_parser.py +573 -0
  13. misp_modules/lib/lastline_api.py +841 -0
  14. misp_modules/lib/qintel_helper.py +263 -0
  15. misp_modules/lib/stix2misp.py +2080 -0
  16. misp_modules/lib/stix2misp_mapping.py +460 -0
  17. misp_modules/lib/synonymsToTagNames.json +1 -0
  18. misp_modules/lib/vt_graph_parser/__init__.py +8 -0
  19. misp_modules/lib/vt_graph_parser/errors.py +20 -0
  20. misp_modules/lib/vt_graph_parser/helpers/__init__.py +7 -0
  21. misp_modules/lib/vt_graph_parser/helpers/parsers.py +88 -0
  22. misp_modules/lib/vt_graph_parser/helpers/rules.py +304 -0
  23. misp_modules/lib/vt_graph_parser/helpers/wrappers.py +58 -0
  24. misp_modules/lib/vt_graph_parser/importers/__init__.py +7 -0
  25. misp_modules/lib/vt_graph_parser/importers/base.py +98 -0
  26. misp_modules/lib/vt_graph_parser/importers/pymisp_response.py +73 -0
  27. misp_modules/modules/__init__.py +4 -0
  28. misp_modules/modules/action_mod/__init__.py +1 -0
  29. misp_modules/modules/action_mod/_utils/__init__.py +1 -0
  30. misp_modules/modules/action_mod/_utils/utils.py +70 -0
  31. misp_modules/modules/action_mod/mattermost.py +113 -0
  32. misp_modules/modules/action_mod/slack.py +96 -0
  33. misp_modules/modules/action_mod/testaction.py +68 -0
  34. misp_modules/modules/expansion/__init__.py +36 -0
  35. misp_modules/modules/expansion/_dnsdb_query/COPYRIGHT +27 -0
  36. misp_modules/modules/expansion/_dnsdb_query/LICENSE +202 -0
  37. misp_modules/modules/expansion/_dnsdb_query/README.md +162 -0
  38. misp_modules/modules/expansion/_dnsdb_query/__init__.py +0 -0
  39. misp_modules/modules/expansion/_dnsdb_query/dnsdb_query.py +327 -0
  40. misp_modules/modules/expansion/_ransomcoindb/__init__.py +0 -0
  41. misp_modules/modules/expansion/_ransomcoindb/ransomcoindb.py +96 -0
  42. misp_modules/modules/expansion/_vulnerability_parser/__init__.py +0 -0
  43. misp_modules/modules/expansion/_vulnerability_parser/vulnerability_parser.py +118 -0
  44. misp_modules/modules/expansion/abuseipdb.py +146 -0
  45. misp_modules/modules/expansion/apiosintds.py +365 -0
  46. misp_modules/modules/expansion/apivoid.py +133 -0
  47. misp_modules/modules/expansion/assemblyline_query.py +179 -0
  48. misp_modules/modules/expansion/assemblyline_submit.py +100 -0
  49. misp_modules/modules/expansion/backscatter_io.py +84 -0
  50. misp_modules/modules/expansion/btc_scam_check.py +54 -0
  51. misp_modules/modules/expansion/btc_steroids.py +238 -0
  52. misp_modules/modules/expansion/censys_enrich.py +287 -0
  53. misp_modules/modules/expansion/circl_passivedns.py +85 -0
  54. misp_modules/modules/expansion/circl_passivessl.py +113 -0
  55. misp_modules/modules/expansion/clamav.py +129 -0
  56. misp_modules/modules/expansion/cluster25_expand.py +239 -0
  57. misp_modules/modules/expansion/countrycode.py +69 -0
  58. misp_modules/modules/expansion/cpe.py +140 -0
  59. misp_modules/modules/expansion/crowdsec.py +235 -0
  60. misp_modules/modules/expansion/crowdstrike_falcon.py +148 -0
  61. misp_modules/modules/expansion/cuckoo_submit.py +161 -0
  62. misp_modules/modules/expansion/cve.py +57 -0
  63. misp_modules/modules/expansion/cve_advanced.py +185 -0
  64. misp_modules/modules/expansion/cytomic_orion.py +196 -0
  65. misp_modules/modules/expansion/dbl_spamhaus.py +78 -0
  66. misp_modules/modules/expansion/dns.py +71 -0
  67. misp_modules/modules/expansion/docx_enrich.py +71 -0
  68. misp_modules/modules/expansion/domaintools.py +290 -0
  69. misp_modules/modules/expansion/eql.py +91 -0
  70. misp_modules/modules/expansion/eupi.py +86 -0
  71. misp_modules/modules/expansion/extract_url_components.py +83 -0
  72. misp_modules/modules/expansion/farsight_passivedns.py +243 -0
  73. misp_modules/modules/expansion/geoip_asn.py +75 -0
  74. misp_modules/modules/expansion/geoip_city.py +75 -0
  75. misp_modules/modules/expansion/geoip_country.py +73 -0
  76. misp_modules/modules/expansion/google_safe_browsing.py +86 -0
  77. misp_modules/modules/expansion/google_search.py +57 -0
  78. misp_modules/modules/expansion/google_threat_intelligence.py +453 -0
  79. misp_modules/modules/expansion/greynoise.py +346 -0
  80. misp_modules/modules/expansion/hashdd.py +53 -0
  81. misp_modules/modules/expansion/hashlookup.py +118 -0
  82. misp_modules/modules/expansion/hibp.py +62 -0
  83. misp_modules/modules/expansion/html_to_markdown.py +63 -0
  84. misp_modules/modules/expansion/hyasinsight.py +881 -0
  85. misp_modules/modules/expansion/intel471.py +73 -0
  86. misp_modules/modules/expansion/intelmq_eventdb.py.experimental +67 -0
  87. misp_modules/modules/expansion/ip2locationio.py +90 -0
  88. misp_modules/modules/expansion/ipasn.py +70 -0
  89. misp_modules/modules/expansion/ipinfo.py +112 -0
  90. misp_modules/modules/expansion/ipqs_fraud_and_risk_scoring.py +633 -0
  91. misp_modules/modules/expansion/iprep.py +98 -0
  92. misp_modules/modules/expansion/jinja_template_rendering.py +54 -0
  93. misp_modules/modules/expansion/joesandbox_query.py +91 -0
  94. misp_modules/modules/expansion/joesandbox_submit.py +147 -0
  95. misp_modules/modules/expansion/lastline_query.py +148 -0
  96. misp_modules/modules/expansion/lastline_submit.py +180 -0
  97. misp_modules/modules/expansion/macaddress_io.py +130 -0
  98. misp_modules/modules/expansion/macvendors.py +55 -0
  99. misp_modules/modules/expansion/malshare_upload.py +107 -0
  100. misp_modules/modules/expansion/malwarebazaar.py +67 -0
  101. misp_modules/modules/expansion/mcafee_insights_enrich.py +249 -0
  102. misp_modules/modules/expansion/mmdb_lookup.py +138 -0
  103. misp_modules/modules/expansion/module.py.skeleton +44 -0
  104. misp_modules/modules/expansion/mwdb.py +152 -0
  105. misp_modules/modules/expansion/ocr_enrich.py +74 -0
  106. misp_modules/modules/expansion/ods_enrich.py +71 -0
  107. misp_modules/modules/expansion/odt_enrich.py +61 -0
  108. misp_modules/modules/expansion/onyphe.py +243 -0
  109. misp_modules/modules/expansion/onyphe_full.py +387 -0
  110. misp_modules/modules/expansion/otx.py +167 -0
  111. misp_modules/modules/expansion/passive_ssh.py +150 -0
  112. misp_modules/modules/expansion/passivetotal.py +358 -0
  113. misp_modules/modules/expansion/pdf_enrich.py +58 -0
  114. misp_modules/modules/expansion/pptx_enrich.py +65 -0
  115. misp_modules/modules/expansion/qintel_qsentry.py +229 -0
  116. misp_modules/modules/expansion/qrcode.py +99 -0
  117. misp_modules/modules/expansion/ransomcoindb.py +82 -0
  118. misp_modules/modules/expansion/rbl.py +126 -0
  119. misp_modules/modules/expansion/recordedfuture.py +544 -0
  120. misp_modules/modules/expansion/reversedns.py +77 -0
  121. misp_modules/modules/expansion/securitytrails.py +571 -0
  122. misp_modules/modules/expansion/shodan.py +244 -0
  123. misp_modules/modules/expansion/sigma_queries.py +61 -0
  124. misp_modules/modules/expansion/sigma_syntax_validator.py +49 -0
  125. misp_modules/modules/expansion/sigmf_expand.py +303 -0
  126. misp_modules/modules/expansion/socialscan.py +108 -0
  127. misp_modules/modules/expansion/sophoslabs_intelix.py +146 -0
  128. misp_modules/modules/expansion/sourcecache.py +57 -0
  129. misp_modules/modules/expansion/stairwell.py +156 -0
  130. misp_modules/modules/expansion/stix2_pattern_syntax_validator.py +56 -0
  131. misp_modules/modules/expansion/threatcrowd.py +170 -0
  132. misp_modules/modules/expansion/threatfox.py +75 -0
  133. misp_modules/modules/expansion/threatminer.py +152 -0
  134. misp_modules/modules/expansion/triage_submit.py +119 -0
  135. misp_modules/modules/expansion/trustar_enrich.py +235 -0
  136. misp_modules/modules/expansion/urlhaus.py +164 -0
  137. misp_modules/modules/expansion/urlscan.py +269 -0
  138. misp_modules/modules/expansion/variotdbs.py +133 -0
  139. misp_modules/modules/expansion/virustotal.py +311 -0
  140. misp_modules/modules/expansion/virustotal_public.py +272 -0
  141. misp_modules/modules/expansion/virustotal_upload.py +91 -0
  142. misp_modules/modules/expansion/vmray_submit.py +161 -0
  143. misp_modules/modules/expansion/vmware_nsx.py +628 -0
  144. misp_modules/modules/expansion/vulndb.py +292 -0
  145. misp_modules/modules/expansion/vulnerability_lookup.py +324 -0
  146. misp_modules/modules/expansion/vulners.py +81 -0
  147. misp_modules/modules/expansion/vysion.py +221 -0
  148. misp_modules/modules/expansion/whois.py +72 -0
  149. misp_modules/modules/expansion/whoisfreaks.py +232 -0
  150. misp_modules/modules/expansion/wiki.py +58 -0
  151. misp_modules/modules/expansion/xforceexchange.py +189 -0
  152. misp_modules/modules/expansion/xlsx_enrich.py +63 -0
  153. misp_modules/modules/expansion/yara_query.py +70 -0
  154. misp_modules/modules/expansion/yara_syntax_validator.py +49 -0
  155. misp_modules/modules/expansion/yeti.py +196 -0
  156. misp_modules/modules/export_mod/__init__.py +3 -0
  157. misp_modules/modules/export_mod/cef_export.py +93 -0
  158. misp_modules/modules/export_mod/cisco_firesight_manager_ACL_rule_export.py +150 -0
  159. misp_modules/modules/export_mod/defender_endpoint_export.py +141 -0
  160. misp_modules/modules/export_mod/goamlexport.py +255 -0
  161. misp_modules/modules/export_mod/liteexport.py +97 -0
  162. misp_modules/modules/export_mod/mass_eql_export.py +106 -0
  163. misp_modules/modules/export_mod/nexthinkexport.py +131 -0
  164. misp_modules/modules/export_mod/osqueryexport.py +125 -0
  165. misp_modules/modules/export_mod/pdfexport.py +107 -0
  166. misp_modules/modules/export_mod/testexport.py +73 -0
  167. misp_modules/modules/export_mod/threatStream_misp_export.py +114 -0
  168. misp_modules/modules/export_mod/threat_connect_export.py +125 -0
  169. misp_modules/modules/export_mod/virustotal_collections.py +141 -0
  170. misp_modules/modules/export_mod/vt_graph.py +120 -0
  171. misp_modules/modules/export_mod/yara_export.py +290 -0
  172. misp_modules/modules/import_mod/__init__.py +22 -0
  173. misp_modules/modules/import_mod/cof2misp.py +264 -0
  174. misp_modules/modules/import_mod/csvimport.py +319 -0
  175. misp_modules/modules/import_mod/cuckooimport.py +749 -0
  176. misp_modules/modules/import_mod/email_import.py +294 -0
  177. misp_modules/modules/import_mod/goamlimport.py +188 -0
  178. misp_modules/modules/import_mod/import_blueprint.py +93 -0
  179. misp_modules/modules/import_mod/joe_import.py +74 -0
  180. misp_modules/modules/import_mod/lastline_import.py +160 -0
  181. misp_modules/modules/import_mod/mispjson.py +73 -0
  182. misp_modules/modules/import_mod/ocr.py +122 -0
  183. misp_modules/modules/import_mod/openiocimport.py +99 -0
  184. misp_modules/modules/import_mod/taxii21.py +383 -0
  185. misp_modules/modules/import_mod/testimport.py +73 -0
  186. misp_modules/modules/import_mod/threatanalyzer_import.py +552 -0
  187. misp_modules/modules/import_mod/url_import.py +97 -0
  188. misp_modules/modules/import_mod/vmray_import.py +96 -0
  189. misp_modules/modules/import_mod/vmray_summary_json_import.py +87 -0
  190. misp_modules-2.4.196.dist-info/LICENSE +661 -0
  191. misp_modules-2.4.196.dist-info/METADATA +281 -0
  192. misp_modules-2.4.196.dist-info/RECORD +194 -0
  193. misp_modules-2.4.196.dist-info/WHEEL +4 -0
  194. misp_modules-2.4.196.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,235 @@
1
+ import json
2
+ import pymisp
3
+ from base64 import b64encode
4
+ from collections import OrderedDict
5
+ from . import check_input_attribute, checking_error, standard_error_message
6
+ from pymisp import MISPAttribute, MISPEvent, MISPObject
7
+ from trustar import TruStar, Indicator
8
+ from urllib.parse import quote
9
+
10
+ misperrors = {'error': "Error"}
11
+ mispattributes = {
12
+ 'input': ["btc", "domain", "email-src", "filename", "hostname", "ip-src", "ip-dst", "malware-type", "md5", "sha1",
13
+ "sha256", "url"], 'format': 'misp_standard'}
14
+
15
+ moduleinfo = {
16
+ 'version': '0.1',
17
+ 'author': 'Jesse Hedden',
18
+ 'description': 'Module to get enrich indicators with TruSTAR.',
19
+ 'module-type': ['hover', 'expansion'],
20
+ 'name': 'TruSTAR Enrich',
21
+ 'logo': 'trustar.png',
22
+ 'requirements': [],
23
+ '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.',
24
+ 'references': ['https://docs.trustar.co/api/v13/indicators/get_indicator_summaries.html'],
25
+ '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',
26
+ 'output': 'MISP attributes enriched with indicator summary data from the TruSTAR API. Data includes a severity level score and additional source and scoring info.',
27
+ }
28
+
29
+ moduleconfig = ["user_api_key", "user_api_secret", "enclave_ids"]
30
+
31
+ MAX_PAGE_SIZE = 100 # Max allowable page size returned from /1.3/indicators/summaries endpoint
32
+
33
+
34
+ class TruSTARParser:
35
+ ENTITY_TYPE_MAPPINGS = {
36
+ 'BITCOIN_ADDRESS': "btc",
37
+ 'CIDR_BLOCK': "ip-src",
38
+ 'CVE': "vulnerability",
39
+ 'URL': "url",
40
+ 'EMAIL_ADDRESS': "email-src",
41
+ 'SOFTWARE': "filename",
42
+ 'IP': "ip-src",
43
+ 'MALWARE': "malware-type",
44
+ 'MD5': "md5",
45
+ 'REGISTRY_KEY': "regkey",
46
+ 'SHA1': "sha1",
47
+ 'SHA256': "sha256"
48
+ }
49
+
50
+ # Relevant fields from each TruSTAR endpoint
51
+ SUMMARY_FIELDS = ["severityLevel", "source", "score", "attributes"]
52
+ METADATA_FIELDS = ["sightings", "firstSeen", "lastSeen", "tags"]
53
+
54
+ REPORT_BASE_URL = "https://station.trustar.co/constellation/reports/{}"
55
+
56
+ CLIENT_METATAG = f"MISP-{pymisp.__version__}"
57
+
58
+ def __init__(self, attribute, config):
59
+ config['enclave_ids'] = config.get('enclave_ids', "").strip().split(',')
60
+ config['client_metatag'] = self.CLIENT_METATAG
61
+ self.ts_client = TruStar(config=config)
62
+
63
+ self.misp_event = MISPEvent()
64
+ self.misp_attribute = MISPAttribute()
65
+ self.misp_attribute.from_dict(**attribute)
66
+ self.misp_event.add_attribute(**self.misp_attribute)
67
+
68
+ def get_results(self):
69
+ """
70
+ Returns the MISP Event enriched with TruSTAR indicator summary data.
71
+ """
72
+ try:
73
+ event = json.loads(self.misp_event.to_json())
74
+ results = {key: event[key] for key in ('Attribute', 'Object') if (key in event and event[key])}
75
+ return {'results': results}
76
+ except Exception as e:
77
+ misperrors['error'] += f" -- Encountered issue serializing enrichment data -- {e}"
78
+ return misperrors
79
+
80
+ def generate_trustar_link(self, entity_type, entity_value):
81
+ """
82
+ Generates link to TruSTAR report of entity.
83
+
84
+ :param entity_type: <str> Type of entity.
85
+ :param entity_value: <str> Value of entity.
86
+ :return: <str> Link to indicator report in TruSTAR platform.
87
+ """
88
+ report_id = b64encode(quote(f"{entity_type}|{entity_value}").encode()).decode()
89
+
90
+ return self.REPORT_BASE_URL.format(report_id)
91
+
92
+ @staticmethod
93
+ def extract_tags(enrichment_report):
94
+ """
95
+ Extracts tags from the enrichment report in order to add them
96
+ to the TruSTAR MISP Object. Removes tags from report to avoid
97
+ redundancy.
98
+
99
+ :param: <OrderedDict> Enrichment data.
100
+ :return: <list> List of tags.
101
+ """
102
+ if enrichment_report and enrichment_report.get('tags'):
103
+ return [tag.get('name') for tag in enrichment_report.pop('tags')]
104
+ return None
105
+
106
+ def generate_enrichment_report(self, summary, metadata):
107
+ """
108
+ Extracts desired fields from summary and metadata reports and
109
+ generates an enrichment report.
110
+
111
+ :param summary: <trustar.IndicatorSummary> Indicator summary report.
112
+ :param metadata: <trustar.Indicator> Indicator metadata report.
113
+ :return: <str> Enrichment report.
114
+ """
115
+ # Preserve order of fields as they exist in SUMMARY_FIELDS and METADATA_FIELDS
116
+ enrichment_report = OrderedDict()
117
+
118
+ if summary:
119
+ summary_dict = summary.to_dict()
120
+ enrichment_report.update(
121
+ {field: summary_dict[field] for field in self.SUMMARY_FIELDS if summary_dict.get(field)})
122
+
123
+ if metadata:
124
+ metadata_dict = metadata.to_dict()
125
+ enrichment_report.update(
126
+ {field: metadata_dict[field] for field in self.METADATA_FIELDS if metadata_dict.get(field)})
127
+
128
+ return enrichment_report
129
+
130
+ def parse_indicator_summary(self, indicator, summary, metadata):
131
+ """
132
+ Pulls enrichment data from the TruSTAR /indicators/summaries and /indicators/metadata endpoints
133
+ and creates a MISP trustar_report.
134
+
135
+ :param indicator: <str> Value of the attribute
136
+ :summary: <trustar.IndicatorSummary> Indicator summary response object.
137
+ :metadata: <trustar.Indicator> Indicator response object.
138
+ """
139
+
140
+ # Verify that the indicator type is supported by TruSTAR
141
+ if summary and summary.indicator_type in self.ENTITY_TYPE_MAPPINGS:
142
+ indicator_type = summary.indicator_type
143
+ elif metadata and metadata.type in self.ENTITY_TYPE_MAPPINGS:
144
+ indicator_type = metadata.type
145
+ else:
146
+ misperrors['error'] += " -- Attribute not found or not supported"
147
+ raise Exception
148
+
149
+ try:
150
+ # Extract most relevant fields from indicator summary and metadata responses
151
+ enrichment_report = self.generate_enrichment_report(summary, metadata)
152
+ tags = self.extract_tags(enrichment_report)
153
+
154
+ if enrichment_report:
155
+ # Create MISP trustar_report object and populate it with enrichment data
156
+ trustar_obj = MISPObject('trustar_report')
157
+ trustar_obj.add_attribute(indicator_type, attribute_type=self.ENTITY_TYPE_MAPPINGS[indicator_type],
158
+ value=indicator)
159
+ trustar_obj.add_attribute("INDICATOR_SUMMARY", attribute_type="text",
160
+ value=json.dumps(enrichment_report, indent=4))
161
+
162
+ report_link = self.generate_trustar_link(indicator_type, indicator)
163
+ trustar_obj.add_attribute("REPORT_LINK", attribute_type="link", value=report_link)
164
+
165
+ self.misp_event.add_object(**trustar_obj)
166
+ elif not tags:
167
+ # If enrichment report is empty and there are no tags, nothing to add to attribute
168
+ raise Exception("No relevant data found")
169
+
170
+ if tags:
171
+ for tag in tags:
172
+ self.misp_event.add_attribute_tag(tag, indicator)
173
+
174
+ except Exception as e:
175
+ misperrors['error'] += f" -- Error enriching attribute {indicator} -- {e}"
176
+ raise e
177
+
178
+
179
+ def handler(q=False):
180
+ """
181
+ MISP handler function. A user's API key and secret will be retrieved from the MISP
182
+ request and used to create a TruSTAR API client. If enclave IDs are provided, only
183
+ those enclaves will be queried for data. Otherwise, all of the enclaves a user has
184
+ access to will be queried.
185
+ """
186
+
187
+ if q is False:
188
+ return False
189
+
190
+ request = json.loads(q)
191
+
192
+ config = request.get('config', {})
193
+ if not config.get('user_api_key') or not config.get('user_api_secret'):
194
+ misperrors['error'] = "Your TruSTAR API key and secret are required for indicator enrichment."
195
+ return misperrors
196
+
197
+ if not request.get('attribute') or not check_input_attribute(request['attribute'], requirements=('type', 'value')):
198
+ return {'error': f'{standard_error_message}, {checking_error}.'}
199
+ attribute = request['attribute']
200
+ if attribute['type'] not in mispattributes['input']:
201
+ return {'error': 'Unsupported attribute type.'}
202
+ trustar_parser = TruSTARParser(attribute, config)
203
+ metadata = None
204
+ summary = None
205
+
206
+ try:
207
+ metadata = trustar_parser.ts_client.get_indicators_metadata([Indicator(value=attribute['value'])])[0]
208
+ except IndexError:
209
+ misperrors['error'] += f" -- No metadata found for indicator {attribute['value']}"
210
+ except Exception as e:
211
+ misperrors['error'] += f" -- Could not retrieve indicator metadata from TruSTAR {e}"
212
+
213
+ try:
214
+ summary = list(
215
+ trustar_parser.ts_client.get_indicator_summaries([attribute['value']], page_size=MAX_PAGE_SIZE))[0]
216
+ except IndexError:
217
+ misperrors['error'] += f" -- No summary data found for indicator {attribute['value']}"
218
+ except Exception as e:
219
+ misperrors['error'] += f" -- Unable to retrieve TruSTAR summary data: {e}"
220
+
221
+ try:
222
+ trustar_parser.parse_indicator_summary(attribute['value'], summary, metadata)
223
+ except Exception:
224
+ return misperrors
225
+
226
+ return trustar_parser.get_results()
227
+
228
+
229
+ def introspection():
230
+ return mispattributes
231
+
232
+
233
+ def version():
234
+ moduleinfo['config'] = moduleconfig
235
+ return moduleinfo
@@ -0,0 +1,164 @@
1
+ # -*- coding: utf-8 -*-
2
+ import json
3
+ import requests
4
+ from . import check_input_attribute, standard_error_message
5
+ from pymisp import MISPAttribute, MISPEvent, MISPObject
6
+
7
+ misperrors = {'error': 'Error'}
8
+ mispattributes = {'input': ['domain', 'hostname', 'ip-src', 'ip-dst', 'md5', 'sha256', 'url'],
9
+ 'output': ['url', 'filename', 'md5', 'sha256'],
10
+ 'format': 'misp_standard'}
11
+ moduleinfo = {
12
+ 'version': '0.1',
13
+ 'author': 'Christian Studer',
14
+ 'description': 'Query of the URLhaus API to get additional information about the input attribute.',
15
+ 'module-type': ['expansion', 'hover'],
16
+ 'name': 'URLhaus Lookup',
17
+ 'logo': 'urlhaus.png',
18
+ 'requirements': [],
19
+ 'features': 'Module using the new format of modules able to return attributes and objects.\n\nThe module takes one of the attribute type specified as input, and query the URLhaus API with it. If any result is returned by the API, attributes and objects are created accordingly.',
20
+ 'references': ['https://urlhaus.abuse.ch/'],
21
+ 'input': 'A domain, hostname, url, ip, md5 or sha256 attribute.',
22
+ 'output': 'MISP attributes & objects fetched from the result of the URLhaus API query.',
23
+ }
24
+ moduleconfig = []
25
+
26
+ file_keys = ('filename', 'response_size', 'response_md5', 'response_sha256')
27
+ file_relations = ('filename', 'size-in-bytes', 'md5', 'sha256')
28
+ vt_keys = ('result', 'link')
29
+ vt_types = ('text', 'link')
30
+ vt_relations = ('detection-ratio', 'permalink')
31
+
32
+
33
+ class URLhaus():
34
+ def __init__(self):
35
+ super(URLhaus, self).__init__()
36
+ self.misp_event = MISPEvent()
37
+
38
+ @staticmethod
39
+ def _create_vt_object(virustotal):
40
+ vt_object = MISPObject('virustotal-report')
41
+ for key, vt_type, relation in zip(vt_keys, vt_types, vt_relations):
42
+ vt_object.add_attribute(relation, **{'type': vt_type, 'value': virustotal[key]})
43
+ return vt_object
44
+
45
+ def get_result(self):
46
+ event = json.loads(self.misp_event.to_json())
47
+ results = {key: event[key] for key in ('Attribute', 'Object') if (key in event and event[key])}
48
+ return {'results': results}
49
+
50
+ def parse_error(self, query_status):
51
+ if query_status == 'no_results':
52
+ return {'error': f'No results found on URLhaus for this {self.attribute.type} attribute'}
53
+ return {'error': f'Error encountered during the query of URLhaus: {query_status}'}
54
+
55
+
56
+ class HostQuery(URLhaus):
57
+ def __init__(self, attribute):
58
+ super(HostQuery, self).__init__()
59
+ self.attribute = MISPAttribute()
60
+ self.attribute.from_dict(**attribute)
61
+ self.url = 'https://urlhaus-api.abuse.ch/v1/host/'
62
+
63
+ def query_api(self):
64
+ response = requests.post(self.url, data={'host': self.attribute.value}).json()
65
+ if response['query_status'] != 'ok':
66
+ return self.parse_error(response['query_status'])
67
+ if 'urls' in response and response['urls']:
68
+ for url in response['urls']:
69
+ self.misp_event.add_attribute(type='url', value=url['url'])
70
+ return self.get_result()
71
+
72
+
73
+ class PayloadQuery(URLhaus):
74
+ def __init__(self, attribute):
75
+ super(PayloadQuery, self).__init__()
76
+ self.attribute = MISPAttribute()
77
+ self.attribute.from_dict(**attribute)
78
+ self.url = 'https://urlhaus-api.abuse.ch/v1/payload/'
79
+
80
+ def query_api(self):
81
+ hash_type = self.attribute.type
82
+ file_object = MISPObject('file')
83
+ if hasattr(self.attribute, 'object_id') and hasattr(self.attribute, 'event_id') and self.attribute.event_id != '0':
84
+ file_object.id = self.attribute.object_id
85
+ response = requests.post(self.url, data={'{}_hash'.format(hash_type): self.attribute.value}).json()
86
+ if response['query_status'] != 'ok':
87
+ return self.parse_error(response['query_status'])
88
+ other_hash_type = 'md5' if hash_type == 'sha256' else 'sha256'
89
+ for key, relation in zip(('{}_hash'.format(other_hash_type), 'file_size'), (other_hash_type, 'size-in-bytes')):
90
+ if response[key]:
91
+ file_object.add_attribute(relation, **{'type': relation, 'value': response[key]})
92
+ if response['virustotal']:
93
+ vt_object = self._create_vt_object(response['virustotal'])
94
+ file_object.add_reference(vt_object.uuid, 'analyzed-with')
95
+ self.misp_event.add_object(**vt_object)
96
+ _filename_ = 'filename'
97
+ for url in response['urls']:
98
+ attribute = MISPAttribute()
99
+ attribute.from_dict(type='url', value=url['url'])
100
+ self.misp_event.add_attribute(**attribute)
101
+ file_object.add_reference(attribute.uuid, 'retrieved-from')
102
+ if url[_filename_]:
103
+ file_object.add_attribute(_filename_, **{'type': _filename_, 'value': url[_filename_]})
104
+ if any((file_object.attributes, file_object.references)):
105
+ self.misp_event.add_object(**file_object)
106
+ return self.get_result()
107
+
108
+
109
+ class UrlQuery(URLhaus):
110
+ def __init__(self, attribute):
111
+ super(UrlQuery, self).__init__()
112
+ self.attribute = MISPAttribute()
113
+ self.attribute.from_dict(**attribute)
114
+ self.url = 'https://urlhaus-api.abuse.ch/v1/url/'
115
+
116
+ @staticmethod
117
+ def _create_file_object(payload):
118
+ file_object = MISPObject('file')
119
+ for key, relation in zip(file_keys, file_relations):
120
+ if payload[key]:
121
+ file_object.add_attribute(relation, **{'type': relation, 'value': payload[key]})
122
+ return file_object
123
+
124
+ def query_api(self):
125
+ response = requests.post(self.url, data={'url': self.attribute.value}).json()
126
+ if response['query_status'] != 'ok':
127
+ return self.parse_error(response['query_status'])
128
+ if 'payloads' in response and response['payloads']:
129
+ for payload in response['payloads']:
130
+ file_object = self._create_file_object(payload)
131
+ if payload['virustotal']:
132
+ vt_object = self._create_vt_object(payload['virustotal'])
133
+ file_object.add_reference(vt_object.uuid, 'analyzed-with')
134
+ self.misp_event.add_object(**vt_object)
135
+ if any((file_object.attributes, file_object.references)):
136
+ self.misp_event.add_object(**file_object)
137
+ return self.get_result()
138
+
139
+
140
+ _misp_type_mapping = {'url': UrlQuery, 'md5': PayloadQuery, 'sha256': PayloadQuery,
141
+ 'domain': HostQuery, 'hostname': HostQuery,
142
+ 'ip-src': HostQuery, 'ip-dst': HostQuery}
143
+
144
+
145
+ def handler(q=False):
146
+ if q is False:
147
+ return False
148
+ request = json.loads(q)
149
+ if not request.get('attribute') or not check_input_attribute(request['attribute']):
150
+ return {'error': f'{standard_error_message}, which should contain at least a type, a value and an uuid.'}
151
+ attribute = request['attribute']
152
+ if attribute['type'] not in mispattributes['input']:
153
+ return {'error': 'Unsupported attribute type.'}
154
+ urlhaus_parser = _misp_type_mapping[attribute['type']](attribute)
155
+ return urlhaus_parser.query_api()
156
+
157
+
158
+ def introspection():
159
+ return mispattributes
160
+
161
+
162
+ def version():
163
+ moduleinfo['config'] = moduleconfig
164
+ return moduleinfo
@@ -0,0 +1,269 @@
1
+ import json
2
+ import requests
3
+ import logging
4
+ import sys
5
+ import time
6
+
7
+ log = logging.getLogger('urlscan')
8
+ log.setLevel(logging.DEBUG)
9
+ ch = logging.StreamHandler(sys.stdout)
10
+ ch.setLevel(logging.DEBUG)
11
+ formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
12
+ ch.setFormatter(formatter)
13
+ log.addHandler(ch)
14
+
15
+ moduleinfo = {
16
+ 'version': '0.1',
17
+ 'author': 'Dave Johnson',
18
+ 'description': 'An expansion module to query urlscan.io.',
19
+ 'module-type': ['expansion'],
20
+ 'name': 'URLScan Lookup',
21
+ 'logo': 'urlscan.jpg',
22
+ 'requirements': ['An access to the urlscan.io API'],
23
+ 'features': 'This module takes a MISP attribute as input and queries urlscan.io 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.',
24
+ 'references': ['https://urlscan.io/'],
25
+ 'input': 'A domain, hostname or url attribute.',
26
+ 'output': 'MISP attributes mapped from the result of the query on urlscan.io.',
27
+ }
28
+
29
+ moduleconfig = ['apikey']
30
+ misperrors = {'error': 'Error'}
31
+ mispattributes = {
32
+ 'input': ['hostname', 'domain', 'ip-src', 'ip-dst', 'url'],
33
+ 'output': ['hostname', 'domain', 'ip-src', 'ip-dst', 'url', 'text', 'link', 'hash']
34
+ }
35
+
36
+
37
+ def handler(q=False):
38
+ if q is False:
39
+ return False
40
+ request = json.loads(q)
41
+ if not request.get('config') or not request['config'].get('apikey'):
42
+ misperrors['error'] = 'Urlscan apikey is missing'
43
+ return misperrors
44
+ client = urlscanAPI(request['config']['apikey'])
45
+
46
+ r = {'results': []}
47
+
48
+ if 'ip-src' in request:
49
+ r['results'] += lookup_indicator(client, request['ip-src'])
50
+ if 'ip-dst' in request:
51
+ r['results'] += lookup_indicator(client, request['ip-dst'])
52
+ if 'domain' in request:
53
+ r['results'] += lookup_indicator(client, request['domain'])
54
+ if 'hostname' in request:
55
+ r['results'] += lookup_indicator(client, request['hostname'])
56
+ if 'url' in request:
57
+ r['results'] += lookup_indicator(client, request['url'])
58
+
59
+ # Return any errors generated from lookup to the UI and remove duplicates
60
+
61
+ uniq = []
62
+ log.debug(r['results'])
63
+ for item in r['results']:
64
+ log.debug(item)
65
+ if 'error' in item:
66
+ misperrors['error'] = item['error']
67
+ return misperrors
68
+ if item not in uniq:
69
+ uniq.append(item)
70
+ r['results'] = uniq
71
+ return r
72
+
73
+
74
+ def lookup_indicator(client, query):
75
+ result = client.search_url(query)
76
+ log.debug('RESULTS: ' + json.dumps(result))
77
+ r = []
78
+ misp_comment = "{}: Enriched via the urlscan module".format(query)
79
+
80
+ # Determine if the page is reachable
81
+ for request in result['data']['requests']:
82
+ if request['response'].get('failed'):
83
+ if request['response']['failed']['errorText']:
84
+ log.debug('The page could not load')
85
+ r.append(
86
+ {'error': 'Domain could not be resolved: {}'.format(request['response']['failed']['errorText'])})
87
+
88
+ if result.get('page'):
89
+ if result['page'].get('domain'):
90
+ misp_val = result['page']['domain']
91
+ r.append({'types': 'domain',
92
+ 'categories': ['Network activity'],
93
+ 'values': misp_val,
94
+ 'comment': misp_comment})
95
+
96
+ if result['page'].get('ip'):
97
+ misp_val = result['page']['ip']
98
+ r.append({'types': 'ip-dst',
99
+ 'categories': ['Network activity'],
100
+ 'values': misp_val,
101
+ 'comment': misp_comment})
102
+
103
+ if result['page'].get('country'):
104
+ misp_val = 'country: ' + result['page']['country']
105
+ if result['page'].get('city'):
106
+ misp_val += ', city: ' + result['page']['city']
107
+ r.append({'types': 'text',
108
+ 'categories': ['External analysis'],
109
+ 'values': misp_val,
110
+ 'comment': misp_comment})
111
+
112
+ if result['page'].get('asn'):
113
+ misp_val = result['page']['asn']
114
+ r.append({'types': 'AS', 'categories': ['External analysis'], 'values': misp_val, 'comment': misp_comment})
115
+
116
+ if result['page'].get('asnname'):
117
+ misp_val = result['page']['asnname']
118
+ r.append({'types': 'text',
119
+ 'categories': ['External analysis'],
120
+ 'values': misp_val,
121
+ 'comment': misp_comment})
122
+
123
+ if result.get('stats'):
124
+ if result['stats'].get('malicious'):
125
+ log.debug('There is something in results > stats > malicious')
126
+ threat_list = set()
127
+
128
+ if 'matches' in result['meta']['processors']['gsb']['data']:
129
+ for item in result['meta']['processors']['gsb']['data']['matches']:
130
+ if item['threatType']:
131
+ threat_list.add(item['threatType'])
132
+
133
+ threat_list = ', '.join(threat_list)
134
+ log.debug('threat_list values are: \'' + threat_list + '\'')
135
+
136
+ if threat_list:
137
+ misp_val = '{} threat(s) detected'.format(threat_list)
138
+ r.append({'types': 'text',
139
+ 'categories': ['External analysis'],
140
+ 'values': misp_val,
141
+ 'comment': misp_comment})
142
+
143
+ if result.get('lists'):
144
+ if result['lists'].get('urls'):
145
+ for url in result['lists']['urls']:
146
+ url = url.lower()
147
+ if 'office' in url:
148
+ misp_val = "Possible Office-themed phishing"
149
+ elif 'o365' in url or '0365' in url:
150
+ misp_val = "Possible O365-themed phishing"
151
+ elif 'microsoft' in url:
152
+ misp_val = "Possible Microsoft-themed phishing"
153
+ elif 'paypal' in url:
154
+ misp_val = "Possible PayPal-themed phishing"
155
+ elif 'onedrive' in url:
156
+ misp_val = "Possible OneDrive-themed phishing"
157
+ elif 'docusign' in url:
158
+ misp_val = "Possible DocuSign-themed phishing"
159
+ r.append({'types': 'text',
160
+ 'categories': ['External analysis'],
161
+ 'values': misp_val,
162
+ 'comment': misp_comment})
163
+
164
+ if result.get('task'):
165
+ if result['task'].get('reportURL'):
166
+ misp_val = result['task']['reportURL']
167
+ r.append({'types': 'link',
168
+ 'categories': ['External analysis'],
169
+ 'values': misp_val,
170
+ 'comment': misp_comment})
171
+
172
+ if result['task'].get('screenshotURL'):
173
+ image_url = result['task']['screenshotURL']
174
+ r.append({'types': 'link',
175
+ 'categories': ['External analysis'],
176
+ 'values': image_url,
177
+ 'comment': misp_comment})
178
+ # ## TO DO ###
179
+ # ## Add ability to add an in-line screenshot of the target website into an attribute
180
+ # screenshot = requests.get(image_url).content
181
+ # r.append({'types': ['attachment'],
182
+ # 'categories': ['External analysis'],
183
+ # 'values': image_url,
184
+ # 'image': str(base64.b64encode(screenshot), 'utf-8'),
185
+ # 'comment': 'Screenshot of website'})
186
+
187
+ return r
188
+
189
+
190
+ def introspection():
191
+ return mispattributes
192
+
193
+
194
+ def version():
195
+ moduleinfo['config'] = moduleconfig
196
+ return moduleinfo
197
+
198
+
199
+ class urlscanAPI():
200
+ def __init__(self, apikey=None, uuid=None):
201
+ self.key = apikey
202
+ self.uuid = uuid
203
+
204
+ def request(self, query):
205
+ log.debug('From request function with the parameter: ' + query)
206
+ payload = {'url': query}
207
+ headers = {'API-Key': self.key,
208
+ 'Content-Type': "application/json",
209
+ 'Cache-Control': "no-cache"}
210
+
211
+ # Troubleshooting problems with initial search request
212
+ log.debug('PAYLOAD: ' + json.dumps(payload))
213
+ log.debug('HEADERS: ' + json.dumps(headers))
214
+
215
+ search_url_string = "https://urlscan.io/api/v1/scan/"
216
+ response = requests.request("POST",
217
+ search_url_string,
218
+ data=json.dumps(payload),
219
+ headers=headers)
220
+
221
+ # HTTP 400 - Bad Request
222
+ if response.status_code == 400:
223
+ raise Exception('HTTP Error 400 - Bad Request')
224
+
225
+ # HTTP 404 - Not found
226
+ if response.status_code == 404:
227
+ raise Exception('HTTP Error 404 - These are not the droids you\'re looking for')
228
+
229
+ # Any other status code
230
+ if response.status_code != 200:
231
+ raise Exception('HTTP Error ' + str(response.status_code))
232
+
233
+ if response.text:
234
+ response = json.loads(response.content.decode("utf-8"))
235
+ time.sleep(3)
236
+ self.uuid = response['uuid']
237
+
238
+ # Strings for to check for errors on the results page
239
+ # Null response string for any unavailable resources
240
+ null_response_string = '"status": 404'
241
+ # Redirect string accounting for 301/302/303/307/308 status codes
242
+ redirect_string = '"status": 30'
243
+ # Normal response string with 200 status code
244
+ normal_response_string = '"status": 200'
245
+
246
+ results_url_string = "https://urlscan.io/api/v1/result/" + self.uuid
247
+ log.debug('Results URL: ' + results_url_string)
248
+
249
+ # Need to wait for results to process and check if they are valid
250
+ tries = 10
251
+ while tries >= 0:
252
+ results = requests.request("GET", results_url_string)
253
+ log.debug('Made a GET request')
254
+ results = results.content.decode("utf-8")
255
+ # checking if there is a 404 status code and no available resources
256
+ if null_response_string in results and \
257
+ redirect_string not in results and \
258
+ normal_response_string not in results:
259
+ log.debug('Results not processed. Please check again later.')
260
+ time.sleep(3)
261
+ tries -= 1
262
+ else:
263
+ return json.loads(results)
264
+
265
+ raise Exception('Results contained a 404 status error and could not be processed.')
266
+
267
+ def search_url(self, query):
268
+ log.debug('From search_url with parameter: ' + query)
269
+ return self.request(query)