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,113 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import pypssl
|
|
3
|
+
from . import check_input_attribute, standard_error_message
|
|
4
|
+
from pymisp import MISPAttribute, MISPEvent, MISPObject
|
|
5
|
+
|
|
6
|
+
mispattributes = {'input': ['ip-src', 'ip-dst', 'ip-src|port', 'ip-dst|port'], 'format': 'misp_standard'}
|
|
7
|
+
moduleinfo = {
|
|
8
|
+
'version': '0.2',
|
|
9
|
+
'author': 'Raphaël Vinot',
|
|
10
|
+
'description': 'Modules to access CIRCL Passive SSL.',
|
|
11
|
+
'module-type': ['expansion', 'hover'],
|
|
12
|
+
'name': 'CIRCL Passive SSL',
|
|
13
|
+
'logo': 'passivessl.png',
|
|
14
|
+
'requirements': ['pypssl: Passive SSL python library', 'A CIRCL passive SSL account with username & password'],
|
|
15
|
+
'features': 'This module takes an ip-address (ip-src or ip-dst) attribute as input, and queries the CIRCL Passive SSL REST API to gather the related certificates and return the corresponding MISP objects.\n\nTo make it work a username and a password are required to authenticate to the CIRCL Passive SSL API.',
|
|
16
|
+
'references': ['https://www.circl.lu/services/passive-ssl/'],
|
|
17
|
+
'input': 'IP address attribute.',
|
|
18
|
+
'output': 'x509 certificate objects seen by the IP address(es).',
|
|
19
|
+
}
|
|
20
|
+
moduleconfig = ['username', 'password']
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PassiveSSLParser():
|
|
24
|
+
def __init__(self, attribute, authentication):
|
|
25
|
+
self.misp_event = MISPEvent()
|
|
26
|
+
self.attribute = MISPAttribute()
|
|
27
|
+
self.attribute.from_dict(**attribute)
|
|
28
|
+
self.misp_event.add_attribute(**self.attribute)
|
|
29
|
+
self.pssl = pypssl.PyPSSL(basic_auth=authentication)
|
|
30
|
+
self.cert_hash = 'x509-fingerprint-sha1'
|
|
31
|
+
self.cert_type = 'pem'
|
|
32
|
+
self.mapping = {'issuer': ('text', 'issuer'),
|
|
33
|
+
'keylength': ('text', 'pubkey-info-size'),
|
|
34
|
+
'not_after': ('datetime', 'validity-not-after'),
|
|
35
|
+
'not_before': ('datetime', 'validity-not-before'),
|
|
36
|
+
'subject': ('text', 'subject')}
|
|
37
|
+
|
|
38
|
+
def get_results(self):
|
|
39
|
+
if hasattr(self, 'result'):
|
|
40
|
+
return self.result
|
|
41
|
+
event = json.loads(self.misp_event.to_json())
|
|
42
|
+
results = {key: event[key] for key in ('Attribute', 'Object')}
|
|
43
|
+
return {'results': results}
|
|
44
|
+
|
|
45
|
+
def parse(self):
|
|
46
|
+
value = self.attribute.value.split('|')[0] if '|' in self.attribute.type else self.attribute.value
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
results = self.pssl.query(value)
|
|
50
|
+
except Exception:
|
|
51
|
+
self.result = {'error': 'There is an authentication error, please make sure you supply correct credentials.'}
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
if not results:
|
|
55
|
+
self.result = {'error': 'Not found'}
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
if 'error' in results:
|
|
59
|
+
self.result = {'error': results['error']}
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
for ip_address, certificates in results.items():
|
|
63
|
+
ip_uuid = self._handle_ip_attribute(ip_address)
|
|
64
|
+
for certificate in certificates['certificates']:
|
|
65
|
+
self._handle_certificate(certificate, ip_uuid)
|
|
66
|
+
|
|
67
|
+
def _handle_certificate(self, certificate, ip_uuid):
|
|
68
|
+
x509 = MISPObject('x509')
|
|
69
|
+
x509.add_attribute(self.cert_hash, type=self.cert_hash, value=certificate)
|
|
70
|
+
cert_details = self.pssl.fetch_cert(certificate)
|
|
71
|
+
info = cert_details['info']
|
|
72
|
+
for feature, mapping in self.mapping.items():
|
|
73
|
+
attribute_type, object_relation = mapping
|
|
74
|
+
x509.add_attribute(object_relation, type=attribute_type, value=info[feature])
|
|
75
|
+
x509.add_attribute(self.cert_type, type='text', value=self.cert_type)
|
|
76
|
+
x509.add_reference(ip_uuid, 'seen-by')
|
|
77
|
+
self.misp_event.add_object(**x509)
|
|
78
|
+
|
|
79
|
+
def _handle_ip_attribute(self, ip_address):
|
|
80
|
+
if ip_address == self.attribute.value:
|
|
81
|
+
return self.attribute.uuid
|
|
82
|
+
ip_attribute = MISPAttribute()
|
|
83
|
+
ip_attribute.from_dict(**{'type': self.attribute.type, 'value': ip_address})
|
|
84
|
+
self.misp_event.add_attribute(**ip_attribute)
|
|
85
|
+
return ip_attribute.uuid
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def handler(q=False):
|
|
89
|
+
if q is False:
|
|
90
|
+
return False
|
|
91
|
+
request = json.loads(q)
|
|
92
|
+
if not request.get('config'):
|
|
93
|
+
return {'error': 'CIRCL Passive SSL authentication is missing.'}
|
|
94
|
+
if not request['config'].get('username') or not request['config'].get('password'):
|
|
95
|
+
return {'error': 'CIRCL Passive SSL authentication is incomplete, please provide your username and password.'}
|
|
96
|
+
authentication = (request['config']['username'], request['config']['password'])
|
|
97
|
+
if not request.get('attribute') or not check_input_attribute(request['attribute']):
|
|
98
|
+
return {'error': f'{standard_error_message}, which should contain at least a type, a value and an uuid.'}
|
|
99
|
+
attribute = request['attribute']
|
|
100
|
+
if not any(input_type == attribute['type'] for input_type in mispattributes['input']):
|
|
101
|
+
return {'error': 'Unsupported attribute type.'}
|
|
102
|
+
pssl_parser = PassiveSSLParser(attribute, authentication)
|
|
103
|
+
pssl_parser.parse()
|
|
104
|
+
return pssl_parser.get_results()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def introspection():
|
|
108
|
+
return mispattributes
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def version():
|
|
112
|
+
moduleinfo['config'] = moduleconfig
|
|
113
|
+
return moduleinfo
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import io
|
|
3
|
+
import logging
|
|
4
|
+
import sys
|
|
5
|
+
import zipfile
|
|
6
|
+
import clamd
|
|
7
|
+
from . import check_input_attribute, standard_error_message
|
|
8
|
+
from typing import Optional
|
|
9
|
+
from pymisp import MISPEvent, MISPObject
|
|
10
|
+
|
|
11
|
+
log = logging.getLogger("clamav")
|
|
12
|
+
log.setLevel(logging.DEBUG)
|
|
13
|
+
sh = logging.StreamHandler(sys.stdout)
|
|
14
|
+
sh.setLevel(logging.DEBUG)
|
|
15
|
+
fmt = logging.Formatter(
|
|
16
|
+
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
17
|
+
)
|
|
18
|
+
sh.setFormatter(fmt)
|
|
19
|
+
log.addHandler(sh)
|
|
20
|
+
|
|
21
|
+
moduleinfo = {
|
|
22
|
+
'version': '0.1',
|
|
23
|
+
'author': 'Jakub Onderka',
|
|
24
|
+
'description': 'Submit file to ClamAV',
|
|
25
|
+
'module-type': ['expansion'],
|
|
26
|
+
'name': 'ClaamAV',
|
|
27
|
+
'logo': '',
|
|
28
|
+
'requirements': [],
|
|
29
|
+
'features': '',
|
|
30
|
+
'references': [],
|
|
31
|
+
'input': '',
|
|
32
|
+
'output': '',
|
|
33
|
+
}
|
|
34
|
+
moduleconfig = ["connection"]
|
|
35
|
+
mispattributes = {
|
|
36
|
+
"input": ["attachment", "malware-sample"],
|
|
37
|
+
"format": "misp_standard"
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def create_response(original_attribute: dict, software: str, signature: Optional[str] = None) -> dict:
|
|
42
|
+
misp_event = MISPEvent()
|
|
43
|
+
if signature:
|
|
44
|
+
misp_event.add_attribute(**original_attribute)
|
|
45
|
+
|
|
46
|
+
av_signature_object = MISPObject("av-signature")
|
|
47
|
+
av_signature_object.add_attribute("signature", signature)
|
|
48
|
+
av_signature_object.add_attribute("software", software)
|
|
49
|
+
av_signature_object.add_reference(original_attribute["uuid"], "belongs-to")
|
|
50
|
+
misp_event.add_object(av_signature_object)
|
|
51
|
+
|
|
52
|
+
event = misp_event.to_dict()
|
|
53
|
+
results = {key: event[key] for key in ('Attribute', 'Object') if (key in event and event[key])}
|
|
54
|
+
return {"results": results}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def connect_to_clamav(connection_string: str) -> clamd.ClamdNetworkSocket:
|
|
58
|
+
if connection_string.startswith("unix://"):
|
|
59
|
+
return clamd.ClamdUnixSocket(connection_string.replace("unix://", ""))
|
|
60
|
+
elif ":" in connection_string:
|
|
61
|
+
host, port = connection_string.split(":")
|
|
62
|
+
return clamd.ClamdNetworkSocket(host, int(port))
|
|
63
|
+
else:
|
|
64
|
+
raise Exception("ClamAV connection string is invalid. It must be unix socket path with 'unix://' prefix or IP:PORT.")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def dict_handler(request: dict):
|
|
68
|
+
connection_string: str = request["config"].get("connection")
|
|
69
|
+
if not connection_string:
|
|
70
|
+
return {"error": "No ClamAV connection string provided"}
|
|
71
|
+
|
|
72
|
+
attribute = request.get("attribute")
|
|
73
|
+
if not attribute:
|
|
74
|
+
return {"error": "No attribute provided"}
|
|
75
|
+
|
|
76
|
+
if not check_input_attribute(request['attribute']):
|
|
77
|
+
return {'error': f'{standard_error_message}, which should contain at least a type, a value and an uuid.'}
|
|
78
|
+
|
|
79
|
+
if attribute["type"] not in mispattributes["input"]:
|
|
80
|
+
return {"error": "Invalid attribute type provided, expected 'malware-sample' or 'attachment'"}
|
|
81
|
+
|
|
82
|
+
attribute_data = attribute.get("data")
|
|
83
|
+
if not attribute_data:
|
|
84
|
+
return {"error": "No attribute data provided"}
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
clamav = connect_to_clamav(connection_string)
|
|
88
|
+
software_version = clamav.version()
|
|
89
|
+
except Exception:
|
|
90
|
+
logging.exception("Could not connect to ClamAV")
|
|
91
|
+
return {"error": "Could not connect to ClamAV"}
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
data = base64.b64decode(attribute_data, validate=True)
|
|
95
|
+
except Exception:
|
|
96
|
+
logging.exception("Provided data is not valid base64 encoded string")
|
|
97
|
+
return {"error": "Provided data is not valid base64 encoded string"}
|
|
98
|
+
|
|
99
|
+
if attribute["type"] == "malware-sample":
|
|
100
|
+
try:
|
|
101
|
+
with zipfile.ZipFile(io.BytesIO(data)) as zipf:
|
|
102
|
+
data = zipf.read(zipf.namelist()[0], pwd=b"infected")
|
|
103
|
+
except Exception:
|
|
104
|
+
logging.exception("Could not extract malware sample from ZIP file")
|
|
105
|
+
return {"error": "Could not extract malware sample from ZIP file"}
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
status, reason = clamav.instream(io.BytesIO(data))["stream"]
|
|
109
|
+
except Exception:
|
|
110
|
+
logging.exception("Could not send attribute data to ClamAV. Maybe file is too big?")
|
|
111
|
+
return {"error": "Could not send attribute data to ClamAV. Maybe file is too big?"}
|
|
112
|
+
|
|
113
|
+
if status == "ERROR":
|
|
114
|
+
return {"error": "ClamAV returned error message: {}".format(reason)}
|
|
115
|
+
elif status == "OK":
|
|
116
|
+
return {"results": {}}
|
|
117
|
+
elif status == "FOUND":
|
|
118
|
+
return create_response(attribute, software_version, reason)
|
|
119
|
+
else:
|
|
120
|
+
return {"error": "ClamAV returned invalid status {}: {}".format(status, reason)}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def introspection():
|
|
124
|
+
return mispattributes
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def version():
|
|
128
|
+
moduleinfo["config"] = moduleconfig
|
|
129
|
+
return moduleinfo
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import requests
|
|
3
|
+
import uuid
|
|
4
|
+
from . import check_input_attribute, standard_error_message
|
|
5
|
+
from pymisp import MISPAttribute, MISPEvent, MISPObject
|
|
6
|
+
|
|
7
|
+
moduleinfo = {
|
|
8
|
+
'version': '0.1',
|
|
9
|
+
'author': 'Milo Volpicelli',
|
|
10
|
+
'description': 'Module to query Cluster25 CTI.',
|
|
11
|
+
'module-type': ['expansion', 'hover'],
|
|
12
|
+
'name': 'Cluster25 Expand',
|
|
13
|
+
'logo': 'cluster25.png',
|
|
14
|
+
'requirements': ['A Cluster25 API access (API id & key)'],
|
|
15
|
+
'features': 'This module takes a MISP attribute value as input to query the Cluster25CTI API. The result is then mapped into compatible MISP Objects and relative attributes.\n',
|
|
16
|
+
'references': [''],
|
|
17
|
+
'input': 'An Indicator value of type included in the following list:\n- domain\n- email-src\n- email-dst\n- filename\n- md5\n- sha1\n- sha256\n- ip-src\n- ip-dst\n- url\n- vulnerability\n- btc\n- xmr\n ja3-fingerprint-md5',
|
|
18
|
+
'output': 'A series of c25 MISP Objects with colletion of attributes mapped from Cluster25 CTI query result.',
|
|
19
|
+
}
|
|
20
|
+
moduleconfig = ['api_id', 'apikey', 'base_url']
|
|
21
|
+
misperrors = {'error': 'Error'}
|
|
22
|
+
misp_type_in = ['domain', 'email-src', 'email-dst', 'filename', 'md5', 'sha1', 'sha256', 'ip-src', 'ip-dst', 'url',
|
|
23
|
+
'vulnerability', 'btc', 'xmr', 'ja3-fingerprint-md5']
|
|
24
|
+
|
|
25
|
+
mapping_out = { # mapping between the MISP attributes type and the compatible Cluster25 indicator types.
|
|
26
|
+
'domain': {'type': 'domain', 'to_ids': True},
|
|
27
|
+
'email-src': {'type': 'email-src', 'to_ids': True},
|
|
28
|
+
'email-dst': {'type': 'email-dst', 'to_ids': True},
|
|
29
|
+
'filename': {'type': 'filename', 'to_ids': True},
|
|
30
|
+
'md5': {'type': 'md5', 'to_ids': True},
|
|
31
|
+
'sha1': {'type': 'sha1', 'to_ids': True},
|
|
32
|
+
'sha256': {'type': 'sha256', 'to_ids': True},
|
|
33
|
+
'ip-src': {'type': 'ip-src', 'to_ids': True},
|
|
34
|
+
'ip-dst': {'type': 'ip-dst', 'to_ids': True},
|
|
35
|
+
'url': {'type': 'url', 'to_ids': True},
|
|
36
|
+
'cve': {'type': 'vulnerability', 'to_ids': True},
|
|
37
|
+
'btcaddress': {'type': 'btc', 'to_ids': True},
|
|
38
|
+
'xmraddress': {'type': 'xmr', 'to_ids': True},
|
|
39
|
+
'ja3': {'type': 'ja3-fingerprint-md5', 'to_ids': True},
|
|
40
|
+
}
|
|
41
|
+
misp_type_out = [item['type'] for item in mapping_out.values()]
|
|
42
|
+
misp_attributes = {'input': misp_type_in, 'format': 'misp_standard'}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def handler(q=False):
|
|
46
|
+
if q is False:
|
|
47
|
+
return False
|
|
48
|
+
request = json.loads(q)
|
|
49
|
+
# validate Cluster25 params
|
|
50
|
+
if request.get('config'):
|
|
51
|
+
if request['config'].get('apikey') is None:
|
|
52
|
+
misperrors['error'] = 'Cluster25 apikey is missing'
|
|
53
|
+
return misperrors
|
|
54
|
+
if request['config'].get('api_id') is None:
|
|
55
|
+
misperrors['error'] = 'Cluster25 api_id is missing'
|
|
56
|
+
return misperrors
|
|
57
|
+
if request['config'].get('base_url') is None:
|
|
58
|
+
misperrors['error'] = 'Cluster25 base_url is missing'
|
|
59
|
+
return misperrors
|
|
60
|
+
|
|
61
|
+
# validate attribute
|
|
62
|
+
if not request.get('attribute') or not check_input_attribute(request['attribute']):
|
|
63
|
+
return {'error': f'{standard_error_message}, which should contain at least a type, a value and an uuid.'}
|
|
64
|
+
attribute = request.get('attribute')
|
|
65
|
+
if not any(input_type == attribute.get('type') for input_type in misp_type_in):
|
|
66
|
+
return {'error': 'Unsupported attribute type.'}
|
|
67
|
+
|
|
68
|
+
client = Cluster25CTI(request['config']['api_id'], request['config']['apikey'], request['config']['base_url'])
|
|
69
|
+
|
|
70
|
+
return lookup_indicator(client, request.get('attribute'))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def format_content(content):
|
|
74
|
+
if isinstance(content, str) or isinstance(content, bool) or isinstance(content, int) or isinstance(content, float):
|
|
75
|
+
return content
|
|
76
|
+
ret = ""
|
|
77
|
+
tmp_ret = []
|
|
78
|
+
if content is None:
|
|
79
|
+
return ret
|
|
80
|
+
is_dict = isinstance(content, dict)
|
|
81
|
+
is_list = isinstance(content, list)
|
|
82
|
+
for index, key in enumerate(content):
|
|
83
|
+
if is_dict:
|
|
84
|
+
if isinstance(content[key], dict):
|
|
85
|
+
ret = format_content(content[key])
|
|
86
|
+
elif isinstance(content[key], list):
|
|
87
|
+
for list_item in content[key]:
|
|
88
|
+
tmp_ret.append(format_content(list_item))
|
|
89
|
+
else:
|
|
90
|
+
tmp_ret.append(f"{key}: {content[key]}")
|
|
91
|
+
elif is_list:
|
|
92
|
+
if isinstance(content[index], str):
|
|
93
|
+
ret = ", ".join(content)
|
|
94
|
+
else:
|
|
95
|
+
ret = format_content(content)
|
|
96
|
+
if tmp_ret:
|
|
97
|
+
ret = " ".join(tmp_ret)
|
|
98
|
+
return ret
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def lookup_indicator(client, attr):
|
|
102
|
+
result = client.investigate(attr)
|
|
103
|
+
if result.get('error'):
|
|
104
|
+
return result
|
|
105
|
+
misp_event = MISPEvent()
|
|
106
|
+
attribute = MISPAttribute()
|
|
107
|
+
attribute.from_dict(**attr)
|
|
108
|
+
misp_event.add_attribute(**attribute)
|
|
109
|
+
|
|
110
|
+
misp_object_g = MISPObject('c25_generic_info')
|
|
111
|
+
misp_object_g.template_uuid = uuid.uuid4()
|
|
112
|
+
misp_object_g.description = 'c25_generic_info'
|
|
113
|
+
setattr(misp_object_g, 'meta-category', 'network')
|
|
114
|
+
|
|
115
|
+
misp_objects = []
|
|
116
|
+
for ind, entry in enumerate(result):
|
|
117
|
+
if isinstance(result[entry], dict):
|
|
118
|
+
tmp_obj = MISPObject(f"c25_{entry}")
|
|
119
|
+
tmp_obj.template_uuid = uuid.uuid4()
|
|
120
|
+
tmp_obj.description = f"c25_{entry}"
|
|
121
|
+
setattr(tmp_obj, 'meta-category', 'network')
|
|
122
|
+
tmp_obj.add_reference(attribute['uuid'], 'related-to')
|
|
123
|
+
for key in result[entry]:
|
|
124
|
+
if isinstance(result[entry][key], dict):
|
|
125
|
+
for index, item in enumerate(result[entry][key]):
|
|
126
|
+
if result[entry][key][item]:
|
|
127
|
+
tmp_obj.add_attribute(f"{entry}_{key}_{item}", **{'type': 'text', 'value': format_content(
|
|
128
|
+
result[entry][key][item])})
|
|
129
|
+
|
|
130
|
+
elif isinstance(result[entry][key], list):
|
|
131
|
+
for index, item in enumerate(result[entry][key]):
|
|
132
|
+
if isinstance(item, dict):
|
|
133
|
+
tmp_obj_2 = MISPObject(f"c25_{entry}_{key}_{index + 1}")
|
|
134
|
+
tmp_obj_2.template_uuid = uuid.uuid4()
|
|
135
|
+
tmp_obj_2.description = f"c25_{entry}_{key}"
|
|
136
|
+
setattr(tmp_obj_2, 'meta-category', 'network')
|
|
137
|
+
tmp_obj_2.add_reference(attribute['uuid'], 'related-to')
|
|
138
|
+
for sub_key in item:
|
|
139
|
+
if isinstance(item[sub_key], list):
|
|
140
|
+
for sub_item in item[sub_key]:
|
|
141
|
+
if isinstance(sub_item, dict):
|
|
142
|
+
tmp_obj_3 = MISPObject(f"c25_{entry}_{sub_key}_{index + 1}")
|
|
143
|
+
tmp_obj_3.template_uuid = uuid.uuid4()
|
|
144
|
+
tmp_obj_3.description = f"c25_{entry}_{sub_key}"
|
|
145
|
+
setattr(tmp_obj_3, 'meta-category', 'network')
|
|
146
|
+
tmp_obj_3.add_reference(attribute['uuid'], 'related-to')
|
|
147
|
+
for sub_sub_key in sub_item:
|
|
148
|
+
if isinstance(sub_item[sub_sub_key], list):
|
|
149
|
+
for idx, sub_sub_item in enumerate(sub_item[sub_sub_key]):
|
|
150
|
+
if sub_sub_item.get("name"):
|
|
151
|
+
sub_sub_item = sub_sub_item.get("name")
|
|
152
|
+
tmp_obj_3.add_attribute(f"{sub_sub_key}_{idx + 1}",
|
|
153
|
+
**{'type': 'text',
|
|
154
|
+
'value': format_content(
|
|
155
|
+
sub_sub_item)})
|
|
156
|
+
else:
|
|
157
|
+
tmp_obj_3.add_attribute(sub_sub_key,
|
|
158
|
+
**{'type': 'text',
|
|
159
|
+
'value': format_content(
|
|
160
|
+
sub_item[sub_sub_key])})
|
|
161
|
+
misp_objects.append(tmp_obj_3)
|
|
162
|
+
else:
|
|
163
|
+
tmp_obj_2.add_attribute(sub_key, **{'type': 'text',
|
|
164
|
+
'value': format_content(sub_item)})
|
|
165
|
+
|
|
166
|
+
elif item[sub_key]:
|
|
167
|
+
tmp_obj_2.add_attribute(sub_key,
|
|
168
|
+
**{'type': 'text', 'value': format_content(item[sub_key])})
|
|
169
|
+
misp_objects.append(tmp_obj_2)
|
|
170
|
+
elif item is not None:
|
|
171
|
+
tmp_obj.add_attribute(f"{entry}_{key}", **{'type': 'text', 'value': format_content(item)})
|
|
172
|
+
elif result[entry][key] is not None:
|
|
173
|
+
tmp_obj.add_attribute(key, **{'type': 'text', 'value': result[entry][key]})
|
|
174
|
+
|
|
175
|
+
if tmp_obj.attributes:
|
|
176
|
+
misp_objects.append(tmp_obj)
|
|
177
|
+
|
|
178
|
+
elif isinstance(result[entry], list):
|
|
179
|
+
for index, key in enumerate(result[entry]):
|
|
180
|
+
if isinstance(key, dict):
|
|
181
|
+
tmp_obj = MISPObject(f"c25_{entry}_{index + 1}")
|
|
182
|
+
tmp_obj.template_uuid = uuid.uuid4()
|
|
183
|
+
tmp_obj.description = f"c25_{entry}_{index + 1}"
|
|
184
|
+
setattr(tmp_obj, 'meta-category', 'network')
|
|
185
|
+
tmp_obj.add_reference(attribute['uuid'], 'related-to')
|
|
186
|
+
for item in key:
|
|
187
|
+
if key[item]:
|
|
188
|
+
tmp_obj.add_attribute(item, **{'type': 'text', 'value': format_content(key[item])})
|
|
189
|
+
tmp_obj.add_reference(attribute['uuid'], 'related-to')
|
|
190
|
+
misp_objects.append(tmp_obj)
|
|
191
|
+
elif key is not None:
|
|
192
|
+
misp_object_g.add_attribute(f"{entry}_{index + 1}",
|
|
193
|
+
**{'type': 'text', 'value': format_content(key)})
|
|
194
|
+
else:
|
|
195
|
+
if result[entry]:
|
|
196
|
+
misp_object_g.add_attribute(entry, **{'type': 'text', 'value': result[entry]})
|
|
197
|
+
|
|
198
|
+
misp_object_g.add_reference(attribute['uuid'], 'related-to')
|
|
199
|
+
misp_event.add_object(misp_object_g)
|
|
200
|
+
for misp_object in misp_objects:
|
|
201
|
+
misp_event.add_object(misp_object)
|
|
202
|
+
|
|
203
|
+
event = json.loads(misp_event.to_json())
|
|
204
|
+
results = {key: event[key] for key in ('Attribute', 'Object')}
|
|
205
|
+
return {'results': results}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def introspection():
|
|
209
|
+
return misp_attributes
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def version():
|
|
213
|
+
moduleinfo['config'] = moduleconfig
|
|
214
|
+
return moduleinfo
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class Cluster25CTI:
|
|
218
|
+
def __init__(self, customer_id=None, customer_key=None, base_url=None):
|
|
219
|
+
self.client_id = customer_id
|
|
220
|
+
self.client_secret = customer_key
|
|
221
|
+
self.base_url = base_url
|
|
222
|
+
self.current_token = self._get_cluster25_token()
|
|
223
|
+
self.headers = {"Authorization": f"Bearer {self.current_token}"}
|
|
224
|
+
|
|
225
|
+
def _get_cluster25_token(self):
|
|
226
|
+
payload = {"client_id": self.client_id, "client_secret": self.client_secret}
|
|
227
|
+
r = requests.post(url=f"{self.base_url}/token", json=payload, headers={"Content-Type": "application/json"})
|
|
228
|
+
if r.status_code != 200:
|
|
229
|
+
return {'error': f"Unable to retrieve the token from C25 platform, status {r.status_code}"}
|
|
230
|
+
return r.json()["data"]["token"]
|
|
231
|
+
|
|
232
|
+
def investigate(self, indicator) -> dict:
|
|
233
|
+
params = {'indicator': indicator.get('value')}
|
|
234
|
+
r = requests.get(url=f"{self.base_url}/investigate", params=params, headers=self.headers)
|
|
235
|
+
if r.status_code != 200:
|
|
236
|
+
return {'error': f"Unable to retrieve investigate result for indicator '{indicator.get('value')}' "
|
|
237
|
+
f"from C25 platform, status {r.status_code}"}
|
|
238
|
+
return r.json()["data"]
|
|
239
|
+
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import requests
|
|
3
|
+
|
|
4
|
+
misperrors = {'error': 'Error'}
|
|
5
|
+
mispattributes = {'input': ['hostname', 'domain']}
|
|
6
|
+
|
|
7
|
+
# possible module-types: 'expansion', 'hover' or both
|
|
8
|
+
moduleinfo = {
|
|
9
|
+
'version': '1',
|
|
10
|
+
'author': 'Hannah Ward',
|
|
11
|
+
'description': 'Module to expand country codes.',
|
|
12
|
+
'module-type': ['hover'],
|
|
13
|
+
'name': 'Country Code',
|
|
14
|
+
'logo': '',
|
|
15
|
+
'requirements': [],
|
|
16
|
+
'features': 'The module takes a domain or a hostname as input, and returns the country it belongs to.\n\nFor non country domains, a list of the most common possible extensions is used.',
|
|
17
|
+
'references': [],
|
|
18
|
+
'input': 'Hostname or domain attribute.',
|
|
19
|
+
'output': 'Text with the country code the input belongs to.',
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
# config fields that your code expects from the site admin
|
|
23
|
+
moduleconfig = []
|
|
24
|
+
|
|
25
|
+
common_tlds = {"com": "Commercial (Worldwide)",
|
|
26
|
+
"org": "Organisation (Worldwide)",
|
|
27
|
+
"net": "Network (Worldwide)",
|
|
28
|
+
"int": "International (Worldwide)",
|
|
29
|
+
"edu": "Education (Usually USA)",
|
|
30
|
+
"gov": "Government (USA)"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def parse_country_code(extension):
|
|
35
|
+
# Retrieve a json full of country info
|
|
36
|
+
try:
|
|
37
|
+
codes = requests.get("http://www.geognos.com/api/en/countries/info/all.json").json()
|
|
38
|
+
except Exception:
|
|
39
|
+
return "http://www.geognos.com/api/en/countries/info/all.json not reachable"
|
|
40
|
+
if not codes.get('StatusMsg') or not codes["StatusMsg"] == "OK":
|
|
41
|
+
return 'Not able to get the countrycode references from http://www.geognos.com/api/en/countries/info/all.json'
|
|
42
|
+
for country in codes['Results'].values():
|
|
43
|
+
if country['CountryCodes']['tld'] == extension:
|
|
44
|
+
return country['Name']
|
|
45
|
+
return "Unknown"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def handler(q=False):
|
|
49
|
+
if q is False:
|
|
50
|
+
return False
|
|
51
|
+
request = json.loads(q)
|
|
52
|
+
domain = request["domain"] if "domain" in request else request["hostname"]
|
|
53
|
+
|
|
54
|
+
# Get the extension
|
|
55
|
+
ext = domain.split(".")[-1]
|
|
56
|
+
|
|
57
|
+
# Check if it's a common, non country one
|
|
58
|
+
val = common_tlds[ext] if ext in common_tlds.keys() else parse_country_code(ext)
|
|
59
|
+
r = {'results': [{'types': ['text'], 'values':[val]}]}
|
|
60
|
+
return r
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def introspection():
|
|
64
|
+
return mispattributes
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def version():
|
|
68
|
+
moduleinfo['config'] = moduleconfig
|
|
69
|
+
return moduleinfo
|