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,170 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import requests
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
misperrors = {'error': 'Error'}
|
|
6
|
+
mispattributes = {'input': ["hostname", "domain", "ip-src", "ip-dst", "md5", "sha1", "sha256", "sha512", "whois-registrant-email"],
|
|
7
|
+
'output': ["domain", "ip-src", "ip-dst", "text", "md5", "sha1", "sha256", "sha512", "hostname", "whois-registrant-email"]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
# possible module-types: 'expansion', 'hover' or both
|
|
11
|
+
moduleinfo = {
|
|
12
|
+
'version': '1',
|
|
13
|
+
'author': 'chrisdoman',
|
|
14
|
+
'description': 'Module to get information from ThreatCrowd.',
|
|
15
|
+
'module-type': ['expansion'],
|
|
16
|
+
'name': 'ThreatCrowd Lookup',
|
|
17
|
+
'logo': 'threatcrowd.png',
|
|
18
|
+
'requirements': [],
|
|
19
|
+
'features': 'This module takes a MISP attribute as input and queries ThreatCrowd with it.\n\nThe result of this query is then parsed and some data is mapped into MISP attributes in order to enrich the input attribute.',
|
|
20
|
+
'references': ['https://www.threatcrowd.org/'],
|
|
21
|
+
'input': 'A MISP attribute included in the following list:\n- hostname\n- domain\n- ip-src\n- ip-dst\n- md5\n- sha1\n- sha256\n- sha512\n- whois-registrant-email',
|
|
22
|
+
'output': 'MISP attributes mapped from the result of the query on ThreatCrowd, included in the following list:\n- domain\n- ip-src\n- ip-dst\n- text\n- md5\n- sha1\n- sha256\n- sha512\n- hostname\n- whois-registrant-email',
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
moduleconfig = []
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Avoid adding windows update to enrichment etc.
|
|
29
|
+
def isBlacklisted(value):
|
|
30
|
+
blacklist = ['8.8.8.8', '255.255.255.255', '192.168.56.', 'time.windows.com']
|
|
31
|
+
|
|
32
|
+
for b in blacklist:
|
|
33
|
+
if value in b:
|
|
34
|
+
return True
|
|
35
|
+
|
|
36
|
+
return False
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def valid_ip(ip):
|
|
40
|
+
m = re.match(r"^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$", ip)
|
|
41
|
+
return bool(m) and all(map(lambda n: 0 <= int(n) <= 255, m.groups()))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def valid_domain(hostname):
|
|
45
|
+
if len(hostname) > 255:
|
|
46
|
+
return False
|
|
47
|
+
if hostname[-1] == ".":
|
|
48
|
+
hostname = hostname[:-1] # strip exactly one dot from the right, if present
|
|
49
|
+
allowed = re.compile(r"(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)
|
|
50
|
+
return all(allowed.match(x) for x in hostname.split("."))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def valid_email(email):
|
|
54
|
+
return bool(re.search(r"^[\w\.\+\-]+\@[\w]+\.[a-z]{2,3}$", email))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def handler(q=False):
|
|
58
|
+
if q is False:
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
q = json.loads(q)
|
|
62
|
+
|
|
63
|
+
r = {"results": []}
|
|
64
|
+
|
|
65
|
+
if "ip-src" in q:
|
|
66
|
+
r["results"] += getIP(q["ip-src"])
|
|
67
|
+
if "ip-dst" in q:
|
|
68
|
+
r["results"] += getIP(q["ip-dst"])
|
|
69
|
+
if "domain" in q:
|
|
70
|
+
r["results"] += getDomain(q["domain"])
|
|
71
|
+
if 'hostname' in q:
|
|
72
|
+
r["results"] += getDomain(q['hostname'])
|
|
73
|
+
if 'md5' in q:
|
|
74
|
+
r["results"] += getHash(q['md5'])
|
|
75
|
+
if 'sha1' in q:
|
|
76
|
+
r["results"] += getHash(q['sha1'])
|
|
77
|
+
if 'sha256' in q:
|
|
78
|
+
r["results"] += getHash(q['sha256'])
|
|
79
|
+
if 'sha512' in q:
|
|
80
|
+
r["results"] += getHash(q['sha512'])
|
|
81
|
+
if 'whois-registrant-email' in q:
|
|
82
|
+
r["results"] += getEmail(q['whois-registrant-email'])
|
|
83
|
+
|
|
84
|
+
uniq = []
|
|
85
|
+
for res in r["results"]:
|
|
86
|
+
if res not in uniq:
|
|
87
|
+
uniq.append(res)
|
|
88
|
+
r["results"] = uniq
|
|
89
|
+
return r
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def getHash(hash):
|
|
93
|
+
|
|
94
|
+
ret = []
|
|
95
|
+
req = json.loads(requests.get("https://www.threatcrowd.org/searchApi/v2/file/report/?resource=" + hash).text)
|
|
96
|
+
|
|
97
|
+
if "domains" in req:
|
|
98
|
+
domains = req["domains"]
|
|
99
|
+
for domain in domains:
|
|
100
|
+
if not isBlacklisted(domain) and valid_domain(domain):
|
|
101
|
+
ret.append({"types": ["hostname"], "values": [domain]})
|
|
102
|
+
|
|
103
|
+
if "ips" in req:
|
|
104
|
+
ips = req["ips"]
|
|
105
|
+
for ip in ips:
|
|
106
|
+
if not isBlacklisted(ip):
|
|
107
|
+
ret.append({"types": ["ip-dst"], "values": [ip]})
|
|
108
|
+
|
|
109
|
+
return ret
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def getIP(ip):
|
|
113
|
+
ret = []
|
|
114
|
+
req = json.loads(requests.get("https://www.threatcrowd.org/searchApi/v2/ip/report/?ip=" + ip).text)
|
|
115
|
+
|
|
116
|
+
if "resolutions" in req:
|
|
117
|
+
for dns in req["resolutions"]:
|
|
118
|
+
if "domain" in dns:
|
|
119
|
+
if valid_domain(dns["domain"]):
|
|
120
|
+
ret.append({"types": ["hostname"], "values": [dns["domain"]]})
|
|
121
|
+
|
|
122
|
+
if "hashes" in req:
|
|
123
|
+
for hash in req["hashes"]:
|
|
124
|
+
ret.append({"types": ["md5"], "values": [hash]})
|
|
125
|
+
|
|
126
|
+
return ret
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def getEmail(email):
|
|
130
|
+
ret = []
|
|
131
|
+
j = requests.get("https://www.threatcrowd.org/searchApi/v2/email/report/?email=" + email).text
|
|
132
|
+
req = json.loads(j)
|
|
133
|
+
|
|
134
|
+
if "domains" in req:
|
|
135
|
+
domains = req["domains"]
|
|
136
|
+
for domain in domains:
|
|
137
|
+
if not isBlacklisted(domain) and valid_domain(domain):
|
|
138
|
+
ret.append({"types": ["hostname"], "values": [domain]})
|
|
139
|
+
|
|
140
|
+
return ret
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def getDomain(domain):
|
|
144
|
+
|
|
145
|
+
ret = []
|
|
146
|
+
req = json.loads(requests.get("https://www.threatcrowd.org/searchApi/v2/domain/report/?domain=" + domain).text)
|
|
147
|
+
|
|
148
|
+
if "resolutions" in req:
|
|
149
|
+
for dns in req["resolutions"]:
|
|
150
|
+
if "ip_address" in dns:
|
|
151
|
+
ret.append({"types": ["ip-dst"], "values": [dns["ip_address"]]})
|
|
152
|
+
|
|
153
|
+
if "emails" in req:
|
|
154
|
+
for email in req["emails"]:
|
|
155
|
+
ret.append({"types": ["whois-registrant-email"], "values": [email]})
|
|
156
|
+
|
|
157
|
+
if "hashes" in req:
|
|
158
|
+
for hash in req["hashes"]:
|
|
159
|
+
ret.append({"types": ["md5"], "values": [hash]})
|
|
160
|
+
|
|
161
|
+
return ret
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def introspection():
|
|
165
|
+
return mispattributes
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def version():
|
|
169
|
+
moduleinfo['config'] = moduleconfig
|
|
170
|
+
return moduleinfo
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import requests
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
misperrors = {'error': 'Error'}
|
|
6
|
+
mispattributes = {'input': ['md5', 'sha1', 'sha256', 'domain', 'url', 'email-src', 'ip-dst|port', 'ip-src|port'], 'output': ['text']}
|
|
7
|
+
moduleinfo = {
|
|
8
|
+
'version': '0.1',
|
|
9
|
+
'author': 'Corsin Camichel',
|
|
10
|
+
'description': 'Module to search for an IOC on ThreatFox by abuse.ch.',
|
|
11
|
+
'module-type': ['hover', 'expansion'],
|
|
12
|
+
'name': 'ThreadFox Lookup',
|
|
13
|
+
'logo': '',
|
|
14
|
+
'requirements': [],
|
|
15
|
+
'features': '',
|
|
16
|
+
'references': [],
|
|
17
|
+
'input': '',
|
|
18
|
+
'output': '',
|
|
19
|
+
}
|
|
20
|
+
moduleconfig = []
|
|
21
|
+
|
|
22
|
+
API_URL = "https://threatfox-api.abuse.ch/api/v1/"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# copied from
|
|
26
|
+
# https://github.com/marjatech/threatfox2misp/blob/main/threatfox2misp.py
|
|
27
|
+
def confidence_level_to_tag(level: int) -> str:
|
|
28
|
+
confidence_tagging = {
|
|
29
|
+
0: 'misp:confidence-level="unconfident"',
|
|
30
|
+
10: 'misp:confidence-level="rarely-confident"',
|
|
31
|
+
37: 'misp:confidence-level="fairly-confident"',
|
|
32
|
+
63: 'misp:confidence-level="usually-confident"',
|
|
33
|
+
90: 'misp:confidence-level="completely-confident"',
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
confidence_tag = ""
|
|
37
|
+
for tag_minvalue, tag in confidence_tagging.items():
|
|
38
|
+
if level >= tag_minvalue:
|
|
39
|
+
confidence_tag = tag
|
|
40
|
+
return confidence_tag
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def handler(q=False):
|
|
44
|
+
if q is False:
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
request = json.loads(q)
|
|
48
|
+
ret_val = ""
|
|
49
|
+
|
|
50
|
+
for input_type in mispattributes['input']:
|
|
51
|
+
if input_type in request:
|
|
52
|
+
to_query = request[input_type]
|
|
53
|
+
break
|
|
54
|
+
else:
|
|
55
|
+
misperrors['error'] = "Unsupported attributes type:"
|
|
56
|
+
return misperrors
|
|
57
|
+
|
|
58
|
+
data = {"query": "search_ioc", "search_term": f"{to_query}"}
|
|
59
|
+
response = requests.post(API_URL, data=json.dumps(data))
|
|
60
|
+
if response.status_code == 200:
|
|
61
|
+
result = json.loads(response.text)
|
|
62
|
+
if(result["query_status"] == "ok"):
|
|
63
|
+
confidence_tag = confidence_level_to_tag(result["data"][0]["confidence_level"])
|
|
64
|
+
ret_val = {'results': [{'types': mispattributes['output'], 'values': [result["data"][0]["threat_type_desc"]], 'tags': [result["data"][0]["malware"], result["data"][0]["malware_printable"], confidence_tag]}]}
|
|
65
|
+
|
|
66
|
+
return ret_val
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def introspection():
|
|
70
|
+
return mispattributes
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def version():
|
|
74
|
+
moduleinfo['config'] = moduleconfig
|
|
75
|
+
return moduleinfo
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import requests
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
|
|
5
|
+
misperrors = {'error': 'Error'}
|
|
6
|
+
mispattributes = {'input': ['hostname', 'domain', 'ip-src', 'ip-dst', 'md5', 'sha1', 'sha256', 'sha512'],
|
|
7
|
+
'output': ['domain', 'ip-src', 'ip-dst', 'text', 'md5', 'sha1', 'sha256', 'sha512', 'ssdeep',
|
|
8
|
+
'authentihash', 'filename', 'whois-registrant-email', 'url', 'link']
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
# possible module-types: 'expansion', 'hover' or both
|
|
12
|
+
moduleinfo = {
|
|
13
|
+
'version': '1',
|
|
14
|
+
'author': 'KX499',
|
|
15
|
+
'description': 'Module to get information from ThreatMiner.',
|
|
16
|
+
'module-type': ['expansion'],
|
|
17
|
+
'name': 'ThreatMiner Lookup',
|
|
18
|
+
'logo': 'threatminer.png',
|
|
19
|
+
'requirements': [],
|
|
20
|
+
'features': 'This module takes a MISP attribute as input and queries ThreatMiner with it.\n\nThe result of this query is then parsed and some data is mapped into MISP attributes in order to enrich the input attribute.',
|
|
21
|
+
'references': ['https://www.threatminer.org/'],
|
|
22
|
+
'input': 'A MISP attribute included in the following list:\n- hostname\n- domain\n- ip-src\n- ip-dst\n- md5\n- sha1\n- sha256\n- sha512',
|
|
23
|
+
'output': 'MISP attributes mapped from the result of the query on ThreatMiner, included in the following list:\n- domain\n- ip-src\n- ip-dst\n- text\n- md5\n- sha1\n- sha256\n- sha512\n- ssdeep\n- authentihash\n- filename\n- whois-registrant-email\n- url\n- link',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ThreatMiner():
|
|
28
|
+
def __init__(self):
|
|
29
|
+
self.results = defaultdict(set)
|
|
30
|
+
self.comment = '{}: Threatminer - {}'
|
|
31
|
+
self.types_mapping = {'domain': '_get_domain', 'hostname': '_get_domain',
|
|
32
|
+
'ip-dst': '_get_ip', 'ip-src': '_get_ip',
|
|
33
|
+
'md5': '_get_hash', 'sha1': '_get_hash',
|
|
34
|
+
'sha256': '_get_hash', 'sha512': '_get_hash'}
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def parsed_results(self):
|
|
38
|
+
to_return = []
|
|
39
|
+
for key, values in self.results.items():
|
|
40
|
+
if values:
|
|
41
|
+
input_value, comment = key[:2]
|
|
42
|
+
types = [k for k in key[2:]]
|
|
43
|
+
to_return.append({'types': types, 'values': list(values),
|
|
44
|
+
'comment': self.comment.format(input_value, comment)})
|
|
45
|
+
return to_return
|
|
46
|
+
|
|
47
|
+
def parse_query(self, request):
|
|
48
|
+
for input_type, to_call in self.types_mapping.items():
|
|
49
|
+
if request.get(input_type):
|
|
50
|
+
getattr(self, to_call)(request[input_type])
|
|
51
|
+
|
|
52
|
+
def _get_domain(self, q):
|
|
53
|
+
queries_mapping = {1: ('_add_whois', 'whois'), 2: ('_add_ip', 'pdns'),
|
|
54
|
+
3: ('_add_uri', 'uri'), 4: ('_add_hash', 'samples'),
|
|
55
|
+
5: ('_add_domain', 'subdomain'), 6: ('_add_link', 'report')}
|
|
56
|
+
for flag, mapped in queries_mapping.items():
|
|
57
|
+
req = requests.get('https://www.threatminer.org/domain.php', params={'q': q, 'api': 'True', 'rt': flag})
|
|
58
|
+
if not req.status_code == 200:
|
|
59
|
+
continue
|
|
60
|
+
results = req.json().get('results')
|
|
61
|
+
if not results:
|
|
62
|
+
continue
|
|
63
|
+
to_call, comment = mapped
|
|
64
|
+
getattr(self, to_call)(results, q, comment)
|
|
65
|
+
|
|
66
|
+
def _get_hash(self, q):
|
|
67
|
+
queries_mapping = {1: ('_add_filename', 'file'), 3: ('_add_network', 'network'),
|
|
68
|
+
6: ('_add_text', 'detection'), 7: ('_add_hash', 'report')}
|
|
69
|
+
for flag, mapped in queries_mapping.items():
|
|
70
|
+
req = requests.get('https://www.threatminer.org/sample.php', params={'q': q, 'api': 'True', 'rt': flag})
|
|
71
|
+
if not req.status_code == 200:
|
|
72
|
+
continue
|
|
73
|
+
results = req.json().get('results')
|
|
74
|
+
if not results:
|
|
75
|
+
continue
|
|
76
|
+
to_call, comment = mapped
|
|
77
|
+
getattr(self, to_call)(results, q, comment)
|
|
78
|
+
|
|
79
|
+
def _get_ip(self, q):
|
|
80
|
+
queries_mapping = {1: ('_add_whois', 'whois'), 2: ('_add_ip', 'pdns'),
|
|
81
|
+
3: ('_add_uri', 'uri'), 4: ('_add_hash', 'samples'),
|
|
82
|
+
5: ('_add_x509', 'ssl'), 6: ('_add_link', 'report')}
|
|
83
|
+
for flag, mapped in queries_mapping.items():
|
|
84
|
+
req = requests.get('https://www.threatminer.org/host.php', params={'q': q, 'api': 'True', 'rt': flag})
|
|
85
|
+
if not req.status_code == 200:
|
|
86
|
+
continue
|
|
87
|
+
results = req.json().get('results')
|
|
88
|
+
if not results:
|
|
89
|
+
continue
|
|
90
|
+
to_call, comment = mapped
|
|
91
|
+
getattr(self, to_call)(results, q, comment)
|
|
92
|
+
|
|
93
|
+
def _add_domain(self, results, q, comment):
|
|
94
|
+
self.results[(q, comment, 'domain')].update({result for result in results if isinstance(result, str)})
|
|
95
|
+
|
|
96
|
+
def _add_filename(self, results, q, comment):
|
|
97
|
+
self.results[(q, comment, 'filename')].update({result['file_name'] for result in results if result.get('file_name')})
|
|
98
|
+
|
|
99
|
+
def _add_hash(self, results, q, comment):
|
|
100
|
+
self.results[(q, comment, 'sha256')].update({result for result in results if isinstance(result, str)})
|
|
101
|
+
|
|
102
|
+
def _add_ip(self, results, q, comment):
|
|
103
|
+
self.results[(q, comment, 'ip-src', 'ip-dst')].update({result['ip'] for result in results if result.get('ip')})
|
|
104
|
+
|
|
105
|
+
def _add_link(self, results, q, comment):
|
|
106
|
+
self.results[(q, comment, 'link')].update({result['URL'] for result in results if result.get('URL')})
|
|
107
|
+
|
|
108
|
+
def _add_network(self, results, q, comment):
|
|
109
|
+
for result in results:
|
|
110
|
+
domains = result.get('domains')
|
|
111
|
+
if domains:
|
|
112
|
+
self.results[(q, comment, 'domain')].update({domain['domain'] for domain in domains if domain.get('domain')})
|
|
113
|
+
hosts = result.get('hosts')
|
|
114
|
+
if hosts:
|
|
115
|
+
self.results[(q, comment, 'ip-src', 'ip-dst')].update({host for host in hosts if isinstance(host, str)})
|
|
116
|
+
|
|
117
|
+
def _add_text(self, results, q, comment):
|
|
118
|
+
for result in results:
|
|
119
|
+
detections = result.get('av_detections')
|
|
120
|
+
if detections:
|
|
121
|
+
self.results[(q, comment, 'text')].update({d['detection'] for d in detections if d.get('detection')})
|
|
122
|
+
|
|
123
|
+
def _add_uri(self, results, q, comment):
|
|
124
|
+
self.results[(q, comment, 'url')].update({result['uri'] for result in results if result.get('uri')})
|
|
125
|
+
|
|
126
|
+
def _add_whois(self, results, q, comment):
|
|
127
|
+
for result in results:
|
|
128
|
+
emails = result.get('whois', {}).get('emails')
|
|
129
|
+
if emails:
|
|
130
|
+
self.results[(q, comment, 'whois-registrant-email')].update({email for em_type, email in emails.items() if em_type == 'registrant' and email})
|
|
131
|
+
|
|
132
|
+
def _add_x509(self, results, q, comment):
|
|
133
|
+
self.results[(q, comment, 'x509-fingerprint-sha1')].update({result for result in results if isinstance(result, str)})
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def handler(q=False):
|
|
137
|
+
if q is False:
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
q = json.loads(q)
|
|
141
|
+
|
|
142
|
+
parser = ThreatMiner()
|
|
143
|
+
parser.parse_query(q)
|
|
144
|
+
return {'results': parser.parsed_results}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def introspection():
|
|
148
|
+
return mispattributes
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def version():
|
|
152
|
+
return moduleinfo
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import requests
|
|
3
|
+
import base64
|
|
4
|
+
import io
|
|
5
|
+
import zipfile
|
|
6
|
+
|
|
7
|
+
misperrors = {'error': 'Error'}
|
|
8
|
+
mispattributes = {'input': ['attachment', 'malware-sample', 'url'], 'output': ['link']}
|
|
9
|
+
moduleinfo = {
|
|
10
|
+
'version': '1',
|
|
11
|
+
'author': 'Karen Yousefi',
|
|
12
|
+
'description': 'Module to submit samples to tria.ge',
|
|
13
|
+
'module-type': ['expansion', 'hover'],
|
|
14
|
+
'name': 'Triage Submit',
|
|
15
|
+
'logo': '',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
moduleconfig = ['apikey', 'url_mode']
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def handler(q=False):
|
|
22
|
+
if q is False:
|
|
23
|
+
return False
|
|
24
|
+
|
|
25
|
+
request = json.loads(q)
|
|
26
|
+
|
|
27
|
+
if request.get('config', {}).get('apikey') is None:
|
|
28
|
+
misperrors['error'] = 'tria.ge API key is missing'
|
|
29
|
+
return misperrors
|
|
30
|
+
|
|
31
|
+
api_key = request['config']['apikey']
|
|
32
|
+
url_mode = request['config'].get('url_mode', 'submit') # 'submit' or 'fetch'
|
|
33
|
+
base_url = 'https://tria.ge/api/v0/samples'
|
|
34
|
+
headers = {'Authorization': f'Bearer {api_key}'}
|
|
35
|
+
|
|
36
|
+
if 'attachment' in request:
|
|
37
|
+
data = request['data']
|
|
38
|
+
filename = request['attachment']
|
|
39
|
+
return submit_file(headers, base_url, data, filename)
|
|
40
|
+
elif 'malware-sample' in request:
|
|
41
|
+
data = request['data']
|
|
42
|
+
filename = request['malware-sample'].split('|')[0]
|
|
43
|
+
return submit_file(headers, base_url, data, filename, is_malware_sample=True)
|
|
44
|
+
elif 'url' in request:
|
|
45
|
+
url = request['url']
|
|
46
|
+
return submit_url(headers, base_url, url, url_mode)
|
|
47
|
+
else:
|
|
48
|
+
misperrors['error'] = 'Unsupported input type'
|
|
49
|
+
return misperrors
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def submit_file(headers, base_url, data, filename, is_malware_sample=False):
|
|
53
|
+
try:
|
|
54
|
+
if is_malware_sample:
|
|
55
|
+
file_data = base64.b64decode(data)
|
|
56
|
+
zip_file = zipfile.ZipFile(io.BytesIO(file_data))
|
|
57
|
+
file_data = zip_file.read(zip_file.namelist()[0], pwd=b'infected')
|
|
58
|
+
else:
|
|
59
|
+
file_data = base64.b64decode(data)
|
|
60
|
+
|
|
61
|
+
files = {'file': (filename, file_data)}
|
|
62
|
+
response = requests.post(base_url, headers=headers, files=files)
|
|
63
|
+
response.raise_for_status()
|
|
64
|
+
result = response.json()
|
|
65
|
+
|
|
66
|
+
sample_id = result['id']
|
|
67
|
+
sample_url = f'https://tria.ge/{sample_id}'
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
'results': [
|
|
71
|
+
{
|
|
72
|
+
'types': 'link',
|
|
73
|
+
'values': sample_url,
|
|
74
|
+
'comment': 'Link to tria.ge analysis',
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
except Exception as e:
|
|
80
|
+
misperrors['error'] = f'Error submitting to tria.ge: {str(e)}'
|
|
81
|
+
return misperrors
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def submit_url(headers, base_url, url, mode):
|
|
85
|
+
try:
|
|
86
|
+
if mode == 'fetch':
|
|
87
|
+
data = {'kind': 'fetch', 'url': url}
|
|
88
|
+
else: # submit
|
|
89
|
+
data = {'kind': 'url', 'url': url}
|
|
90
|
+
|
|
91
|
+
response = requests.post(base_url, headers=headers, json=data)
|
|
92
|
+
response.raise_for_status()
|
|
93
|
+
result = response.json()
|
|
94
|
+
|
|
95
|
+
sample_id = result['id']
|
|
96
|
+
sample_url = f'https://tria.ge/{sample_id}'
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
'results': [
|
|
100
|
+
{
|
|
101
|
+
'types': 'link',
|
|
102
|
+
'values': sample_url,
|
|
103
|
+
'comment': f'Link to tria.ge analysis ({mode} mode)',
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
except Exception as e:
|
|
109
|
+
misperrors['error'] = f'Error submitting to tria.ge: {str(e)}'
|
|
110
|
+
return misperrors
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def introspection():
|
|
114
|
+
return mispattributes
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def version():
|
|
118
|
+
moduleinfo['config'] = moduleconfig
|
|
119
|
+
return moduleinfo
|