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,272 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import vt
|
|
4
|
+
from . import check_input_attribute, standard_error_message
|
|
5
|
+
from urllib.parse import urlparse
|
|
6
|
+
from pymisp import MISPAttribute, MISPEvent, MISPObject
|
|
7
|
+
|
|
8
|
+
misperrors = {'error': 'Error'}
|
|
9
|
+
mispattributes = {'input': ['hostname', 'domain', "ip-src", "ip-dst", "md5", "sha1", "sha256", "url"],
|
|
10
|
+
'format': 'misp_standard'}
|
|
11
|
+
moduleinfo = {
|
|
12
|
+
'version': '2',
|
|
13
|
+
'author': 'Christian Studer',
|
|
14
|
+
'description': 'Enrich observables with the VirusTotal v3 public API',
|
|
15
|
+
'module-type': ['expansion', 'hover'],
|
|
16
|
+
'name': 'VirusTotal Public API Lookup',
|
|
17
|
+
'logo': 'virustotal.png',
|
|
18
|
+
'requirements': ['An access to the VirusTotal API (apikey)'],
|
|
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 [more advanced VirusTotal expansion module](https://github.com/MISP/misp-modules/blob/main/misp_modules/modules/expansion/virustotal.py), this module is made for VirusTotal users who have a low request rate limit.\n\nThus, it only queries the API once and returns the results that is parsed into MISP attributes and objects.',
|
|
20
|
+
'references': ['https://www.virustotal.com', 'https://docs.virustotal.com/reference/overview'],
|
|
21
|
+
'input': 'A domain, hostname, ip, url or hash (md5, sha1, sha256 or sha512) attribute.',
|
|
22
|
+
'output': 'MISP attributes and objects resulting from the parsing of the VirusTotal report concerning the input attribute.',
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
moduleconfig = ['apikey', 'proxy_host', 'proxy_port', 'proxy_username', 'proxy_password']
|
|
26
|
+
|
|
27
|
+
LOGGER = logging.getLogger('virus_total_public')
|
|
28
|
+
LOGGER.setLevel(logging.INFO)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
DEFAULT_RESULTS_LIMIT = 10
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class VirusTotalParser:
|
|
35
|
+
def __init__(self, client: vt.Client, limit: int) -> None:
|
|
36
|
+
self.client = client
|
|
37
|
+
self.limit = limit or DEFAULT_RESULTS_LIMIT
|
|
38
|
+
self.misp_event = MISPEvent()
|
|
39
|
+
self.attribute = MISPAttribute()
|
|
40
|
+
self.parsed_objects = {}
|
|
41
|
+
self.input_types_mapping = {'ip-src': self.parse_ip, 'ip-dst': self.parse_ip,
|
|
42
|
+
'domain': self.parse_domain, 'hostname': self.parse_domain,
|
|
43
|
+
'md5': self.parse_hash, 'sha1': self.parse_hash,
|
|
44
|
+
'sha256': self.parse_hash, 'url': self.parse_url}
|
|
45
|
+
self.proxies = None
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def get_total_analysis(analysis: dict, known_distributors: dict = None) -> int:
|
|
49
|
+
if not analysis:
|
|
50
|
+
return 0
|
|
51
|
+
count = sum([analysis['undetected'], analysis['suspicious'], analysis['harmless']])
|
|
52
|
+
return count if known_distributors else count + analysis['malicious']
|
|
53
|
+
|
|
54
|
+
def query_api(self, attribute: dict) -> None:
|
|
55
|
+
self.attribute.from_dict(**attribute)
|
|
56
|
+
self.input_types_mapping[self.attribute.type](self.attribute.value)
|
|
57
|
+
|
|
58
|
+
def get_result(self) -> dict:
|
|
59
|
+
event = json.loads(self.misp_event.to_json())
|
|
60
|
+
results = {key: event[key] for key in ('Attribute', 'Object') if (key in event and event[key])}
|
|
61
|
+
return {'results': results}
|
|
62
|
+
|
|
63
|
+
def add_vt_report(self, report: vt.Object) -> str:
|
|
64
|
+
analysis = report.get('last_analysis_stats')
|
|
65
|
+
total = self.get_total_analysis(analysis, report.get('known_distributors'))
|
|
66
|
+
permalink = f'https://www.virustotal.com/gui/{report.type}/{report.id}'
|
|
67
|
+
|
|
68
|
+
vt_object = MISPObject('virustotal-report')
|
|
69
|
+
vt_object.add_attribute('permalink', type='link', value=permalink)
|
|
70
|
+
detection_ratio = f"{analysis['malicious']}/{total}" if analysis else '-/-'
|
|
71
|
+
vt_object.add_attribute('detection-ratio', type='text', value=detection_ratio, disable_correlation=True)
|
|
72
|
+
self.misp_event.add_object(**vt_object)
|
|
73
|
+
return vt_object.uuid
|
|
74
|
+
|
|
75
|
+
def create_misp_object(self, report: vt.Object) -> MISPObject:
|
|
76
|
+
misp_object = None
|
|
77
|
+
vt_uuid = self.add_vt_report(report)
|
|
78
|
+
if report.type == 'file':
|
|
79
|
+
misp_object = MISPObject('file')
|
|
80
|
+
for hash_type in ('md5', 'sha1', 'sha256', 'tlsh',
|
|
81
|
+
'vhash', 'ssdeep', 'imphash'):
|
|
82
|
+
misp_object.add_attribute(**{'type': hash_type,
|
|
83
|
+
'object_relation': hash_type,
|
|
84
|
+
'value': report.get(hash_type)})
|
|
85
|
+
elif report.type == 'domain':
|
|
86
|
+
misp_object = MISPObject('domain-ip')
|
|
87
|
+
misp_object.add_attribute('domain', type='domain', value=report.id)
|
|
88
|
+
elif report.type == 'ip_address':
|
|
89
|
+
misp_object = MISPObject('domain-ip')
|
|
90
|
+
misp_object.add_attribute('ip', type='ip-dst', value=report.id)
|
|
91
|
+
elif report.type == 'url':
|
|
92
|
+
misp_object = MISPObject('url')
|
|
93
|
+
misp_object.add_attribute('url', type='url', value=report.id)
|
|
94
|
+
misp_object.add_reference(vt_uuid, 'analyzed-with')
|
|
95
|
+
return misp_object
|
|
96
|
+
|
|
97
|
+
################################################################################
|
|
98
|
+
#### Main parsing functions #### # noqa
|
|
99
|
+
################################################################################
|
|
100
|
+
|
|
101
|
+
def parse_domain(self, domain: str) -> str:
|
|
102
|
+
domain_report = self.client.get_object(f'/domains/{domain}')
|
|
103
|
+
|
|
104
|
+
# DOMAIN
|
|
105
|
+
domain_object = self.create_misp_object(domain_report)
|
|
106
|
+
|
|
107
|
+
# WHOIS
|
|
108
|
+
if domain_report.whois:
|
|
109
|
+
whois_object = MISPObject('whois')
|
|
110
|
+
whois_object.add_attribute('text', type='text', value=domain_report.whois)
|
|
111
|
+
self.misp_event.add_object(**whois_object)
|
|
112
|
+
|
|
113
|
+
# SIBLINGS AND SUBDOMAINS
|
|
114
|
+
for relationship_name, misp_name in [('siblings', 'sibling-of'), ('subdomains', 'subdomain')]:
|
|
115
|
+
rel_iterator = self.client.iterator(f'/domains/{domain_report.id}/{relationship_name}', limit=self.limit)
|
|
116
|
+
for item in rel_iterator:
|
|
117
|
+
attr = MISPAttribute()
|
|
118
|
+
attr.from_dict(**dict(type='domain', value=item.id))
|
|
119
|
+
self.misp_event.add_attribute(**attr)
|
|
120
|
+
domain_object.add_reference(attr.uuid, misp_name)
|
|
121
|
+
|
|
122
|
+
# RESOLUTIONS
|
|
123
|
+
resolutions_iterator = self.client.iterator(f'/domains/{domain_report.id}/resolutions', limit=self.limit)
|
|
124
|
+
for resolution in resolutions_iterator:
|
|
125
|
+
domain_object.add_attribute('ip', type='ip-dst', value=resolution.ip_address)
|
|
126
|
+
|
|
127
|
+
# COMMUNICATING AND REFERRER FILES
|
|
128
|
+
for relationship_name, misp_name in [
|
|
129
|
+
('communicating_files', 'communicates-with'),
|
|
130
|
+
('referrer_files', 'referring')
|
|
131
|
+
]:
|
|
132
|
+
files_iterator = self.client.iterator(f'/domains/{domain_report.id}/{relationship_name}', limit=self.limit)
|
|
133
|
+
for file in files_iterator:
|
|
134
|
+
file_object = self.create_misp_object(file)
|
|
135
|
+
file_object.add_reference(domain_object.uuid, misp_name)
|
|
136
|
+
self.misp_event.add_object(**file_object)
|
|
137
|
+
|
|
138
|
+
self.misp_event.add_object(**domain_object)
|
|
139
|
+
return domain_object.uuid
|
|
140
|
+
|
|
141
|
+
def parse_hash(self, file_hash: str) -> str:
|
|
142
|
+
file_report = self.client.get_object(f'/files/{file_hash}')
|
|
143
|
+
file_object = self.create_misp_object(file_report)
|
|
144
|
+
|
|
145
|
+
# COMMUNICATING, DOWNLOADED AND REFERRER FILES
|
|
146
|
+
for relationship_name, misp_name in [
|
|
147
|
+
('contacted_urls', 'communicates-with'),
|
|
148
|
+
('contacted_domains', 'communicates-with'),
|
|
149
|
+
('contacted_ips', 'communicates-with')
|
|
150
|
+
]:
|
|
151
|
+
related_files_iterator = self.client.iterator(f'/files/{file_report.id}/{relationship_name}', limit=self.limit)
|
|
152
|
+
for related_file in related_files_iterator:
|
|
153
|
+
related_file_object = self.create_misp_object(related_file)
|
|
154
|
+
related_file_object.add_reference(file_object.uuid, misp_name)
|
|
155
|
+
self.misp_event.add_object(**related_file_object)
|
|
156
|
+
|
|
157
|
+
self.misp_event.add_object(**file_object)
|
|
158
|
+
return file_object.uuid
|
|
159
|
+
|
|
160
|
+
def parse_ip(self, ip: str) -> str:
|
|
161
|
+
ip_report = self.client.get_object(f'/ip_addresses/{ip}')
|
|
162
|
+
|
|
163
|
+
# IP
|
|
164
|
+
ip_object = self.create_misp_object(ip_report)
|
|
165
|
+
|
|
166
|
+
# ASN
|
|
167
|
+
asn_object = MISPObject('asn')
|
|
168
|
+
asn_object.add_attribute('asn', type='AS', value=ip_report.asn)
|
|
169
|
+
asn_object.add_attribute('subnet-announced', type='ip-src', value=ip_report.network)
|
|
170
|
+
asn_object.add_attribute('country', type='text', value=ip_report.country)
|
|
171
|
+
self.misp_event.add_object(**asn_object)
|
|
172
|
+
|
|
173
|
+
# RESOLUTIONS
|
|
174
|
+
resolutions_iterator = self.client.iterator(f'/ip_addresses/{ip_report.id}/resolutions', limit=self.limit)
|
|
175
|
+
for resolution in resolutions_iterator:
|
|
176
|
+
ip_object.add_attribute('domain', type='domain', value=resolution.host_name)
|
|
177
|
+
|
|
178
|
+
self.misp_event.add_object(**ip_object)
|
|
179
|
+
return ip_object.uuid
|
|
180
|
+
|
|
181
|
+
def parse_url(self, url: str) -> str:
|
|
182
|
+
url_id = vt.url_id(url)
|
|
183
|
+
url_report = self.client.get_object(f'/urls/{url_id}')
|
|
184
|
+
url_object = self.create_misp_object(url_report)
|
|
185
|
+
self.misp_event.add_object(**url_object)
|
|
186
|
+
return url_object.uuid
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def get_proxy_settings(config: dict) -> dict:
|
|
190
|
+
"""Returns proxy settings in the requests format.
|
|
191
|
+
If no proxy settings are set, return None."""
|
|
192
|
+
proxies = None
|
|
193
|
+
host = config.get('proxy_host')
|
|
194
|
+
port = config.get('proxy_port')
|
|
195
|
+
username = config.get('proxy_username')
|
|
196
|
+
password = config.get('proxy_password')
|
|
197
|
+
|
|
198
|
+
if host:
|
|
199
|
+
if not port:
|
|
200
|
+
misperrors['error'] = 'The virustotal_proxy_host config is set, ' \
|
|
201
|
+
'please also set the virustotal_proxy_port.'
|
|
202
|
+
raise KeyError
|
|
203
|
+
parsed = urlparse(host)
|
|
204
|
+
if 'http' in parsed.scheme:
|
|
205
|
+
scheme = 'http'
|
|
206
|
+
else:
|
|
207
|
+
scheme = parsed.scheme
|
|
208
|
+
netloc = parsed.netloc
|
|
209
|
+
host = f'{netloc}:{port}'
|
|
210
|
+
|
|
211
|
+
if username:
|
|
212
|
+
if not password:
|
|
213
|
+
misperrors['error'] = 'The virustotal_proxy_username config is set, ' \
|
|
214
|
+
'please also set the virustotal_proxy_password.'
|
|
215
|
+
raise KeyError
|
|
216
|
+
auth = f'{username}:{password}'
|
|
217
|
+
host = auth + '@' + host
|
|
218
|
+
|
|
219
|
+
proxies = {
|
|
220
|
+
'http': f'{scheme}://{host}',
|
|
221
|
+
'https': f'{scheme}://{host}'
|
|
222
|
+
}
|
|
223
|
+
return proxies
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def parse_error(status_code: int) -> str:
|
|
227
|
+
status_mapping = {204: 'VirusTotal request rate limit exceeded.',
|
|
228
|
+
400: 'Incorrect request, please check the arguments.',
|
|
229
|
+
403: 'You don\'t have enough privileges to make the request.'}
|
|
230
|
+
if status_code in status_mapping:
|
|
231
|
+
return status_mapping[status_code]
|
|
232
|
+
return "VirusTotal may not be accessible."
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def handler(q=False):
|
|
236
|
+
if q is False:
|
|
237
|
+
return False
|
|
238
|
+
request = json.loads(q)
|
|
239
|
+
if not request.get('config') or not request['config'].get('apikey'):
|
|
240
|
+
misperrors['error'] = 'A VirusTotal api key is required for this module.'
|
|
241
|
+
return misperrors
|
|
242
|
+
if not request.get('attribute') or not check_input_attribute(request['attribute']):
|
|
243
|
+
return {'error': f'{standard_error_message}, which should contain at least a type, a value and an uuid.'}
|
|
244
|
+
if request['attribute']['type'] not in mispattributes['input']:
|
|
245
|
+
return {'error': 'Unsupported attribute type.'}
|
|
246
|
+
|
|
247
|
+
event_limit = request['config'].get('event_limit')
|
|
248
|
+
attribute = request['attribute']
|
|
249
|
+
proxy_settings = get_proxy_settings(request.get('config'))
|
|
250
|
+
|
|
251
|
+
try:
|
|
252
|
+
client = vt.Client(request['config']['apikey'],
|
|
253
|
+
headers={
|
|
254
|
+
'x-tool': 'MISPModuleVirusTotalPublicExpansion',
|
|
255
|
+
},
|
|
256
|
+
proxy=proxy_settings['http'] if proxy_settings else None)
|
|
257
|
+
parser = VirusTotalParser(client, int(event_limit) if event_limit else None)
|
|
258
|
+
parser.query_api(attribute)
|
|
259
|
+
except vt.APIError as ex:
|
|
260
|
+
misperrors['error'] = ex.message
|
|
261
|
+
return misperrors
|
|
262
|
+
|
|
263
|
+
return parser.get_result()
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def introspection():
|
|
267
|
+
return mispattributes
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def version():
|
|
271
|
+
moduleinfo['config'] = moduleconfig
|
|
272
|
+
return moduleinfo
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sys
|
|
3
|
+
import base64
|
|
4
|
+
import io
|
|
5
|
+
import zipfile
|
|
6
|
+
import requests
|
|
7
|
+
import hashlib
|
|
8
|
+
|
|
9
|
+
misperrors = {'error': 'Error'}
|
|
10
|
+
mispattributes = {'input': ['attachment', 'malware-sample'], 'output': ['link']}
|
|
11
|
+
moduleinfo = {
|
|
12
|
+
'version': '1',
|
|
13
|
+
'author': 'Karen Yousefi',
|
|
14
|
+
'description': 'Module to push malware samples to VirusTotal',
|
|
15
|
+
'module-type': ['expansion'],
|
|
16
|
+
'name': 'VirusTotal Upload',
|
|
17
|
+
'requirements': ['requests library'],
|
|
18
|
+
'logo': 'virustotal.png',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
moduleconfig = ['virustotal_apikey']
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def handler(q=False):
|
|
25
|
+
if q is False:
|
|
26
|
+
return False
|
|
27
|
+
request = json.loads(q)
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
data = request.get("data")
|
|
31
|
+
if 'malware-sample' in request:
|
|
32
|
+
sample_filename = request.get("malware-sample").split("|", 1)[0]
|
|
33
|
+
data = base64.b64decode(data)
|
|
34
|
+
fl = io.BytesIO(data)
|
|
35
|
+
zf = zipfile.ZipFile(fl)
|
|
36
|
+
sample_hashname = zf.namelist()[0]
|
|
37
|
+
data = zf.read(sample_hashname, b"infected")
|
|
38
|
+
zf.close()
|
|
39
|
+
elif 'attachment' in request:
|
|
40
|
+
sample_filename = request.get("attachment")
|
|
41
|
+
data = base64.b64decode(data)
|
|
42
|
+
else:
|
|
43
|
+
misperrors['error'] = "No malware sample or attachment supplied"
|
|
44
|
+
return misperrors
|
|
45
|
+
except Exception:
|
|
46
|
+
misperrors['error'] = "Unable to process submitted sample data"
|
|
47
|
+
return misperrors
|
|
48
|
+
|
|
49
|
+
if request["config"].get("virustotal_apikey") is None:
|
|
50
|
+
misperrors["error"] = "Missing VirusTotal API key"
|
|
51
|
+
return misperrors
|
|
52
|
+
|
|
53
|
+
virustotal_apikey = request["config"].get("virustotal_apikey")
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
url = "https://www.virustotal.com/api/v3/files"
|
|
57
|
+
headers = {
|
|
58
|
+
"accept": "application/json",
|
|
59
|
+
"x-apikey": virustotal_apikey,
|
|
60
|
+
}
|
|
61
|
+
files = {"file": (sample_filename, data)}
|
|
62
|
+
response = requests.post(url, headers=headers, files=files)
|
|
63
|
+
response.raise_for_status()
|
|
64
|
+
|
|
65
|
+
# Calculate SHA256 of the file
|
|
66
|
+
sha256 = hashlib.sha256(data).hexdigest()
|
|
67
|
+
|
|
68
|
+
virustotal_link = f"https://www.virustotal.com/gui/file/{sha256}"
|
|
69
|
+
except Exception as e:
|
|
70
|
+
misperrors['error'] = f"Unable to send sample to VirusTotal: {str(e)}"
|
|
71
|
+
return misperrors
|
|
72
|
+
|
|
73
|
+
r = {
|
|
74
|
+
'results': [
|
|
75
|
+
{
|
|
76
|
+
'types': 'link',
|
|
77
|
+
'values': virustotal_link,
|
|
78
|
+
'comment': 'Link to VirusTotal analysis',
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
return r
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def introspection():
|
|
86
|
+
return mispattributes
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def version():
|
|
90
|
+
moduleinfo['config'] = moduleconfig
|
|
91
|
+
return moduleinfo
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
'''
|
|
4
|
+
Submit sample to VMRay.
|
|
5
|
+
|
|
6
|
+
Requires "vmray_rest_api"
|
|
7
|
+
|
|
8
|
+
The expansion module vmray_submit and import module vmray_import are a two step
|
|
9
|
+
process to import data from VMRay.
|
|
10
|
+
You can automate this by setting the PyMISP example script 'vmray_automation'
|
|
11
|
+
as a cron job
|
|
12
|
+
|
|
13
|
+
'''
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import base64
|
|
17
|
+
from distutils.util import strtobool
|
|
18
|
+
|
|
19
|
+
import io
|
|
20
|
+
import zipfile
|
|
21
|
+
|
|
22
|
+
from _vmray.rest_api import VMRayRESTAPI
|
|
23
|
+
|
|
24
|
+
misperrors = {'error': 'Error'}
|
|
25
|
+
mispattributes = {'input': ['attachment', 'malware-sample'], 'output': ['text', 'sha1', 'sha256', 'md5', 'link']}
|
|
26
|
+
moduleinfo = {
|
|
27
|
+
'version': '0.3',
|
|
28
|
+
'author': 'Koen Van Impe',
|
|
29
|
+
'description': 'Module to submit a sample to VMRay.',
|
|
30
|
+
'module-type': ['expansion'],
|
|
31
|
+
'name': 'VMRay Submit',
|
|
32
|
+
'logo': 'vmray.png',
|
|
33
|
+
'requirements': ['An access to the VMRay API (apikey & url)'],
|
|
34
|
+
'features': 'This module takes an attachment or malware-sample attribute as input to query the VMRay API.\n\nThe sample contained within the attribute in then enriched with data from VMRay mapped into MISP attributes.',
|
|
35
|
+
'references': ['https://www.vmray.com/'],
|
|
36
|
+
'input': 'An attachment or malware-sample attribute.',
|
|
37
|
+
'output': 'MISP attributes mapped from the result of the query on VMRay API, included in the following list:\n- text\n- sha1\n- sha256\n- md5\n- link',
|
|
38
|
+
}
|
|
39
|
+
moduleconfig = ['apikey', 'url', 'shareable', 'do_not_reanalyze', 'do_not_include_vmrayjobids']
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
include_vmrayjobids = False
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def handler(q=False):
|
|
46
|
+
global include_vmrayjobids
|
|
47
|
+
|
|
48
|
+
if q is False:
|
|
49
|
+
return False
|
|
50
|
+
request = json.loads(q)
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
data = request.get("data")
|
|
54
|
+
if 'malware-sample' in request:
|
|
55
|
+
# malicious samples are encrypted with zip (password infected) and then base64 encoded
|
|
56
|
+
sample_filename = request.get("malware-sample").split("|", 1)[0]
|
|
57
|
+
data = base64.b64decode(data)
|
|
58
|
+
fl = io.BytesIO(data)
|
|
59
|
+
zf = zipfile.ZipFile(fl)
|
|
60
|
+
sample_hashname = zf.namelist()[0]
|
|
61
|
+
data = zf.read(sample_hashname, b"infected")
|
|
62
|
+
zf.close()
|
|
63
|
+
elif 'attachment' in request:
|
|
64
|
+
# All attachments get base64 encoded
|
|
65
|
+
sample_filename = request.get("attachment")
|
|
66
|
+
data = base64.b64decode(data)
|
|
67
|
+
|
|
68
|
+
else:
|
|
69
|
+
misperrors['error'] = "No malware sample or attachment supplied"
|
|
70
|
+
return misperrors
|
|
71
|
+
except Exception:
|
|
72
|
+
misperrors['error'] = "Unable to process submited sample data"
|
|
73
|
+
return misperrors
|
|
74
|
+
|
|
75
|
+
if (request["config"].get("apikey") is None) or (request["config"].get("url") is None):
|
|
76
|
+
misperrors["error"] = "Missing API key or server URL (hint: try cloud.vmray.com)"
|
|
77
|
+
return misperrors
|
|
78
|
+
|
|
79
|
+
api = VMRayRESTAPI(request["config"].get("url"), request["config"].get("apikey"), False)
|
|
80
|
+
|
|
81
|
+
shareable = request["config"].get("shareable")
|
|
82
|
+
do_not_reanalyze = request["config"].get("do_not_reanalyze")
|
|
83
|
+
do_not_include_vmrayjobids = request["config"].get("do_not_include_vmrayjobids")
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
shareable = bool(strtobool(shareable)) # Do we want the sample to be shared?
|
|
87
|
+
reanalyze = not bool(strtobool(do_not_reanalyze)) # Always reanalyze the sample?
|
|
88
|
+
include_vmrayjobids = not bool(strtobool(do_not_include_vmrayjobids)) # Include the references to VMRay job IDs
|
|
89
|
+
except ValueError:
|
|
90
|
+
misperrors["error"] = "Error while processing settings. Please double-check your values."
|
|
91
|
+
return misperrors
|
|
92
|
+
|
|
93
|
+
if data and sample_filename:
|
|
94
|
+
args = {}
|
|
95
|
+
args["shareable"] = shareable
|
|
96
|
+
args["sample_file"] = {'data': io.BytesIO(data), 'filename': sample_filename}
|
|
97
|
+
args["reanalyze"] = reanalyze
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
vmraydata = vmraySubmit(api, args)
|
|
101
|
+
if vmraydata["errors"] and "Submission not stored" not in vmraydata["errors"][0]["error_msg"]:
|
|
102
|
+
misperrors['error'] = "VMRay: %s" % vmraydata["errors"][0]["error_msg"]
|
|
103
|
+
return misperrors
|
|
104
|
+
else:
|
|
105
|
+
return vmrayProcess(vmraydata)
|
|
106
|
+
except Exception:
|
|
107
|
+
misperrors['error'] = "Problem when calling API."
|
|
108
|
+
return misperrors
|
|
109
|
+
else:
|
|
110
|
+
misperrors['error'] = "No sample data or filename."
|
|
111
|
+
return misperrors
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def introspection():
|
|
115
|
+
return mispattributes
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def version():
|
|
119
|
+
moduleinfo['config'] = moduleconfig
|
|
120
|
+
return moduleinfo
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def vmrayProcess(vmraydata):
|
|
124
|
+
''' Process the JSON file returned by vmray'''
|
|
125
|
+
if vmraydata:
|
|
126
|
+
try:
|
|
127
|
+
sample = vmraydata["samples"][0]
|
|
128
|
+
jobs = vmraydata["jobs"]
|
|
129
|
+
|
|
130
|
+
# Result received?
|
|
131
|
+
if sample:
|
|
132
|
+
r = {'results': []}
|
|
133
|
+
r['results'].append({'types': 'md5', 'values': sample['sample_md5hash']})
|
|
134
|
+
r['results'].append({'types': 'sha1', 'values': sample['sample_sha1hash']})
|
|
135
|
+
r['results'].append({'types': 'sha256', 'values': sample['sample_sha256hash']})
|
|
136
|
+
r['results'].append({'types': 'text', 'values': 'VMRay Sample ID: %s' % sample['sample_id'], 'tags': 'workflow:state="incomplete"'})
|
|
137
|
+
r['results'].append({'types': 'link', 'values': sample['sample_webif_url']})
|
|
138
|
+
|
|
139
|
+
# Include data from different jobs
|
|
140
|
+
if include_vmrayjobids and len(jobs) > 0:
|
|
141
|
+
for job in jobs:
|
|
142
|
+
job_id = job["job_id"]
|
|
143
|
+
job_vm_name = job["job_vm_name"]
|
|
144
|
+
job_configuration_name = job["job_configuration_name"]
|
|
145
|
+
r["results"].append({"types": "text", "values": "VMRay Job ID %s (%s - %s)" % (job_id, job_vm_name, job_configuration_name)})
|
|
146
|
+
return r
|
|
147
|
+
else:
|
|
148
|
+
misperrors['error'] = "No valid results returned."
|
|
149
|
+
return misperrors
|
|
150
|
+
except Exception:
|
|
151
|
+
misperrors['error'] = "No valid submission data returned."
|
|
152
|
+
return misperrors
|
|
153
|
+
else:
|
|
154
|
+
misperrors['error'] = "Unable to parse results."
|
|
155
|
+
return misperrors
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def vmraySubmit(api, args):
|
|
159
|
+
''' Submit the sample to VMRay'''
|
|
160
|
+
vmraydata = api.call("POST", "/rest/sample/submit", args)
|
|
161
|
+
return vmraydata
|