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.
- misp_modules/__init__.py +379 -0
- misp_modules/helpers/__init__.py +1 -0
- misp_modules/helpers/cache.py +84 -0
- misp_modules/lib/__init__.py +4 -0
- misp_modules/lib/_vmray/__init__.py +0 -0
- misp_modules/lib/_vmray/parser.py +1417 -0
- misp_modules/lib/_vmray/rest_api.py +148 -0
- misp_modules/lib/cof2misp/LICENSE-2.0.txt +202 -0
- misp_modules/lib/cof2misp/__init__.py +0 -0
- misp_modules/lib/cof2misp/cof.py +165 -0
- misp_modules/lib/joe_mapping.py +114 -0
- misp_modules/lib/joe_parser.py +573 -0
- misp_modules/lib/lastline_api.py +841 -0
- misp_modules/lib/qintel_helper.py +263 -0
- misp_modules/lib/stix2misp.py +2080 -0
- misp_modules/lib/stix2misp_mapping.py +460 -0
- misp_modules/lib/synonymsToTagNames.json +1 -0
- misp_modules/lib/vt_graph_parser/__init__.py +8 -0
- misp_modules/lib/vt_graph_parser/errors.py +20 -0
- misp_modules/lib/vt_graph_parser/helpers/__init__.py +7 -0
- misp_modules/lib/vt_graph_parser/helpers/parsers.py +88 -0
- misp_modules/lib/vt_graph_parser/helpers/rules.py +304 -0
- misp_modules/lib/vt_graph_parser/helpers/wrappers.py +58 -0
- misp_modules/lib/vt_graph_parser/importers/__init__.py +7 -0
- misp_modules/lib/vt_graph_parser/importers/base.py +98 -0
- misp_modules/lib/vt_graph_parser/importers/pymisp_response.py +73 -0
- misp_modules/modules/__init__.py +4 -0
- misp_modules/modules/action_mod/__init__.py +1 -0
- misp_modules/modules/action_mod/_utils/__init__.py +1 -0
- misp_modules/modules/action_mod/_utils/utils.py +70 -0
- misp_modules/modules/action_mod/mattermost.py +113 -0
- misp_modules/modules/action_mod/slack.py +96 -0
- misp_modules/modules/action_mod/testaction.py +68 -0
- misp_modules/modules/expansion/__init__.py +36 -0
- misp_modules/modules/expansion/_dnsdb_query/COPYRIGHT +27 -0
- misp_modules/modules/expansion/_dnsdb_query/LICENSE +202 -0
- misp_modules/modules/expansion/_dnsdb_query/README.md +162 -0
- misp_modules/modules/expansion/_dnsdb_query/__init__.py +0 -0
- misp_modules/modules/expansion/_dnsdb_query/dnsdb_query.py +327 -0
- misp_modules/modules/expansion/_ransomcoindb/__init__.py +0 -0
- misp_modules/modules/expansion/_ransomcoindb/ransomcoindb.py +96 -0
- misp_modules/modules/expansion/_vulnerability_parser/__init__.py +0 -0
- misp_modules/modules/expansion/_vulnerability_parser/vulnerability_parser.py +118 -0
- misp_modules/modules/expansion/abuseipdb.py +146 -0
- misp_modules/modules/expansion/apiosintds.py +365 -0
- misp_modules/modules/expansion/apivoid.py +133 -0
- misp_modules/modules/expansion/assemblyline_query.py +179 -0
- misp_modules/modules/expansion/assemblyline_submit.py +100 -0
- misp_modules/modules/expansion/backscatter_io.py +84 -0
- misp_modules/modules/expansion/btc_scam_check.py +54 -0
- misp_modules/modules/expansion/btc_steroids.py +238 -0
- misp_modules/modules/expansion/censys_enrich.py +287 -0
- misp_modules/modules/expansion/circl_passivedns.py +85 -0
- misp_modules/modules/expansion/circl_passivessl.py +113 -0
- misp_modules/modules/expansion/clamav.py +129 -0
- misp_modules/modules/expansion/cluster25_expand.py +239 -0
- misp_modules/modules/expansion/countrycode.py +69 -0
- misp_modules/modules/expansion/cpe.py +140 -0
- misp_modules/modules/expansion/crowdsec.py +235 -0
- misp_modules/modules/expansion/crowdstrike_falcon.py +148 -0
- misp_modules/modules/expansion/cuckoo_submit.py +161 -0
- misp_modules/modules/expansion/cve.py +57 -0
- misp_modules/modules/expansion/cve_advanced.py +185 -0
- misp_modules/modules/expansion/cytomic_orion.py +196 -0
- misp_modules/modules/expansion/dbl_spamhaus.py +78 -0
- misp_modules/modules/expansion/dns.py +71 -0
- misp_modules/modules/expansion/docx_enrich.py +71 -0
- misp_modules/modules/expansion/domaintools.py +290 -0
- misp_modules/modules/expansion/eql.py +91 -0
- misp_modules/modules/expansion/eupi.py +86 -0
- misp_modules/modules/expansion/extract_url_components.py +83 -0
- misp_modules/modules/expansion/farsight_passivedns.py +243 -0
- misp_modules/modules/expansion/geoip_asn.py +75 -0
- misp_modules/modules/expansion/geoip_city.py +75 -0
- misp_modules/modules/expansion/geoip_country.py +73 -0
- misp_modules/modules/expansion/google_safe_browsing.py +86 -0
- misp_modules/modules/expansion/google_search.py +57 -0
- misp_modules/modules/expansion/google_threat_intelligence.py +453 -0
- misp_modules/modules/expansion/greynoise.py +346 -0
- misp_modules/modules/expansion/hashdd.py +53 -0
- misp_modules/modules/expansion/hashlookup.py +118 -0
- misp_modules/modules/expansion/hibp.py +62 -0
- misp_modules/modules/expansion/html_to_markdown.py +63 -0
- misp_modules/modules/expansion/hyasinsight.py +881 -0
- misp_modules/modules/expansion/intel471.py +73 -0
- misp_modules/modules/expansion/intelmq_eventdb.py.experimental +67 -0
- misp_modules/modules/expansion/ip2locationio.py +90 -0
- misp_modules/modules/expansion/ipasn.py +70 -0
- misp_modules/modules/expansion/ipinfo.py +112 -0
- misp_modules/modules/expansion/ipqs_fraud_and_risk_scoring.py +633 -0
- misp_modules/modules/expansion/iprep.py +98 -0
- misp_modules/modules/expansion/jinja_template_rendering.py +54 -0
- misp_modules/modules/expansion/joesandbox_query.py +91 -0
- misp_modules/modules/expansion/joesandbox_submit.py +147 -0
- misp_modules/modules/expansion/lastline_query.py +148 -0
- misp_modules/modules/expansion/lastline_submit.py +180 -0
- misp_modules/modules/expansion/macaddress_io.py +130 -0
- misp_modules/modules/expansion/macvendors.py +55 -0
- misp_modules/modules/expansion/malshare_upload.py +107 -0
- misp_modules/modules/expansion/malwarebazaar.py +67 -0
- misp_modules/modules/expansion/mcafee_insights_enrich.py +249 -0
- misp_modules/modules/expansion/mmdb_lookup.py +138 -0
- misp_modules/modules/expansion/module.py.skeleton +44 -0
- misp_modules/modules/expansion/mwdb.py +152 -0
- misp_modules/modules/expansion/ocr_enrich.py +74 -0
- misp_modules/modules/expansion/ods_enrich.py +71 -0
- misp_modules/modules/expansion/odt_enrich.py +61 -0
- misp_modules/modules/expansion/onyphe.py +243 -0
- misp_modules/modules/expansion/onyphe_full.py +387 -0
- misp_modules/modules/expansion/otx.py +167 -0
- misp_modules/modules/expansion/passive_ssh.py +150 -0
- misp_modules/modules/expansion/passivetotal.py +358 -0
- misp_modules/modules/expansion/pdf_enrich.py +58 -0
- misp_modules/modules/expansion/pptx_enrich.py +65 -0
- misp_modules/modules/expansion/qintel_qsentry.py +229 -0
- misp_modules/modules/expansion/qrcode.py +99 -0
- misp_modules/modules/expansion/ransomcoindb.py +82 -0
- misp_modules/modules/expansion/rbl.py +126 -0
- misp_modules/modules/expansion/recordedfuture.py +544 -0
- misp_modules/modules/expansion/reversedns.py +77 -0
- misp_modules/modules/expansion/securitytrails.py +571 -0
- misp_modules/modules/expansion/shodan.py +244 -0
- misp_modules/modules/expansion/sigma_queries.py +61 -0
- misp_modules/modules/expansion/sigma_syntax_validator.py +49 -0
- misp_modules/modules/expansion/sigmf_expand.py +303 -0
- misp_modules/modules/expansion/socialscan.py +108 -0
- misp_modules/modules/expansion/sophoslabs_intelix.py +146 -0
- misp_modules/modules/expansion/sourcecache.py +57 -0
- misp_modules/modules/expansion/stairwell.py +156 -0
- misp_modules/modules/expansion/stix2_pattern_syntax_validator.py +56 -0
- misp_modules/modules/expansion/threatcrowd.py +170 -0
- misp_modules/modules/expansion/threatfox.py +75 -0
- misp_modules/modules/expansion/threatminer.py +152 -0
- misp_modules/modules/expansion/triage_submit.py +119 -0
- misp_modules/modules/expansion/trustar_enrich.py +235 -0
- misp_modules/modules/expansion/urlhaus.py +164 -0
- misp_modules/modules/expansion/urlscan.py +269 -0
- misp_modules/modules/expansion/variotdbs.py +133 -0
- misp_modules/modules/expansion/virustotal.py +311 -0
- misp_modules/modules/expansion/virustotal_public.py +272 -0
- misp_modules/modules/expansion/virustotal_upload.py +91 -0
- misp_modules/modules/expansion/vmray_submit.py +161 -0
- misp_modules/modules/expansion/vmware_nsx.py +628 -0
- misp_modules/modules/expansion/vulndb.py +292 -0
- misp_modules/modules/expansion/vulnerability_lookup.py +324 -0
- misp_modules/modules/expansion/vulners.py +81 -0
- misp_modules/modules/expansion/vysion.py +221 -0
- misp_modules/modules/expansion/whois.py +72 -0
- misp_modules/modules/expansion/whoisfreaks.py +232 -0
- misp_modules/modules/expansion/wiki.py +58 -0
- misp_modules/modules/expansion/xforceexchange.py +189 -0
- misp_modules/modules/expansion/xlsx_enrich.py +63 -0
- misp_modules/modules/expansion/yara_query.py +70 -0
- misp_modules/modules/expansion/yara_syntax_validator.py +49 -0
- misp_modules/modules/expansion/yeti.py +196 -0
- misp_modules/modules/export_mod/__init__.py +3 -0
- misp_modules/modules/export_mod/cef_export.py +93 -0
- misp_modules/modules/export_mod/cisco_firesight_manager_ACL_rule_export.py +150 -0
- misp_modules/modules/export_mod/defender_endpoint_export.py +141 -0
- misp_modules/modules/export_mod/goamlexport.py +255 -0
- misp_modules/modules/export_mod/liteexport.py +97 -0
- misp_modules/modules/export_mod/mass_eql_export.py +106 -0
- misp_modules/modules/export_mod/nexthinkexport.py +131 -0
- misp_modules/modules/export_mod/osqueryexport.py +125 -0
- misp_modules/modules/export_mod/pdfexport.py +107 -0
- misp_modules/modules/export_mod/testexport.py +73 -0
- misp_modules/modules/export_mod/threatStream_misp_export.py +114 -0
- misp_modules/modules/export_mod/threat_connect_export.py +125 -0
- misp_modules/modules/export_mod/virustotal_collections.py +141 -0
- misp_modules/modules/export_mod/vt_graph.py +120 -0
- misp_modules/modules/export_mod/yara_export.py +290 -0
- misp_modules/modules/import_mod/__init__.py +22 -0
- misp_modules/modules/import_mod/cof2misp.py +264 -0
- misp_modules/modules/import_mod/csvimport.py +319 -0
- misp_modules/modules/import_mod/cuckooimport.py +749 -0
- misp_modules/modules/import_mod/email_import.py +294 -0
- misp_modules/modules/import_mod/goamlimport.py +188 -0
- misp_modules/modules/import_mod/import_blueprint.py +93 -0
- misp_modules/modules/import_mod/joe_import.py +74 -0
- misp_modules/modules/import_mod/lastline_import.py +160 -0
- misp_modules/modules/import_mod/mispjson.py +73 -0
- misp_modules/modules/import_mod/ocr.py +122 -0
- misp_modules/modules/import_mod/openiocimport.py +99 -0
- misp_modules/modules/import_mod/taxii21.py +383 -0
- misp_modules/modules/import_mod/testimport.py +73 -0
- misp_modules/modules/import_mod/threatanalyzer_import.py +552 -0
- misp_modules/modules/import_mod/url_import.py +97 -0
- misp_modules/modules/import_mod/vmray_import.py +96 -0
- misp_modules/modules/import_mod/vmray_summary_json_import.py +87 -0
- misp_modules-2.4.196.dist-info/LICENSE +661 -0
- misp_modules-2.4.196.dist-info/METADATA +281 -0
- misp_modules-2.4.196.dist-info/RECORD +194 -0
- misp_modules-2.4.196.dist-info/WHEEL +4 -0
- 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())
|