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,628 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Expansion module integrating with VMware NSX Defender.
4
+ """
5
+ import argparse
6
+ import base64
7
+ import configparser
8
+ import datetime
9
+ import hashlib
10
+ import io
11
+ import ipaddress
12
+ import json
13
+ import logging
14
+ import pymisp
15
+ import sys
16
+ import vt
17
+ import zipfile
18
+ from urllib import parse
19
+ from typing import Any, Dict, List, Optional, Tuple, Union
20
+
21
+ import tau_clients
22
+ from tau_clients import exceptions
23
+ from tau_clients import nsx_defender
24
+
25
+
26
+ logger = logging.getLogger("vmware_nsx")
27
+ logger.setLevel(logging.DEBUG)
28
+
29
+ misperrors = {
30
+ "error": "Error",
31
+ }
32
+
33
+ mispattributes = {
34
+ "input": [
35
+ "attachment",
36
+ "malware-sample",
37
+ "url",
38
+ "md5",
39
+ "sha1",
40
+ "sha256",
41
+ ],
42
+ "format": "misp_standard",
43
+ }
44
+
45
+ moduleinfo = {
46
+ 'version': '0.2',
47
+ 'author': 'Jason Zhang, Stefano Ortolani',
48
+ 'description': 'Module to enrich a file or URL with VMware NSX Defender.',
49
+ 'module-type': ['expansion', 'hover'],
50
+ 'name': 'VMware NSX Defender Enrich',
51
+ 'logo': 'vmware_nsx.png',
52
+ 'requirements': ['The module requires a VMware NSX Defender Analysis `api_token` and `key`.'],
53
+ 'features': 'This module takes an IoC such as file hash, file attachment, malware-sample or url as input to query VMware NSX Defender.\n\nThe IoC is then enriched with data from VMware NSX Defender.',
54
+ 'references': ['https://www.vmware.com'],
55
+ 'input': 'File hash, attachment or URL to be enriched with VMware NSX Defender.',
56
+ 'output': 'Objects and tags generated by VMware NSX Defender.',
57
+ }
58
+
59
+ moduleconfig = [
60
+ "analysis_url", # optional, defaults to hard-coded values
61
+ "analysis_verify_ssl", # optional, defaults to True
62
+ "analysis_key", # required
63
+ "analysis_api_token", # required
64
+ "vt_key", # optional
65
+ "misp_url", # optional
66
+ "misp_verify_ssl", # optional, defaults to True
67
+ "misp_key", # optional
68
+ ]
69
+
70
+ DEFAULT_ZIP_PASSWORD = b"infected"
71
+
72
+ DEFAULT_ENDPOINT = tau_clients.NSX_DEFENDER_DC_WESTUS
73
+
74
+ WORKFLOW_COMPLETE_TAG = "workflow:state='complete'"
75
+
76
+ WORKFLOW_INCOMPLETE_TAG = "workflow:state='incomplete'"
77
+
78
+ VT_DOWNLOAD_TAG = "vt:download"
79
+
80
+ GALAXY_ATTACK_PATTERNS_UUID = "c4e851fa-775f-11e7-8163-b774922098cd"
81
+
82
+
83
+ class ResultParser:
84
+ """This is a parser to extract *basic* information from a result dictionary."""
85
+
86
+ def __init__(self, techniques_galaxy: Optional[Dict[str, str]] = None):
87
+ """Constructor."""
88
+ self.techniques_galaxy = techniques_galaxy or {}
89
+
90
+ def parse(self, analysis_link: str, result: Dict[str, Any]) -> pymisp.MISPEvent:
91
+ """
92
+ Parse the analysis result into a MISP event.
93
+
94
+ :param str analysis_link: the analysis link
95
+ :param dict[str, any] result: the JSON returned by the analysis client.
96
+ :rtype: pymisp.MISPEvent
97
+ :return: a MISP event
98
+ """
99
+ misp_event = pymisp.MISPEvent()
100
+
101
+ # Add analysis subject info
102
+ if "url" in result["analysis_subject"]:
103
+ o = pymisp.MISPObject("url")
104
+ o.add_attribute("url", result["analysis_subject"]["url"])
105
+ else:
106
+ o = pymisp.MISPObject("file")
107
+ o.add_attribute("md5", type="md5", value=result["analysis_subject"]["md5"])
108
+ o.add_attribute("sha1", type="sha1", value=result["analysis_subject"]["sha1"])
109
+ o.add_attribute("sha256", type="sha256", value=result["analysis_subject"]["sha256"])
110
+ o.add_attribute(
111
+ "mimetype",
112
+ category="Payload delivery",
113
+ type="mime-type",
114
+ value=result["analysis_subject"]["mime_type"]
115
+ )
116
+ misp_event.add_object(o)
117
+
118
+ # Add HTTP requests from url analyses
119
+ network_dict = result.get("report", {}).get("analysis", {}).get("network", {})
120
+ for request in network_dict.get("requests", []):
121
+ if not request["url"] and not request["ip"]:
122
+ continue
123
+ o = pymisp.MISPObject(name="http-request")
124
+ o.add_attribute("method", "GET")
125
+ if request["url"]:
126
+ parsed_uri = parse.urlparse(request["url"])
127
+ o.add_attribute("host", parsed_uri.netloc)
128
+ o.add_attribute("uri", request["url"])
129
+ if request["ip"]:
130
+ o.add_attribute("ip-dst", request["ip"])
131
+ misp_event.add_object(o)
132
+
133
+ # Add network behaviors from files
134
+ for subject in result.get("report", {}).get("analysis_subjects", []):
135
+
136
+ # Add DNS requests
137
+ for dns_query in subject.get("dns_queries", []):
138
+ hostname = dns_query.get("hostname")
139
+ # Skip if it is an IP address
140
+ try:
141
+ if hostname == "wpad" or hostname == "localhost":
142
+ continue
143
+ # Invalid hostname, e.g., hostname: ZLKKJRPY or 2.2.0.10.in-addr.arpa.
144
+ if "." not in hostname or hostname[-1] == ".":
145
+ continue
146
+ _ = ipaddress.ip_address(hostname)
147
+ continue
148
+ except ValueError:
149
+ pass
150
+
151
+ o = pymisp.MISPObject(name="domain-ip")
152
+ o.add_attribute("hostname", type="hostname", value=hostname)
153
+ for ip in dns_query.get("results", []):
154
+ o.add_attribute("ip", type="ip-dst", value=ip)
155
+
156
+ misp_event.add_object(o)
157
+
158
+ # Add HTTP conversations (as network connection and as http request)
159
+ for http_conversation in subject.get("http_conversations", []):
160
+ o = pymisp.MISPObject(name="network-connection")
161
+ o.add_attribute("ip-src", http_conversation["src_ip"])
162
+ o.add_attribute("ip-dst", http_conversation["dst_ip"])
163
+ o.add_attribute("src-port", http_conversation["src_port"])
164
+ o.add_attribute("dst-port", http_conversation["dst_port"])
165
+ o.add_attribute("hostname-dst", http_conversation["dst_host"])
166
+ o.add_attribute("layer3-protocol", "IP")
167
+ o.add_attribute("layer4-protocol", "TCP")
168
+ o.add_attribute("layer7-protocol", "HTTP")
169
+ misp_event.add_object(o)
170
+
171
+ method, path, http_version = http_conversation["url"].split(" ")
172
+ if http_conversation["dst_port"] == 80:
173
+ uri = "http://{}{}".format(http_conversation["dst_host"], path)
174
+ else:
175
+ uri = "http://{}:{}{}".format(
176
+ http_conversation["dst_host"],
177
+ http_conversation["dst_port"],
178
+ path
179
+ )
180
+ o = pymisp.MISPObject(name="http-request")
181
+ o.add_attribute("host", http_conversation["dst_host"])
182
+ o.add_attribute("method", method)
183
+ o.add_attribute("uri", uri)
184
+ o.add_attribute("ip-dst", http_conversation["dst_ip"])
185
+ misp_event.add_object(o)
186
+
187
+ # Add sandbox info like score and sandbox type
188
+ o = pymisp.MISPObject(name="sandbox-report")
189
+ sandbox_type = "saas" if tau_clients.is_task_hosted(analysis_link) else "on-premise"
190
+ o.add_attribute("score", result["score"])
191
+ o.add_attribute("sandbox-type", sandbox_type)
192
+ o.add_attribute("{}-sandbox".format(sandbox_type), "vmware-nsx-defender")
193
+ o.add_attribute("permalink", analysis_link)
194
+ misp_event.add_object(o)
195
+
196
+ # Add behaviors
197
+ # Check if its not empty first, as at least one attribute has to be set for sb-signature object
198
+ if result.get("malicious_activity", []):
199
+ o = pymisp.MISPObject(name="sb-signature")
200
+ o.add_attribute("software", "VMware NSX Defender")
201
+ for activity in result.get("malicious_activity", []):
202
+ a = pymisp.MISPAttribute()
203
+ a.from_dict(type="text", value=activity)
204
+ o.add_attribute("signature", **a)
205
+ misp_event.add_object(o)
206
+
207
+ # Add mitre techniques
208
+ for techniques in result.get("activity_to_mitre_techniques", {}).values():
209
+ for technique in techniques:
210
+ for misp_technique_id, misp_technique_name in self.techniques_galaxy.items():
211
+ if technique["id"].casefold() in misp_technique_id.casefold():
212
+ # If report details a sub-technique, trust the match
213
+ # Otherwise trust it only if the MISP technique is not a sub-technique
214
+ if "." in technique["id"] or "." not in misp_technique_id:
215
+ misp_event.add_tag(misp_technique_name)
216
+ break
217
+ return misp_event
218
+
219
+
220
+ def _parse_submission_response(response: Dict[str, Any]) -> Tuple[str, List[str]]:
221
+ """
222
+ Parse the response from "submit_*" methods.
223
+
224
+ :param dict[str, any] response: the client response
225
+ :rtype: tuple(str, list[str])
226
+ :return: the task_uuid and whether the analysis is available
227
+ :raises ValueError: in case of any error
228
+ """
229
+ task_uuid = response.get("task_uuid")
230
+ if not task_uuid:
231
+ raise ValueError("Submission failed, unable to process the data")
232
+ if response.get("score") is not None:
233
+ tags = [WORKFLOW_COMPLETE_TAG]
234
+ else:
235
+ tags = [WORKFLOW_INCOMPLETE_TAG]
236
+ return task_uuid, tags
237
+
238
+
239
+ def _unzip(zipped_data: bytes, password: bytes = DEFAULT_ZIP_PASSWORD) -> bytes:
240
+ """
241
+ Unzip the data.
242
+
243
+ :param bytes zipped_data: the zipped data
244
+ :param bytes password: the password
245
+ :rtype: bytes
246
+ :return: the unzipped data
247
+ :raises ValueError: in case of any error
248
+ """
249
+ try:
250
+ data_file_object = io.BytesIO(zipped_data)
251
+ with zipfile.ZipFile(data_file_object) as zip_file:
252
+ sample_hash_name = zip_file.namelist()[0]
253
+ return zip_file.read(sample_hash_name, password)
254
+ except (IOError, ValueError) as e:
255
+ raise ValueError(str(e))
256
+
257
+
258
+ def _download_from_vt(client: vt.Client, file_hash: str) -> bytes:
259
+ """
260
+ Download file from VT.
261
+
262
+ :param vt.Client client: the VT client
263
+ :param str file_hash: the file hash
264
+ :rtype: bytes
265
+ :return: the downloaded data
266
+ :raises ValueError: in case of any error
267
+ """
268
+ try:
269
+ buffer = io.BytesIO()
270
+ client.download_file(file_hash, buffer)
271
+ buffer.seek(0, 0)
272
+ return buffer.read()
273
+ except (IOError, vt.APIError) as e:
274
+ raise ValueError(str(e))
275
+ finally:
276
+ # vt.Client likes to free resources at shutdown, and it can be used as context to ease that
277
+ # Since the structure of the module does not play well with how MISP modules are organized
278
+ # let's play nice and close connections pro-actively (opened by "download_file")
279
+ if client:
280
+ client.close()
281
+
282
+
283
+ def _get_analysis_tags(
284
+ clients: Dict[str, nsx_defender.AnalysisClient],
285
+ task_uuid: str,
286
+ ) -> List[str]:
287
+ """
288
+ Get the analysis tags of a task.
289
+
290
+ :param dict[str, nsx_defender.AnalysisClient] clients: the analysis clients
291
+ :param str task_uuid: the task uuid
292
+ :rtype: list[str]
293
+ :return: the analysis tags
294
+ :raises exceptions.ApiError: in case of client errors
295
+ :raises exceptions.CommunicationError: in case of client communication errors
296
+ """
297
+ client = clients[DEFAULT_ENDPOINT]
298
+ response = client.get_analysis_tags(task_uuid)
299
+ tags = set([])
300
+ for tag in response.get("analysis_tags", []):
301
+ tag_header = None
302
+ tag_type = tag["data"]["type"]
303
+ if tag_type == "av_family":
304
+ tag_header = "av-fam"
305
+ elif tag_type == "av_class":
306
+ tag_header = "av-cls"
307
+ elif tag_type == "lastline_malware":
308
+ tag_header = "nsx"
309
+ if tag_header:
310
+ tags.add("{}:{}".format(tag_header, tag["data"]["value"]))
311
+ return sorted(tags)
312
+
313
+
314
+ def _get_latest_analysis(
315
+ clients: Dict[str, nsx_defender.AnalysisClient],
316
+ file_hash: str,
317
+ ) -> Optional[str]:
318
+ """
319
+ Get the latest analysis.
320
+
321
+ :param dict[str, nsx_defender.AnalysisClient] clients: the analysis clients
322
+ :param str file_hash: the hash of the file
323
+ :rtype: str|None
324
+ :return: the task uuid if present, None otherwise
325
+ :raises exceptions.ApiError: in case of client errors
326
+ :raises exceptions.CommunicationError: in case of client communication errors
327
+ """
328
+ def _parse_expiration(task_info: Dict[str, str]) -> datetime.datetime:
329
+ """
330
+ Parse expiration time of a task
331
+
332
+ :param dict[str, str] task_info: the task
333
+ :rtype: datetime.datetime
334
+ :return: the parsed datetime object
335
+ """
336
+ return datetime.datetime.strptime(task_info["expires"], "%Y-%m-%d %H:%M:%S")
337
+ results = []
338
+ for data_center, client in clients.items():
339
+ response = client.query_file_hash(file_hash=file_hash)
340
+ for task in response.get("tasks", []):
341
+ results.append(task)
342
+ if results:
343
+ return sorted(results, key=_parse_expiration)[-1]["task_uuid"]
344
+ else:
345
+ return None
346
+
347
+
348
+ def _get_mitre_techniques_galaxy(misp_client: pymisp.PyMISP) -> Dict[str, str]:
349
+ """
350
+ Get all the MITRE techniques from the MISP galaxy.
351
+
352
+ :param pymisp.PyMISP misp_client: the MISP client
353
+ :rtype: dict[str, str]
354
+ :return: all techniques indexed by their id
355
+ """
356
+ galaxy_attack_patterns = misp_client.get_galaxy(
357
+ galaxy=GALAXY_ATTACK_PATTERNS_UUID,
358
+ withCluster=True,
359
+ pythonify=True,
360
+ )
361
+ ret = {}
362
+ for cluster in galaxy_attack_patterns.clusters:
363
+ ret[cluster.value] = cluster.tag_name
364
+ return ret
365
+
366
+
367
+ def introspection() -> Dict[str, Union[str, List[str]]]:
368
+ """
369
+ Implement interface.
370
+
371
+ :return: the supported MISP attributes
372
+ :rtype: dict[str, list[str]]
373
+ """
374
+ return mispattributes
375
+
376
+
377
+ def version() -> Dict[str, Union[str, List[str]]]:
378
+ """
379
+ Implement interface.
380
+
381
+ :return: the module config inside another dictionary
382
+ :rtype: dict[str, list[str]]
383
+ """
384
+ moduleinfo["config"] = moduleconfig
385
+ return moduleinfo
386
+
387
+
388
+ def handler(q: Union[bool, str] = False) -> Union[bool, Dict[str, Any]]:
389
+ """
390
+ Implement interface.
391
+
392
+ :param bool|str q: the input received
393
+ :rtype: bool|dict[str, any]
394
+ """
395
+ if q is False:
396
+ return False
397
+
398
+ request = json.loads(q)
399
+ config = request.get("config", {})
400
+
401
+ # Load the client to connect to VMware NSX ATA (hard-fail)
402
+ try:
403
+ analysis_url = config.get("analysis_url")
404
+ login_params = {
405
+ "key": config["analysis_key"],
406
+ "api_token": config["analysis_api_token"],
407
+ }
408
+ # If 'analysis_url' is specified we are connecting on-premise
409
+ if analysis_url:
410
+ analysis_clients = {
411
+ DEFAULT_ENDPOINT: nsx_defender.AnalysisClient(
412
+ api_url=analysis_url,
413
+ login_params=login_params,
414
+ verify_ssl=bool(config.get("analysis_verify_ssl", True)),
415
+ )
416
+ }
417
+ logger.info("Connected NSX AnalysisClient to on-premise infrastructure")
418
+ else:
419
+ analysis_clients = {
420
+ data_center: nsx_defender.AnalysisClient(
421
+ api_url=tau_clients.NSX_DEFENDER_ANALYSIS_URLS[data_center],
422
+ login_params=login_params,
423
+ verify_ssl=bool(config.get("analysis_verify_ssl", True)),
424
+ ) for data_center in [
425
+ tau_clients.NSX_DEFENDER_DC_WESTUS,
426
+ tau_clients.NSX_DEFENDER_DC_NLEMEA,
427
+ ]
428
+ }
429
+ logger.info("Connected NSX AnalysisClient to hosted infrastructure")
430
+ except KeyError as ke:
431
+ logger.error("Integration with VMware NSX ATA failed to connect: %s", str(ke))
432
+ return {"error": "Error connecting to VMware NSX ATA: {}".format(ke)}
433
+
434
+ # Load the client to connect to MISP (soft-fail)
435
+ try:
436
+ misp_client = pymisp.PyMISP(
437
+ url=config["misp_url"],
438
+ key=config["misp_key"],
439
+ ssl=bool(config.get("misp_verify_ssl", True)),
440
+ )
441
+ except (KeyError, pymisp.PyMISPError):
442
+ logger.error("Integration with pyMISP disabled: no MITRE techniques tags")
443
+ misp_client = None
444
+
445
+ # Load the client to connect to VT (soft-fail)
446
+ try:
447
+ vt_client = vt.Client(apikey=config["vt_key"])
448
+ except (KeyError, ValueError):
449
+ logger.error("Integration with VT disabled: no automatic download of samples")
450
+ vt_client = None
451
+
452
+ # Decode and issue the request
453
+ try:
454
+ if request["attribute"]["type"] == "url":
455
+ sample_url = request["attribute"]["value"]
456
+ response = analysis_clients[DEFAULT_ENDPOINT].submit_url(sample_url)
457
+ task_uuid, tags = _parse_submission_response(response)
458
+ else:
459
+ if request["attribute"]["type"] == "malware-sample":
460
+ # Raise TypeError
461
+ file_data = _unzip(base64.b64decode(request["attribute"]["data"]))
462
+ file_name = request["attribute"]["value"].split("|", 1)[0]
463
+ hash_value = hashlib.sha1(file_data).hexdigest()
464
+ elif request["attribute"]["type"] == "attachment":
465
+ # Raise TypeError
466
+ file_data = base64.b64decode(request["attribute"]["data"])
467
+ file_name = request["attribute"].get("value")
468
+ hash_value = hashlib.sha1(file_data).hexdigest()
469
+ else:
470
+ hash_value = request["attribute"]["value"]
471
+ file_data = None
472
+ file_name = "{}.bin".format(hash_value)
473
+ # Check whether we have a task for that file
474
+ tags = []
475
+ task_uuid = _get_latest_analysis(analysis_clients, hash_value)
476
+ if not task_uuid:
477
+ # If we have no analysis, download the sample from VT
478
+ if not file_data:
479
+ if not vt_client:
480
+ raise ValueError("No file available locally and VT is disabled")
481
+ file_data = _download_from_vt(vt_client, hash_value)
482
+ tags.append(VT_DOWNLOAD_TAG)
483
+ # ... and submit it (_download_from_vt fails if no sample availabe)
484
+ response = analysis_clients[DEFAULT_ENDPOINT].submit_file(file_data, file_name)
485
+ task_uuid, _tags = _parse_submission_response(response)
486
+ tags.extend(_tags)
487
+ except KeyError as e:
488
+ logger.error("Error parsing input: %s", request["attribute"])
489
+ return {"error": "Error parsing input: {}".format(e)}
490
+ except TypeError as e:
491
+ logger.error("Error decoding input: %s", request["attribute"])
492
+ return {"error": "Error decoding input: {}".format(e)}
493
+ except ValueError as e:
494
+ logger.error("Error processing input: %s", request["attribute"])
495
+ return {"error": "Error processing input: {}".format(e)}
496
+ except (exceptions.CommunicationError, exceptions.ApiError) as e:
497
+ logger.error("Error issuing API call: %s", str(e))
498
+ return {"error": "Error issuing API call: {}".format(e)}
499
+ else:
500
+ analysis_link = tau_clients.get_task_link(
501
+ uuid=task_uuid,
502
+ analysis_url=analysis_clients[DEFAULT_ENDPOINT].base,
503
+ prefer_load_balancer=True,
504
+ )
505
+
506
+ # Return partial results if the analysis has yet to terminate
507
+ try:
508
+ tags.extend(_get_analysis_tags(analysis_clients, task_uuid))
509
+ report = analysis_clients[DEFAULT_ENDPOINT].get_result(task_uuid)
510
+ except (exceptions.CommunicationError, exceptions.ApiError) as e:
511
+ logger.error("Error retrieving the report: %s", str(e))
512
+ return {
513
+ "results": {
514
+ "types": "link",
515
+ "categories": ["External analysis"],
516
+ "values": analysis_link,
517
+ "tags": tags,
518
+ }
519
+ }
520
+
521
+ # Return the enrichment
522
+ try:
523
+ techniques_galaxy = None
524
+ if misp_client:
525
+ techniques_galaxy = _get_mitre_techniques_galaxy(misp_client)
526
+ result_parser = ResultParser(techniques_galaxy=techniques_galaxy)
527
+ misp_event = result_parser.parse(analysis_link, report)
528
+ for tag in tags:
529
+ if tag not in frozenset([WORKFLOW_COMPLETE_TAG]):
530
+ misp_event.add_tag(tag)
531
+ return {
532
+ "results": {
533
+ key: json.loads(misp_event.to_json())[key]
534
+ for key in ("Attribute", "Object", "Tag")
535
+ if (key in misp_event and misp_event[key])
536
+ }
537
+ }
538
+ except pymisp.PyMISPError as e:
539
+ logger.error("Error parsing the report: %s", str(e))
540
+ return {"error": "Error parsing the report: {}".format(e)}
541
+
542
+
543
+ def main():
544
+ """Main function used to test basic functionalities of the module."""
545
+ parser = argparse.ArgumentParser()
546
+ parser.add_argument(
547
+ "-c",
548
+ "--config-file",
549
+ dest="config_file",
550
+ required=True,
551
+ help="the configuration file used for testing",
552
+ )
553
+ parser.add_argument(
554
+ "-t",
555
+ "--test-attachment",
556
+ dest="test_attachment",
557
+ default=None,
558
+ help="the path to a test attachment",
559
+ )
560
+ args = parser.parse_args()
561
+ conf = configparser.ConfigParser()
562
+ conf.read(args.config_file)
563
+ config = {
564
+ "analysis_verify_ssl": conf.getboolean("analysis", "analysis_verify_ssl"),
565
+ "analysis_key": conf.get("analysis", "analysis_key"),
566
+ "analysis_api_token": conf.get("analysis", "analysis_api_token"),
567
+ "vt_key": conf.get("vt", "vt_key"),
568
+ "misp_url": conf.get("misp", "misp_url"),
569
+ "misp_verify_ssl": conf.getboolean("misp", "misp_verify_ssl"),
570
+ "misp_key": conf.get("misp", "misp_key"),
571
+ }
572
+
573
+ # TEST 1: submit a URL
574
+ j = json.dumps(
575
+ {
576
+ "config": config,
577
+ "attribute": {
578
+ "type": "url",
579
+ "value": "https://www.google.com",
580
+ }
581
+ }
582
+ )
583
+ print(json.dumps(handler(j), indent=4, sort_keys=True))
584
+
585
+ # TEST 2: submit a file attachment
586
+ if args.test_attachment:
587
+ with open(args.test_attachment, "rb") as f:
588
+ data = f.read()
589
+ j = json.dumps(
590
+ {
591
+ "config": config,
592
+ "attribute": {
593
+ "type": "attachment",
594
+ "value": "test.docx",
595
+ "data": base64.b64encode(data).decode("utf-8"),
596
+ }
597
+ }
598
+ )
599
+ print(json.dumps(handler(j), indent=4, sort_keys=True))
600
+
601
+ # TEST 3: submit a file hash that is known by NSX ATA
602
+ j = json.dumps(
603
+ {
604
+ "config": config,
605
+ "attribute": {
606
+ "type": "md5",
607
+ "value": "002c56165a0e78369d0e1023ce044bf0",
608
+ }
609
+ }
610
+ )
611
+ print(json.dumps(handler(j), indent=4, sort_keys=True))
612
+
613
+ # TEST 4 : submit a file hash that is NOT known byt NSX ATA
614
+ j = json.dumps(
615
+ {
616
+ "config": config,
617
+ "attribute": {
618
+ "type": "sha1",
619
+ "value": "2aac25ecdccf87abf6f1651ef2ffb30fcf732250",
620
+ }
621
+ }
622
+ )
623
+ print(json.dumps(handler(j), indent=4, sort_keys=True))
624
+ return 0
625
+
626
+
627
+ if __name__ == "__main__":
628
+ sys.exit(main())