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,133 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import requests
|
|
3
|
+
from . import check_input_attribute, standard_error_message
|
|
4
|
+
from ._vulnerability_parser.vulnerability_parser import (
|
|
5
|
+
VulnerabilityMapping, VulnerabilityParser)
|
|
6
|
+
from pymisp import MISPObject
|
|
7
|
+
|
|
8
|
+
misperrors = {'error': 'Error'}
|
|
9
|
+
mispattributes = {'input': ['vulnerability'], 'format': 'misp_standard'}
|
|
10
|
+
moduleinfo = {
|
|
11
|
+
'version': '1',
|
|
12
|
+
'author': 'Christian Studer',
|
|
13
|
+
'description': 'An expansion module to query the VARIoT db API for more information about a vulnerability.',
|
|
14
|
+
'module-type': ['expansion', 'hover'],
|
|
15
|
+
'name': 'VARIoT db Lookup',
|
|
16
|
+
'logo': 'variot.png',
|
|
17
|
+
'requirements': ['A VARIoT db API key (if you do not want to be limited to 100 queries / day)'],
|
|
18
|
+
'features': 'The module takes a vulnerability attribute as input and queries que VARIoT db API to gather additional information.\n\nThe `vuln` endpoint is queried first to look for additional information about the vulnerability itself.\n\nThe `exploits` endpoint is also queried then to look for the information of the potential related exploits, which are parsed and added to the results using the `exploit` object template.',
|
|
19
|
+
'references': ['https://www.variotdbs.pl/'],
|
|
20
|
+
'input': 'Vulnerability attribute.',
|
|
21
|
+
'output': 'Additional information about the vulnerability, as it is stored on the VARIoT db, about the vulnerability itself, and the potential related exploits.',
|
|
22
|
+
}
|
|
23
|
+
moduleconfig = ['API_key']
|
|
24
|
+
variotdbs_url = 'https://www.variotdbs.pl/api'
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class VariotMapping(VulnerabilityMapping):
|
|
28
|
+
__exploit_mapping = {
|
|
29
|
+
'credits': 'credit',
|
|
30
|
+
'description': 'description',
|
|
31
|
+
'exploit': 'exploit',
|
|
32
|
+
'title': 'title'
|
|
33
|
+
}
|
|
34
|
+
__exploit_multiple_mapping = {
|
|
35
|
+
'cve': {
|
|
36
|
+
'feature': 'cve_id',
|
|
37
|
+
'relation': 'cve-id'
|
|
38
|
+
},
|
|
39
|
+
'references': {
|
|
40
|
+
'feature': 'url',
|
|
41
|
+
'relation': 'reference'
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def exploit_mapping(cls) -> dict:
|
|
47
|
+
return cls.__exploit_mapping
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def exploit_multiple_mapping(cls) -> dict:
|
|
51
|
+
return cls.__exploit_multiple_mapping
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class VariotdbsParser(VulnerabilityParser):
|
|
55
|
+
def __init__(self, attribute):
|
|
56
|
+
super().__init__(attribute)
|
|
57
|
+
self.__mapping = VulnerabilityMapping
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def mapping(self) -> VulnerabilityMapping:
|
|
61
|
+
return self.__mapping
|
|
62
|
+
|
|
63
|
+
def parse_exploit_information(self, query_results):
|
|
64
|
+
for exploit in query_results:
|
|
65
|
+
exploit_object = MISPObject('exploit')
|
|
66
|
+
exploit_object.add_attribute('exploitdb-id', exploit['edb_id'])
|
|
67
|
+
for field, relation in self.mapping.exploit_mapping().items():
|
|
68
|
+
if exploit.get(field):
|
|
69
|
+
exploit_object.add_attribute(
|
|
70
|
+
relation, exploit[field]['data']
|
|
71
|
+
)
|
|
72
|
+
for field, relation in self.mapping.exploit_multiple_mapping().items():
|
|
73
|
+
if exploit.get(field):
|
|
74
|
+
for value in exploit[field]['data']:
|
|
75
|
+
exploit_object.add_attribute(
|
|
76
|
+
relation['relation'], value[relation['feature']]
|
|
77
|
+
)
|
|
78
|
+
exploit_object.add_reference(self.misp_attribute.uuid, 'related-to')
|
|
79
|
+
self.misp_event.add_object(exploit_object)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def handler(q=False):
|
|
83
|
+
if q is False:
|
|
84
|
+
return False
|
|
85
|
+
request = json.loads(q)
|
|
86
|
+
if not request.get('attribute') or not check_input_attribute(request['attribute']):
|
|
87
|
+
return {'error': f'{standard_error_message}, which should contain at least a type, a value and an uuid.'}
|
|
88
|
+
attribute = request['attribute']
|
|
89
|
+
if attribute.get('type') != 'vulnerability':
|
|
90
|
+
return {'error': 'Vulnerability id missing.'}
|
|
91
|
+
headers = {'Content-Type': 'application/json'}
|
|
92
|
+
if request.get('config', {}).get('API_key'):
|
|
93
|
+
headers['Authorization'] = f"Token {request['config']['API_key']}"
|
|
94
|
+
empty = True
|
|
95
|
+
parser = VariotdbsParser(attribute)
|
|
96
|
+
r = requests.get(f"{variotdbs_url}/vuln/{attribute['value']}/", headers=headers)
|
|
97
|
+
if r.status_code == 200:
|
|
98
|
+
vulnerability_results = r.json()
|
|
99
|
+
if vulnerability_results:
|
|
100
|
+
parser._parse_variot_description(vulnerability_results)
|
|
101
|
+
empty = False
|
|
102
|
+
else:
|
|
103
|
+
if r.reason != 'Not Found':
|
|
104
|
+
return {'error': 'Error while querying the variotdbs API.'}
|
|
105
|
+
r = requests.get(f"{variotdbs_url}/exploits/?cve={attribute['value']}", headers=headers)
|
|
106
|
+
if r.status_code == 200:
|
|
107
|
+
exploit_results = r.json()
|
|
108
|
+
if exploit_results:
|
|
109
|
+
parser.parse_exploit_information(exploit_results['results'])
|
|
110
|
+
empty = False
|
|
111
|
+
if exploit_results['next'] is not None:
|
|
112
|
+
while(1):
|
|
113
|
+
exploit_results = requests.get(exploit_results['next'], headers=headers)
|
|
114
|
+
if exploit_results.status_code != 200:
|
|
115
|
+
break
|
|
116
|
+
exploit_results = exploit_results.json()
|
|
117
|
+
parser.parse_exploit_information(exploit_results['results'])
|
|
118
|
+
if exploit_results['next'] is None:
|
|
119
|
+
break
|
|
120
|
+
else:
|
|
121
|
+
return {'error': 'Error while querying the variotdbs API.'}
|
|
122
|
+
if empty:
|
|
123
|
+
return {'error': 'Empty results'}
|
|
124
|
+
return parser.get_results()
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def introspection():
|
|
128
|
+
return mispattributes
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def version():
|
|
132
|
+
moduleinfo['config'] = moduleconfig
|
|
133
|
+
return moduleinfo
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
from urllib.parse import urlparse
|
|
2
|
+
import vt
|
|
3
|
+
from . import check_input_attribute, standard_error_message
|
|
4
|
+
from pymisp import MISPAttribute, MISPEvent, MISPObject
|
|
5
|
+
|
|
6
|
+
misperrors = {'error': 'Error'}
|
|
7
|
+
mispattributes = {'input': ['hostname', 'domain', "ip-src", "ip-dst", "md5", "sha1", "sha256", "url", "ip-src|port", "ip-dst|port"],
|
|
8
|
+
'format': 'misp_standard'}
|
|
9
|
+
|
|
10
|
+
# possible module-types: 'expansion', 'hover' or both
|
|
11
|
+
moduleinfo = {
|
|
12
|
+
'version': '6',
|
|
13
|
+
'author': 'Hannah Ward',
|
|
14
|
+
'description': 'Enrich observables with the VirusTotal v3 API',
|
|
15
|
+
'module-type': ['expansion'],
|
|
16
|
+
'name': 'VirusTotal v3 Lookup',
|
|
17
|
+
'logo': 'virustotal.png',
|
|
18
|
+
'requirements': ['An access to the VirusTotal API (apikey), with a high request rate limit.'],
|
|
19
|
+
'features': 'New format of modules able to return attributes and objects.\n\nA module to take a MISP attribute as input and query the VirusTotal API to get additional data about it.\n\nCompared to the [standard VirusTotal expansion module](https://github.com/MISP/misp-modules/blob/main/misp_modules/modules/expansion/virustotal_public.py), this module is made for advanced parsing of VirusTotal report, with a recursive analysis of the elements found after the first request.\n\nThus, it requires a higher request rate limit to avoid the API to return a 204 error (Request rate limit exceeded), and the data parsed from the different requests are returned as MISP attributes and objects, with the corresponding relations between each one of them.',
|
|
20
|
+
'references': ['https://www.virustotal.com/', 'https://docs.virustotal.com/reference/overview'],
|
|
21
|
+
'input': 'A domain, hash (md5, sha1, sha256 or sha512), hostname or IP address attribute.',
|
|
22
|
+
'output': 'MISP attributes and objects resulting from the parsing of the VirusTotal report concerning the input attribute.',
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# config fields that your code expects from the site admin
|
|
26
|
+
moduleconfig = ["apikey", "event_limit", 'proxy_host', 'proxy_port', 'proxy_username', 'proxy_password']
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
DEFAULT_RESULTS_LIMIT = 10
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class VirusTotalParser:
|
|
33
|
+
def __init__(self, client: vt.Client, limit: int) -> None:
|
|
34
|
+
self.client = client
|
|
35
|
+
self.limit = limit or DEFAULT_RESULTS_LIMIT
|
|
36
|
+
self.misp_event = MISPEvent()
|
|
37
|
+
self.attribute = MISPAttribute()
|
|
38
|
+
self.parsed_objects = {}
|
|
39
|
+
self.input_types_mapping = {'ip-src': self.parse_ip, 'ip-dst': self.parse_ip,
|
|
40
|
+
'domain': self.parse_domain, 'hostname': self.parse_domain,
|
|
41
|
+
'md5': self.parse_hash, 'sha1': self.parse_hash,
|
|
42
|
+
'sha256': self.parse_hash, 'url': self.parse_url,
|
|
43
|
+
'ip-src|port': self.parse_ip_port, 'ip-dst|port': self.parse_ip_port}
|
|
44
|
+
self.proxies = None
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def get_total_analysis(analysis: dict, known_distributors: dict = None) -> int:
|
|
48
|
+
if not analysis:
|
|
49
|
+
return 0
|
|
50
|
+
count = sum([analysis['undetected'], analysis['suspicious'], analysis['harmless']])
|
|
51
|
+
return count if known_distributors else count + analysis['malicious']
|
|
52
|
+
|
|
53
|
+
def query_api(self, attribute: dict) -> None:
|
|
54
|
+
self.attribute.from_dict(**attribute)
|
|
55
|
+
self.input_types_mapping[self.attribute.type](self.attribute.value)
|
|
56
|
+
|
|
57
|
+
def get_result(self) -> dict:
|
|
58
|
+
event = self.misp_event.to_dict()
|
|
59
|
+
results = {key: event[key] for key in ('Attribute', 'Object') if (key in event and event[key])}
|
|
60
|
+
return {'results': results}
|
|
61
|
+
|
|
62
|
+
def add_vt_report(self, report: vt.Object) -> str:
|
|
63
|
+
analysis = report.get('last_analysis_stats')
|
|
64
|
+
total = self.get_total_analysis(analysis, report.get('known_distributors'))
|
|
65
|
+
if report.type == 'ip_address':
|
|
66
|
+
rtype = 'ip-address'
|
|
67
|
+
else:
|
|
68
|
+
rtype = report.type
|
|
69
|
+
permalink = f'https://www.virustotal.com/gui/{rtype}/{report.id}'
|
|
70
|
+
|
|
71
|
+
vt_object = MISPObject('virustotal-report')
|
|
72
|
+
vt_object.add_attribute('permalink', type='link', value=permalink)
|
|
73
|
+
detection_ratio = f"{analysis['malicious']}/{total}" if analysis else '-/-'
|
|
74
|
+
vt_object.add_attribute('detection-ratio', type='text', value=detection_ratio, disable_correlation=True)
|
|
75
|
+
self.misp_event.add_object(**vt_object)
|
|
76
|
+
return vt_object.uuid
|
|
77
|
+
|
|
78
|
+
def create_misp_object(self, report: vt.Object) -> MISPObject:
|
|
79
|
+
misp_object = None
|
|
80
|
+
vt_uuid = self.add_vt_report(report)
|
|
81
|
+
|
|
82
|
+
if report.type == 'file':
|
|
83
|
+
misp_object = MISPObject('file')
|
|
84
|
+
for hash_type in ('md5', 'sha1', 'sha256', 'tlsh',
|
|
85
|
+
'vhash', 'ssdeep', 'imphash'):
|
|
86
|
+
misp_object.add_attribute(hash_type,
|
|
87
|
+
**{'type': hash_type,
|
|
88
|
+
'value': report.get(hash_type)})
|
|
89
|
+
elif report.type == 'domain':
|
|
90
|
+
misp_object = MISPObject('domain-ip')
|
|
91
|
+
misp_object.add_attribute('domain', type='domain', value=report.id)
|
|
92
|
+
elif report.type == 'ip_address':
|
|
93
|
+
misp_object = MISPObject('domain-ip')
|
|
94
|
+
misp_object.add_attribute('ip', type='ip-dst', value=report.id)
|
|
95
|
+
elif report.type == 'url':
|
|
96
|
+
misp_object = MISPObject('url')
|
|
97
|
+
misp_object.add_attribute('url', type='url', value=report.id)
|
|
98
|
+
misp_object.add_reference(vt_uuid, 'analyzed-with')
|
|
99
|
+
return misp_object
|
|
100
|
+
|
|
101
|
+
################################################################################
|
|
102
|
+
#### Main parsing functions #### # noqa
|
|
103
|
+
################################################################################
|
|
104
|
+
|
|
105
|
+
def parse_domain(self, domain: str) -> str:
|
|
106
|
+
domain_report = self.client.get_object(f'/domains/{domain}')
|
|
107
|
+
|
|
108
|
+
# DOMAIN
|
|
109
|
+
domain_object = self.create_misp_object(domain_report)
|
|
110
|
+
|
|
111
|
+
# WHOIS
|
|
112
|
+
if domain_report.whois:
|
|
113
|
+
whois_object = MISPObject('whois')
|
|
114
|
+
whois_object.add_attribute('text', type='text', value=domain_report.whois)
|
|
115
|
+
self.misp_event.add_object(**whois_object)
|
|
116
|
+
|
|
117
|
+
# SIBLINGS AND SUBDOMAINS
|
|
118
|
+
for relationship_name, misp_name in [('siblings', 'sibling-of'), ('subdomains', 'subdomain')]:
|
|
119
|
+
rel_iterator = self.client.iterator(f'/domains/{domain_report.id}/{relationship_name}', limit=self.limit)
|
|
120
|
+
for item in rel_iterator:
|
|
121
|
+
attr = MISPAttribute()
|
|
122
|
+
attr.from_dict(**dict(type='domain', value=item.id))
|
|
123
|
+
self.misp_event.add_attribute(**attr)
|
|
124
|
+
domain_object.add_reference(attr.uuid, misp_name)
|
|
125
|
+
|
|
126
|
+
# RESOLUTIONS
|
|
127
|
+
resolutions_iterator = self.client.iterator(f'/domains/{domain_report.id}/resolutions', limit=self.limit)
|
|
128
|
+
for resolution in resolutions_iterator:
|
|
129
|
+
domain_object.add_attribute('ip', type='ip-dst', value=resolution.ip_address)
|
|
130
|
+
|
|
131
|
+
# COMMUNICATING, DOWNLOADED AND REFERRER FILES
|
|
132
|
+
for relationship_name, misp_name in [
|
|
133
|
+
('communicating_files', 'communicates-with'),
|
|
134
|
+
('downloaded_files', 'downloaded-from'),
|
|
135
|
+
('referrer_files', 'referring')
|
|
136
|
+
]:
|
|
137
|
+
files_iterator = self.client.iterator(f'/domains/{domain_report.id}/{relationship_name}', limit=self.limit)
|
|
138
|
+
for file in files_iterator:
|
|
139
|
+
file_object = self.create_misp_object(file)
|
|
140
|
+
file_object.add_reference(domain_object.uuid, misp_name)
|
|
141
|
+
self.misp_event.add_object(**file_object)
|
|
142
|
+
|
|
143
|
+
# URLS
|
|
144
|
+
urls_iterator = self.client.iterator(f'/domains/{domain_report.id}/urls', limit=self.limit)
|
|
145
|
+
for url in urls_iterator:
|
|
146
|
+
url_object = self.create_misp_object(url)
|
|
147
|
+
url_object.add_reference(domain_object.uuid, 'hosted-in')
|
|
148
|
+
self.misp_event.add_object(**url_object)
|
|
149
|
+
|
|
150
|
+
self.misp_event.add_object(**domain_object)
|
|
151
|
+
return domain_object.uuid
|
|
152
|
+
|
|
153
|
+
def parse_hash(self, file_hash: str) -> str:
|
|
154
|
+
file_report = self.client.get_object(f'/files/{file_hash}')
|
|
155
|
+
file_object = self.create_misp_object(file_report)
|
|
156
|
+
|
|
157
|
+
# ITW URLS
|
|
158
|
+
urls_iterator = self.client.iterator(f'/files/{file_report.id}/itw_urls', limit=self.limit)
|
|
159
|
+
for url in urls_iterator:
|
|
160
|
+
url_object = self.create_misp_object(url)
|
|
161
|
+
url_object.add_reference(file_object.uuid, 'downloaded')
|
|
162
|
+
self.misp_event.add_object(**url_object)
|
|
163
|
+
|
|
164
|
+
# COMMUNICATING, DOWNLOADED AND REFERRER FILES
|
|
165
|
+
for relationship_name, misp_name in [
|
|
166
|
+
('contacted_urls', 'communicates-with'),
|
|
167
|
+
('contacted_domains', 'communicates-with'),
|
|
168
|
+
('contacted_ips', 'communicates-with')
|
|
169
|
+
]:
|
|
170
|
+
related_files_iterator = self.client.iterator(f'/files/{file_report.id}/{relationship_name}', limit=self.limit)
|
|
171
|
+
for related_file in related_files_iterator:
|
|
172
|
+
related_file_object = self.create_misp_object(related_file)
|
|
173
|
+
related_file_object.add_reference(file_object.uuid, misp_name)
|
|
174
|
+
self.misp_event.add_object(**related_file_object)
|
|
175
|
+
|
|
176
|
+
self.misp_event.add_object(**file_object)
|
|
177
|
+
return file_object.uuid
|
|
178
|
+
def parse_ip_port(self, ipport: str) -> str:
|
|
179
|
+
ip = ipport.split('|')[0]
|
|
180
|
+
self.parse_ip(ip)
|
|
181
|
+
|
|
182
|
+
def parse_ip(self, ip: str) -> str:
|
|
183
|
+
ip_report = self.client.get_object(f'/ip_addresses/{ip}')
|
|
184
|
+
|
|
185
|
+
# IP
|
|
186
|
+
ip_object = self.create_misp_object(ip_report)
|
|
187
|
+
|
|
188
|
+
# ASN
|
|
189
|
+
asn_object = MISPObject('asn')
|
|
190
|
+
asn_object.add_attribute('asn', type='AS', value=ip_report.asn)
|
|
191
|
+
asn_object.add_attribute('subnet-announced', type='ip-src', value=ip_report.network)
|
|
192
|
+
asn_object.add_attribute('country', type='text', value=ip_report.country)
|
|
193
|
+
self.misp_event.add_object(**asn_object)
|
|
194
|
+
|
|
195
|
+
# RESOLUTIONS
|
|
196
|
+
resolutions_iterator = self.client.iterator(f'/ip_addresses/{ip_report.id}/resolutions', limit=self.limit)
|
|
197
|
+
for resolution in resolutions_iterator:
|
|
198
|
+
ip_object.add_attribute('domain', type='domain', value=resolution.host_name)
|
|
199
|
+
|
|
200
|
+
# URLS
|
|
201
|
+
urls_iterator = self.client.iterator(f'/ip_addresses/{ip_report.id}/urls', limit=self.limit)
|
|
202
|
+
for url in urls_iterator:
|
|
203
|
+
url_object = self.create_misp_object(url)
|
|
204
|
+
url_object.add_reference(ip_object.uuid, 'hosted-in')
|
|
205
|
+
self.misp_event.add_object(**url_object)
|
|
206
|
+
|
|
207
|
+
self.misp_event.add_object(**ip_object)
|
|
208
|
+
return ip_object.uuid
|
|
209
|
+
|
|
210
|
+
def parse_url(self, url: str) -> str:
|
|
211
|
+
url_id = vt.url_id(url)
|
|
212
|
+
url_report = self.client.get_object(f'/urls/{url_id}')
|
|
213
|
+
url_object = self.create_misp_object(url_report)
|
|
214
|
+
|
|
215
|
+
# COMMUNICATING, DOWNLOADED AND REFERRER FILES
|
|
216
|
+
for relationship_name, misp_name in [
|
|
217
|
+
('communicating_files', 'communicates-with'),
|
|
218
|
+
('downloaded_files', 'downloaded-from'),
|
|
219
|
+
('referrer_files', 'referring')
|
|
220
|
+
]:
|
|
221
|
+
files_iterator = self.client.iterator(f'/urls/{url_report.id}/{relationship_name}', limit=self.limit)
|
|
222
|
+
for file in files_iterator:
|
|
223
|
+
file_object = self.create_misp_object(file)
|
|
224
|
+
file_object.add_reference(url_object.uuid, misp_name)
|
|
225
|
+
self.misp_event.add_object(**file_object)
|
|
226
|
+
|
|
227
|
+
self.misp_event.add_object(**url_object)
|
|
228
|
+
return url_object.uuid
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def get_proxy_settings(config: dict) -> dict:
|
|
232
|
+
"""Returns proxy settings in the requests format.
|
|
233
|
+
If no proxy settings are set, return None."""
|
|
234
|
+
proxies = None
|
|
235
|
+
host = config.get('proxy_host')
|
|
236
|
+
port = config.get('proxy_port')
|
|
237
|
+
username = config.get('proxy_username')
|
|
238
|
+
password = config.get('proxy_password')
|
|
239
|
+
|
|
240
|
+
if host:
|
|
241
|
+
if not port:
|
|
242
|
+
misperrors['error'] = 'The virustotal_proxy_host config is set, ' \
|
|
243
|
+
'please also set the virustotal_proxy_port.'
|
|
244
|
+
raise KeyError
|
|
245
|
+
parsed = urlparse(host)
|
|
246
|
+
if 'http' in parsed.scheme:
|
|
247
|
+
scheme = 'http'
|
|
248
|
+
else:
|
|
249
|
+
scheme = parsed.scheme
|
|
250
|
+
netloc = parsed.netloc
|
|
251
|
+
host = f'{netloc}:{port}'
|
|
252
|
+
|
|
253
|
+
if username:
|
|
254
|
+
if not password:
|
|
255
|
+
misperrors['error'] = 'The virustotal_proxy_username config is set, ' \
|
|
256
|
+
'please also set the virustotal_proxy_password.'
|
|
257
|
+
raise KeyError
|
|
258
|
+
auth = f'{username}:{password}'
|
|
259
|
+
host = auth + '@' + host
|
|
260
|
+
|
|
261
|
+
proxies = {
|
|
262
|
+
'http': f'{scheme}://{host}',
|
|
263
|
+
'https': f'{scheme}://{host}'
|
|
264
|
+
}
|
|
265
|
+
return proxies
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def parse_error(status_code: int) -> str:
|
|
269
|
+
status_mapping = {204: 'VirusTotal request rate limit exceeded.',
|
|
270
|
+
400: 'Incorrect request, please check the arguments.',
|
|
271
|
+
403: 'You don\'t have enough privileges to make the request.'}
|
|
272
|
+
if status_code in status_mapping:
|
|
273
|
+
return status_mapping[status_code]
|
|
274
|
+
return "VirusTotal may not be accessible."
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def dict_handler(request: dict):
|
|
278
|
+
if not request.get('config') or not request['config'].get('apikey'):
|
|
279
|
+
misperrors['error'] = 'A VirusTotal api key is required for this module.'
|
|
280
|
+
return misperrors
|
|
281
|
+
if not request.get('attribute') or not check_input_attribute(request['attribute']):
|
|
282
|
+
return {'error': f'{standard_error_message}, which should contain at least a type, a value and an uuid.'}
|
|
283
|
+
if request['attribute']['type'] not in mispattributes['input']:
|
|
284
|
+
return {'error': 'Unsupported attribute type.'}
|
|
285
|
+
|
|
286
|
+
event_limit = request['config'].get('event_limit')
|
|
287
|
+
attribute = request['attribute']
|
|
288
|
+
proxy_settings = get_proxy_settings(request.get('config'))
|
|
289
|
+
|
|
290
|
+
try:
|
|
291
|
+
client = vt.Client(request['config']['apikey'],
|
|
292
|
+
headers={
|
|
293
|
+
'x-tool': 'MISPModuleVirusTotalExpansion',
|
|
294
|
+
},
|
|
295
|
+
proxy=proxy_settings['http'] if proxy_settings else None)
|
|
296
|
+
parser = VirusTotalParser(client, int(event_limit) if event_limit else None)
|
|
297
|
+
parser.query_api(attribute)
|
|
298
|
+
except vt.APIError as ex:
|
|
299
|
+
misperrors['error'] = ex.message
|
|
300
|
+
return misperrors
|
|
301
|
+
|
|
302
|
+
return parser.get_result()
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def introspection():
|
|
306
|
+
return mispattributes
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def version():
|
|
310
|
+
moduleinfo['config'] = moduleconfig
|
|
311
|
+
return moduleinfo
|