misp-modules 2.4.196__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- misp_modules/__init__.py +379 -0
- misp_modules/helpers/__init__.py +1 -0
- misp_modules/helpers/cache.py +84 -0
- misp_modules/lib/__init__.py +4 -0
- misp_modules/lib/_vmray/__init__.py +0 -0
- misp_modules/lib/_vmray/parser.py +1417 -0
- misp_modules/lib/_vmray/rest_api.py +148 -0
- misp_modules/lib/cof2misp/LICENSE-2.0.txt +202 -0
- misp_modules/lib/cof2misp/__init__.py +0 -0
- misp_modules/lib/cof2misp/cof.py +165 -0
- misp_modules/lib/joe_mapping.py +114 -0
- misp_modules/lib/joe_parser.py +573 -0
- misp_modules/lib/lastline_api.py +841 -0
- misp_modules/lib/qintel_helper.py +263 -0
- misp_modules/lib/stix2misp.py +2080 -0
- misp_modules/lib/stix2misp_mapping.py +460 -0
- misp_modules/lib/synonymsToTagNames.json +1 -0
- misp_modules/lib/vt_graph_parser/__init__.py +8 -0
- misp_modules/lib/vt_graph_parser/errors.py +20 -0
- misp_modules/lib/vt_graph_parser/helpers/__init__.py +7 -0
- misp_modules/lib/vt_graph_parser/helpers/parsers.py +88 -0
- misp_modules/lib/vt_graph_parser/helpers/rules.py +304 -0
- misp_modules/lib/vt_graph_parser/helpers/wrappers.py +58 -0
- misp_modules/lib/vt_graph_parser/importers/__init__.py +7 -0
- misp_modules/lib/vt_graph_parser/importers/base.py +98 -0
- misp_modules/lib/vt_graph_parser/importers/pymisp_response.py +73 -0
- misp_modules/modules/__init__.py +4 -0
- misp_modules/modules/action_mod/__init__.py +1 -0
- misp_modules/modules/action_mod/_utils/__init__.py +1 -0
- misp_modules/modules/action_mod/_utils/utils.py +70 -0
- misp_modules/modules/action_mod/mattermost.py +113 -0
- misp_modules/modules/action_mod/slack.py +96 -0
- misp_modules/modules/action_mod/testaction.py +68 -0
- misp_modules/modules/expansion/__init__.py +36 -0
- misp_modules/modules/expansion/_dnsdb_query/COPYRIGHT +27 -0
- misp_modules/modules/expansion/_dnsdb_query/LICENSE +202 -0
- misp_modules/modules/expansion/_dnsdb_query/README.md +162 -0
- misp_modules/modules/expansion/_dnsdb_query/__init__.py +0 -0
- misp_modules/modules/expansion/_dnsdb_query/dnsdb_query.py +327 -0
- misp_modules/modules/expansion/_ransomcoindb/__init__.py +0 -0
- misp_modules/modules/expansion/_ransomcoindb/ransomcoindb.py +96 -0
- misp_modules/modules/expansion/_vulnerability_parser/__init__.py +0 -0
- misp_modules/modules/expansion/_vulnerability_parser/vulnerability_parser.py +118 -0
- misp_modules/modules/expansion/abuseipdb.py +146 -0
- misp_modules/modules/expansion/apiosintds.py +365 -0
- misp_modules/modules/expansion/apivoid.py +133 -0
- misp_modules/modules/expansion/assemblyline_query.py +179 -0
- misp_modules/modules/expansion/assemblyline_submit.py +100 -0
- misp_modules/modules/expansion/backscatter_io.py +84 -0
- misp_modules/modules/expansion/btc_scam_check.py +54 -0
- misp_modules/modules/expansion/btc_steroids.py +238 -0
- misp_modules/modules/expansion/censys_enrich.py +287 -0
- misp_modules/modules/expansion/circl_passivedns.py +85 -0
- misp_modules/modules/expansion/circl_passivessl.py +113 -0
- misp_modules/modules/expansion/clamav.py +129 -0
- misp_modules/modules/expansion/cluster25_expand.py +239 -0
- misp_modules/modules/expansion/countrycode.py +69 -0
- misp_modules/modules/expansion/cpe.py +140 -0
- misp_modules/modules/expansion/crowdsec.py +235 -0
- misp_modules/modules/expansion/crowdstrike_falcon.py +148 -0
- misp_modules/modules/expansion/cuckoo_submit.py +161 -0
- misp_modules/modules/expansion/cve.py +57 -0
- misp_modules/modules/expansion/cve_advanced.py +185 -0
- misp_modules/modules/expansion/cytomic_orion.py +196 -0
- misp_modules/modules/expansion/dbl_spamhaus.py +78 -0
- misp_modules/modules/expansion/dns.py +71 -0
- misp_modules/modules/expansion/docx_enrich.py +71 -0
- misp_modules/modules/expansion/domaintools.py +290 -0
- misp_modules/modules/expansion/eql.py +91 -0
- misp_modules/modules/expansion/eupi.py +86 -0
- misp_modules/modules/expansion/extract_url_components.py +83 -0
- misp_modules/modules/expansion/farsight_passivedns.py +243 -0
- misp_modules/modules/expansion/geoip_asn.py +75 -0
- misp_modules/modules/expansion/geoip_city.py +75 -0
- misp_modules/modules/expansion/geoip_country.py +73 -0
- misp_modules/modules/expansion/google_safe_browsing.py +86 -0
- misp_modules/modules/expansion/google_search.py +57 -0
- misp_modules/modules/expansion/google_threat_intelligence.py +453 -0
- misp_modules/modules/expansion/greynoise.py +346 -0
- misp_modules/modules/expansion/hashdd.py +53 -0
- misp_modules/modules/expansion/hashlookup.py +118 -0
- misp_modules/modules/expansion/hibp.py +62 -0
- misp_modules/modules/expansion/html_to_markdown.py +63 -0
- misp_modules/modules/expansion/hyasinsight.py +881 -0
- misp_modules/modules/expansion/intel471.py +73 -0
- misp_modules/modules/expansion/intelmq_eventdb.py.experimental +67 -0
- misp_modules/modules/expansion/ip2locationio.py +90 -0
- misp_modules/modules/expansion/ipasn.py +70 -0
- misp_modules/modules/expansion/ipinfo.py +112 -0
- misp_modules/modules/expansion/ipqs_fraud_and_risk_scoring.py +633 -0
- misp_modules/modules/expansion/iprep.py +98 -0
- misp_modules/modules/expansion/jinja_template_rendering.py +54 -0
- misp_modules/modules/expansion/joesandbox_query.py +91 -0
- misp_modules/modules/expansion/joesandbox_submit.py +147 -0
- misp_modules/modules/expansion/lastline_query.py +148 -0
- misp_modules/modules/expansion/lastline_submit.py +180 -0
- misp_modules/modules/expansion/macaddress_io.py +130 -0
- misp_modules/modules/expansion/macvendors.py +55 -0
- misp_modules/modules/expansion/malshare_upload.py +107 -0
- misp_modules/modules/expansion/malwarebazaar.py +67 -0
- misp_modules/modules/expansion/mcafee_insights_enrich.py +249 -0
- misp_modules/modules/expansion/mmdb_lookup.py +138 -0
- misp_modules/modules/expansion/module.py.skeleton +44 -0
- misp_modules/modules/expansion/mwdb.py +152 -0
- misp_modules/modules/expansion/ocr_enrich.py +74 -0
- misp_modules/modules/expansion/ods_enrich.py +71 -0
- misp_modules/modules/expansion/odt_enrich.py +61 -0
- misp_modules/modules/expansion/onyphe.py +243 -0
- misp_modules/modules/expansion/onyphe_full.py +387 -0
- misp_modules/modules/expansion/otx.py +167 -0
- misp_modules/modules/expansion/passive_ssh.py +150 -0
- misp_modules/modules/expansion/passivetotal.py +358 -0
- misp_modules/modules/expansion/pdf_enrich.py +58 -0
- misp_modules/modules/expansion/pptx_enrich.py +65 -0
- misp_modules/modules/expansion/qintel_qsentry.py +229 -0
- misp_modules/modules/expansion/qrcode.py +99 -0
- misp_modules/modules/expansion/ransomcoindb.py +82 -0
- misp_modules/modules/expansion/rbl.py +126 -0
- misp_modules/modules/expansion/recordedfuture.py +544 -0
- misp_modules/modules/expansion/reversedns.py +77 -0
- misp_modules/modules/expansion/securitytrails.py +571 -0
- misp_modules/modules/expansion/shodan.py +244 -0
- misp_modules/modules/expansion/sigma_queries.py +61 -0
- misp_modules/modules/expansion/sigma_syntax_validator.py +49 -0
- misp_modules/modules/expansion/sigmf_expand.py +303 -0
- misp_modules/modules/expansion/socialscan.py +108 -0
- misp_modules/modules/expansion/sophoslabs_intelix.py +146 -0
- misp_modules/modules/expansion/sourcecache.py +57 -0
- misp_modules/modules/expansion/stairwell.py +156 -0
- misp_modules/modules/expansion/stix2_pattern_syntax_validator.py +56 -0
- misp_modules/modules/expansion/threatcrowd.py +170 -0
- misp_modules/modules/expansion/threatfox.py +75 -0
- misp_modules/modules/expansion/threatminer.py +152 -0
- misp_modules/modules/expansion/triage_submit.py +119 -0
- misp_modules/modules/expansion/trustar_enrich.py +235 -0
- misp_modules/modules/expansion/urlhaus.py +164 -0
- misp_modules/modules/expansion/urlscan.py +269 -0
- misp_modules/modules/expansion/variotdbs.py +133 -0
- misp_modules/modules/expansion/virustotal.py +311 -0
- misp_modules/modules/expansion/virustotal_public.py +272 -0
- misp_modules/modules/expansion/virustotal_upload.py +91 -0
- misp_modules/modules/expansion/vmray_submit.py +161 -0
- misp_modules/modules/expansion/vmware_nsx.py +628 -0
- misp_modules/modules/expansion/vulndb.py +292 -0
- misp_modules/modules/expansion/vulnerability_lookup.py +324 -0
- misp_modules/modules/expansion/vulners.py +81 -0
- misp_modules/modules/expansion/vysion.py +221 -0
- misp_modules/modules/expansion/whois.py +72 -0
- misp_modules/modules/expansion/whoisfreaks.py +232 -0
- misp_modules/modules/expansion/wiki.py +58 -0
- misp_modules/modules/expansion/xforceexchange.py +189 -0
- misp_modules/modules/expansion/xlsx_enrich.py +63 -0
- misp_modules/modules/expansion/yara_query.py +70 -0
- misp_modules/modules/expansion/yara_syntax_validator.py +49 -0
- misp_modules/modules/expansion/yeti.py +196 -0
- misp_modules/modules/export_mod/__init__.py +3 -0
- misp_modules/modules/export_mod/cef_export.py +93 -0
- misp_modules/modules/export_mod/cisco_firesight_manager_ACL_rule_export.py +150 -0
- misp_modules/modules/export_mod/defender_endpoint_export.py +141 -0
- misp_modules/modules/export_mod/goamlexport.py +255 -0
- misp_modules/modules/export_mod/liteexport.py +97 -0
- misp_modules/modules/export_mod/mass_eql_export.py +106 -0
- misp_modules/modules/export_mod/nexthinkexport.py +131 -0
- misp_modules/modules/export_mod/osqueryexport.py +125 -0
- misp_modules/modules/export_mod/pdfexport.py +107 -0
- misp_modules/modules/export_mod/testexport.py +73 -0
- misp_modules/modules/export_mod/threatStream_misp_export.py +114 -0
- misp_modules/modules/export_mod/threat_connect_export.py +125 -0
- misp_modules/modules/export_mod/virustotal_collections.py +141 -0
- misp_modules/modules/export_mod/vt_graph.py +120 -0
- misp_modules/modules/export_mod/yara_export.py +290 -0
- misp_modules/modules/import_mod/__init__.py +22 -0
- misp_modules/modules/import_mod/cof2misp.py +264 -0
- misp_modules/modules/import_mod/csvimport.py +319 -0
- misp_modules/modules/import_mod/cuckooimport.py +749 -0
- misp_modules/modules/import_mod/email_import.py +294 -0
- misp_modules/modules/import_mod/goamlimport.py +188 -0
- misp_modules/modules/import_mod/import_blueprint.py +93 -0
- misp_modules/modules/import_mod/joe_import.py +74 -0
- misp_modules/modules/import_mod/lastline_import.py +160 -0
- misp_modules/modules/import_mod/mispjson.py +73 -0
- misp_modules/modules/import_mod/ocr.py +122 -0
- misp_modules/modules/import_mod/openiocimport.py +99 -0
- misp_modules/modules/import_mod/taxii21.py +383 -0
- misp_modules/modules/import_mod/testimport.py +73 -0
- misp_modules/modules/import_mod/threatanalyzer_import.py +552 -0
- misp_modules/modules/import_mod/url_import.py +97 -0
- misp_modules/modules/import_mod/vmray_import.py +96 -0
- misp_modules/modules/import_mod/vmray_summary_json_import.py +87 -0
- misp_modules-2.4.196.dist-info/LICENSE +661 -0
- misp_modules-2.4.196.dist-info/METADATA +281 -0
- misp_modules-2.4.196.dist-info/RECORD +194 -0
- misp_modules-2.4.196.dist-info/WHEEL +4 -0
- misp_modules-2.4.196.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import requests
|
|
3
|
+
from . import check_input_attribute, standard_error_message
|
|
4
|
+
from pymisp import MISPAttribute, MISPEvent, MISPObject
|
|
5
|
+
|
|
6
|
+
misperrors = {'error': 'Error'}
|
|
7
|
+
mispattributes = {'input': ['domain', 'hostname', 'email', 'email-src', 'email-dst', 'email-reply-to', 'dns-soa-email', 'target-email', 'whois-registrant-email'], 'format': 'misp_standard'}
|
|
8
|
+
moduleinfo = {
|
|
9
|
+
'version': '0.2',
|
|
10
|
+
'author': 'Christian Studer',
|
|
11
|
+
'description': 'Module to query APIVoid with some domain attributes.',
|
|
12
|
+
'module-type': ['expansion', 'hover'],
|
|
13
|
+
'name': 'APIVoid',
|
|
14
|
+
'logo': 'apivoid.png',
|
|
15
|
+
'requirements': ['A valid APIVoid API key with enough credits to proceed 2 queries'],
|
|
16
|
+
'features': 'This module takes a domain name and queries API Void to get the related DNS records and the SSL certificates. It returns then those pieces of data as MISP objects that can be added to the event.\n\nTo make it work, a valid API key and enough credits to proceed 2 queries (0.06 + 0.07 credits) are required.',
|
|
17
|
+
'references': ['https://www.apivoid.com/'],
|
|
18
|
+
'input': 'A domain attribute.',
|
|
19
|
+
'output': 'DNS records and SSL certificates related to the domain.',
|
|
20
|
+
}
|
|
21
|
+
moduleconfig = ['apikey']
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class APIVoidParser():
|
|
25
|
+
def __init__(self, attribute):
|
|
26
|
+
self.misp_event = MISPEvent()
|
|
27
|
+
self.attribute = MISPAttribute()
|
|
28
|
+
self.attribute.from_dict(**attribute)
|
|
29
|
+
self.misp_event.add_attribute(**self.attribute)
|
|
30
|
+
self.url = 'https://endpoint.apivoid.com/{}/v1/pay-as-you-go/?key={}&'
|
|
31
|
+
|
|
32
|
+
def get_results(self):
|
|
33
|
+
if hasattr(self, 'result'):
|
|
34
|
+
return self.result
|
|
35
|
+
event = json.loads(self.misp_event.to_json())
|
|
36
|
+
results = {key: event[key] for key in ('Attribute', 'Object')}
|
|
37
|
+
return {'results': results}
|
|
38
|
+
|
|
39
|
+
def parse_domain(self, apikey):
|
|
40
|
+
feature = 'dnslookup'
|
|
41
|
+
if requests.get(f'{self.url.format(feature, apikey)}stats').json()['credits_remained'] < 0.13:
|
|
42
|
+
self.result = {'error': 'You do not have enough APIVoid credits to proceed your request.'}
|
|
43
|
+
return
|
|
44
|
+
mapping = {'A': 'resolution-of', 'MX': 'mail-server-of', 'NS': 'server-name-of'}
|
|
45
|
+
dnslookup = requests.get(f'{self.url.format(feature, apikey)}action=dns-any&host={self.attribute.value}').json()
|
|
46
|
+
for item in dnslookup['data']['records']['items']:
|
|
47
|
+
record_type = item['type']
|
|
48
|
+
try:
|
|
49
|
+
relationship = mapping[record_type]
|
|
50
|
+
except KeyError:
|
|
51
|
+
continue
|
|
52
|
+
self._handle_dns_record(item, record_type, relationship)
|
|
53
|
+
ssl = requests.get(f'{self.url.format("sslinfo", apikey)}host={self.attribute.value}').json()
|
|
54
|
+
self._parse_ssl_certificate(ssl['data']['certificate'])
|
|
55
|
+
|
|
56
|
+
def handle_email(self, apikey):
|
|
57
|
+
feature = 'emailverify'
|
|
58
|
+
if requests.get(f'{self.url.format(feature, apikey)}stats').json()['credits_remained'] < 0.06:
|
|
59
|
+
self.result = {'error': 'You do not have enough APIVoid credits to proceed your request.'}
|
|
60
|
+
return
|
|
61
|
+
emaillookup = requests.get(f'{self.url.format(feature, apikey)}email={self.attribute.value}').json()
|
|
62
|
+
email_verification = MISPObject('apivoid-email-verification')
|
|
63
|
+
boolean_attributes = ['valid_format', 'suspicious_username', 'suspicious_email', 'dirty_words_username',
|
|
64
|
+
'suspicious_email', 'valid_tld', 'disposable', 'has_a_records', 'has_mx_records',
|
|
65
|
+
'has_spf_records', 'is_spoofable', 'dmarc_configured', 'dmarc_enforced', 'free_email',
|
|
66
|
+
'russian_free_email', 'china_free_email', 'suspicious_domain', 'dirty_words_domain',
|
|
67
|
+
'domain_popular', 'risky_tld', 'police_domain', 'government_domain', 'educational_domain',
|
|
68
|
+
'should_block']
|
|
69
|
+
for boolean_attribute in boolean_attributes:
|
|
70
|
+
email_verification.add_attribute(boolean_attribute,
|
|
71
|
+
**{'type': 'boolean', 'value': emaillookup['data'][boolean_attribute]})
|
|
72
|
+
email_verification.add_attribute('email', **{'type': 'email', 'value': emaillookup['data']['email']})
|
|
73
|
+
email_verification.add_attribute('username', **{'type': 'text', 'value': emaillookup['data']['username']})
|
|
74
|
+
email_verification.add_attribute('role_address',
|
|
75
|
+
**{'type': 'boolean', 'value': emaillookup['data']['role_address']})
|
|
76
|
+
email_verification.add_attribute('domain', **{'type': 'domain', 'value': emaillookup['data']['domain']})
|
|
77
|
+
email_verification.add_attribute('score', **{'type': 'float', 'value': emaillookup['data']['score']})
|
|
78
|
+
email_verification.add_reference(self.attribute['uuid'], 'related-to')
|
|
79
|
+
self.misp_event.add_object(email_verification)
|
|
80
|
+
|
|
81
|
+
def _handle_dns_record(self, item, record_type, relationship):
|
|
82
|
+
dns_record = MISPObject('dns-record')
|
|
83
|
+
dns_record.add_attribute('queried-domain', type='domain', value=item['host'])
|
|
84
|
+
attribute_type, feature = ('ip-dst', 'ip') if record_type == 'A' else ('domain', 'target')
|
|
85
|
+
dns_record.add_attribute(f'{record_type.lower()}-record', type=attribute_type, value=item[feature])
|
|
86
|
+
dns_record.add_reference(self.attribute.uuid, relationship)
|
|
87
|
+
self.misp_event.add_object(**dns_record)
|
|
88
|
+
|
|
89
|
+
def _parse_ssl_certificate(self, certificate):
|
|
90
|
+
x509 = MISPObject('x509')
|
|
91
|
+
fingerprint = 'x509-fingerprint-sha1'
|
|
92
|
+
x509.add_attribute(fingerprint, type=fingerprint, value=certificate['fingerprint'])
|
|
93
|
+
x509_mapping = {'subject': {'name': ('text', 'subject')},
|
|
94
|
+
'issuer': {'common_name': ('text', 'issuer')},
|
|
95
|
+
'signature': {'serial': ('text', 'serial-number')},
|
|
96
|
+
'validity': {'valid_from': ('datetime', 'validity-not-before'),
|
|
97
|
+
'valid_to': ('datetime', 'validity-not-after')}}
|
|
98
|
+
certificate = certificate['details']
|
|
99
|
+
for feature, subfeatures in x509_mapping.items():
|
|
100
|
+
for subfeature, mapping in subfeatures.items():
|
|
101
|
+
attribute_type, relation = mapping
|
|
102
|
+
x509.add_attribute(relation, type=attribute_type, value=certificate[feature][subfeature])
|
|
103
|
+
x509.add_reference(self.attribute.uuid, 'seen-by')
|
|
104
|
+
self.misp_event.add_object(**x509)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def handler(q=False):
|
|
108
|
+
if q is False:
|
|
109
|
+
return False
|
|
110
|
+
request = json.loads(q)
|
|
111
|
+
if not request.get('config', {}).get('apikey'):
|
|
112
|
+
return {'error': 'An API key for APIVoid is required.'}
|
|
113
|
+
if not request.get('attribute') or not check_input_attribute(request['attribute']):
|
|
114
|
+
return {'error': f'{standard_error_message}, which should contain at least a type, a value and an uuid.'}
|
|
115
|
+
attribute = request['attribute']
|
|
116
|
+
if attribute['type'] not in mispattributes['input']:
|
|
117
|
+
return {'error': 'Unsupported attribute type.'}
|
|
118
|
+
apikey = request['config']['apikey']
|
|
119
|
+
apivoid_parser = APIVoidParser(attribute)
|
|
120
|
+
if attribute['type'] in ['domain', 'hostname']:
|
|
121
|
+
apivoid_parser.parse_domain(apikey)
|
|
122
|
+
else:
|
|
123
|
+
apivoid_parser.handle_email(apikey)
|
|
124
|
+
return apivoid_parser.get_results()
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def introspection():
|
|
128
|
+
return mispattributes
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def version():
|
|
132
|
+
moduleinfo['config'] = moduleconfig
|
|
133
|
+
return moduleinfo
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import json
|
|
3
|
+
from . import check_input_attribute, standard_error_message
|
|
4
|
+
from assemblyline_client import Client, ClientError
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
from pymisp import MISPAttribute, MISPEvent, MISPObject
|
|
7
|
+
|
|
8
|
+
misperrors = {'error': 'Error'}
|
|
9
|
+
mispattributes = {'input': ['link'], 'format': 'misp_standard'}
|
|
10
|
+
|
|
11
|
+
moduleinfo = {
|
|
12
|
+
'version': '1',
|
|
13
|
+
'author': 'Christian Studer',
|
|
14
|
+
'description': 'A module tu query the AssemblyLine API with a submission ID to get the submission report and parse it.',
|
|
15
|
+
'module-type': ['expansion'],
|
|
16
|
+
'name': 'AssemblyLine Query',
|
|
17
|
+
'logo': 'assemblyline.png',
|
|
18
|
+
'requirements': ['assemblyline_client: Python library to query the AssemblyLine rest API.'],
|
|
19
|
+
'features': 'The module requires the address of the AssemblyLine server you want to query as well as your credentials used for this instance. Credentials include the used-ID and an API key or the password associated to the user-ID.\n\nThe submission ID extracted from the submission link is then used to query AssemblyLine and get the full submission report. This report is parsed to extract file objects and the associated IPs, domains or URLs the files are connecting to.\n\nSome more data may be parsed in the future.',
|
|
20
|
+
'references': ['https://www.cyber.gc.ca/en/assemblyline'],
|
|
21
|
+
'input': 'Link of an AssemblyLine submission report.',
|
|
22
|
+
'output': 'MISP attributes & objects parsed from the AssemblyLine submission.',
|
|
23
|
+
}
|
|
24
|
+
moduleconfig = ["apiurl", "user_id", "apikey", "password", "verifyssl"]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AssemblyLineParser():
|
|
28
|
+
def __init__(self):
|
|
29
|
+
self.misp_event = MISPEvent()
|
|
30
|
+
self.results = {}
|
|
31
|
+
self.attribute = {'to_ids': True}
|
|
32
|
+
self._results_mapping = {'NET_DOMAIN_NAME': 'domain', 'NET_FULL_URI': 'url',
|
|
33
|
+
'NET_IP': 'ip-dst'}
|
|
34
|
+
self._file_mapping = {'entropy': {'type': 'float', 'object_relation': 'entropy'},
|
|
35
|
+
'md5': {'type': 'md5', 'object_relation': 'md5'},
|
|
36
|
+
'mime': {'type': 'mime-type', 'object_relation': 'mimetype'},
|
|
37
|
+
'sha1': {'type': 'sha1', 'object_relation': 'sha1'},
|
|
38
|
+
'sha256': {'type': 'sha256', 'object_relation': 'sha256'},
|
|
39
|
+
'size': {'type': 'size-in-bytes', 'object_relation': 'size-in-bytes'},
|
|
40
|
+
'ssdeep': {'type': 'ssdeep', 'object_relation': 'ssdeep'}}
|
|
41
|
+
|
|
42
|
+
def get_submission(self, attribute, client):
|
|
43
|
+
sid = attribute['value'].split('=')[-1]
|
|
44
|
+
try:
|
|
45
|
+
if not client.submission.is_completed(sid):
|
|
46
|
+
self.results['error'] = 'Submission not completed, please try again later.'
|
|
47
|
+
return
|
|
48
|
+
except Exception as e:
|
|
49
|
+
self.results['error'] = f'Something went wrong while trying to check if the submission in AssemblyLine is completed: {e.__str__()}'
|
|
50
|
+
return
|
|
51
|
+
try:
|
|
52
|
+
submission = client.submission.full(sid)
|
|
53
|
+
except Exception as e:
|
|
54
|
+
self.results['error'] = f"Something went wrong while getting the submission from AssemblyLine: {e.__str__()}"
|
|
55
|
+
return
|
|
56
|
+
self._parse_report(submission)
|
|
57
|
+
|
|
58
|
+
def finalize_results(self):
|
|
59
|
+
if 'error' in self.results:
|
|
60
|
+
return self.results
|
|
61
|
+
event = json.loads(self.misp_event.to_json())
|
|
62
|
+
results = {key: event[key] for key in ('Attribute', 'Object', 'Tag') if (key in event and event[key])}
|
|
63
|
+
return {'results': results}
|
|
64
|
+
|
|
65
|
+
def _create_attribute(self, result, attribute_type):
|
|
66
|
+
attribute = MISPAttribute()
|
|
67
|
+
attribute.from_dict(type=attribute_type, value=result['value'], **self.attribute)
|
|
68
|
+
if result['classification'] != 'UNCLASSIFIED':
|
|
69
|
+
attribute.add_tag(result['classification'].lower())
|
|
70
|
+
self.misp_event.add_attribute(**attribute)
|
|
71
|
+
return {'referenced_uuid': attribute.uuid, 'relationship_type': '-'.join(result['context'].lower().split(' '))}
|
|
72
|
+
|
|
73
|
+
def _create_file_object(self, file_info):
|
|
74
|
+
file_object = MISPObject('file')
|
|
75
|
+
filename_attribute = {'type': 'filename'}
|
|
76
|
+
filename_attribute.update(self.attribute)
|
|
77
|
+
if file_info['classification'] != "UNCLASSIFIED":
|
|
78
|
+
tag = {'Tag': [{'name': file_info['classification'].lower()}]}
|
|
79
|
+
filename_attribute.update(tag)
|
|
80
|
+
for feature, attribute in self._file_mapping.items():
|
|
81
|
+
attribute.update(tag)
|
|
82
|
+
file_object.add_attribute(value=file_info[feature], **attribute)
|
|
83
|
+
return filename_attribute, file_object
|
|
84
|
+
for feature, attribute in self._file_mapping.items():
|
|
85
|
+
file_object.add_attribute(value=file_info[feature], **attribute)
|
|
86
|
+
return filename_attribute, file_object
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def _get_results(submission_results):
|
|
90
|
+
results = defaultdict(list)
|
|
91
|
+
for k, values in submission_results.items():
|
|
92
|
+
h = k.split('.')[0]
|
|
93
|
+
for t in values['result']['tags']:
|
|
94
|
+
if t['context'] is not None:
|
|
95
|
+
results[h].append(t)
|
|
96
|
+
return results
|
|
97
|
+
|
|
98
|
+
def _get_scores(self, file_tree):
|
|
99
|
+
scores = {}
|
|
100
|
+
for h, f in file_tree.items():
|
|
101
|
+
score = f['score']
|
|
102
|
+
if score > 0:
|
|
103
|
+
scores[h] = {'name': f['name'], 'score': score}
|
|
104
|
+
if f['children']:
|
|
105
|
+
scores.update(self._get_scores(f['children']))
|
|
106
|
+
return scores
|
|
107
|
+
|
|
108
|
+
def _parse_report(self, submission):
|
|
109
|
+
if submission['classification'] != 'UNCLASSIFIED':
|
|
110
|
+
self.misp_event.add_tag(submission['classification'].lower())
|
|
111
|
+
filtered_results = self._get_results(submission['results'])
|
|
112
|
+
scores = self._get_scores(submission['file_tree'])
|
|
113
|
+
for h, results in filtered_results.items():
|
|
114
|
+
if h in scores:
|
|
115
|
+
attribute, file_object = self._create_file_object(submission['file_infos'][h])
|
|
116
|
+
print(file_object)
|
|
117
|
+
for filename in scores[h]['name']:
|
|
118
|
+
file_object.add_attribute('filename', value=filename, **attribute)
|
|
119
|
+
for reference in self._parse_results(results):
|
|
120
|
+
file_object.add_reference(**reference)
|
|
121
|
+
self.misp_event.add_object(**file_object)
|
|
122
|
+
|
|
123
|
+
def _parse_results(self, results):
|
|
124
|
+
references = []
|
|
125
|
+
for result in results:
|
|
126
|
+
try:
|
|
127
|
+
attribute_type = self._results_mapping[result['type']]
|
|
128
|
+
except KeyError:
|
|
129
|
+
continue
|
|
130
|
+
references.append(self._create_attribute(result, attribute_type))
|
|
131
|
+
return references
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def parse_config(apiurl, user_id, config):
|
|
135
|
+
error = {"error": "Please provide your AssemblyLine API key or Password."}
|
|
136
|
+
if config.get('apikey'):
|
|
137
|
+
try:
|
|
138
|
+
return Client(apiurl, apikey=(user_id, config['apikey']), verify=config['verifyssl'])
|
|
139
|
+
except ClientError as e:
|
|
140
|
+
error['error'] = f'Error while initiating a connection with AssemblyLine: {e.__str__()}'
|
|
141
|
+
if config.get('password'):
|
|
142
|
+
try:
|
|
143
|
+
return Client(apiurl, auth=(user_id, config['password']))
|
|
144
|
+
except ClientError as e:
|
|
145
|
+
error['error'] = f'Error while initiating a connection with AssemblyLine: {e.__str__()}'
|
|
146
|
+
return error
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def handler(q=False):
|
|
150
|
+
if q is False:
|
|
151
|
+
return False
|
|
152
|
+
request = json.loads(q)
|
|
153
|
+
if not request.get('attribute') or not check_input_attribute(request['attribute']):
|
|
154
|
+
return {'error': f'{standard_error_message}, which should contain at least a type, a value and an uuid.'}
|
|
155
|
+
if request['attribute']['type'] not in mispattributes['input']:
|
|
156
|
+
return {'error': 'Unsupported attribute type.'}
|
|
157
|
+
if not request.get('config'):
|
|
158
|
+
return {"error": "Missing configuration."}
|
|
159
|
+
if not request['config'].get('apiurl'):
|
|
160
|
+
return {"error": "No AssemblyLine server address provided."}
|
|
161
|
+
apiurl = request['config']['apiurl']
|
|
162
|
+
if not request['config'].get('user_id'):
|
|
163
|
+
return {"error": "Please provide your AssemblyLine User ID."}
|
|
164
|
+
user_id = request['config']['user_id']
|
|
165
|
+
client = parse_config(apiurl, user_id, request['config'])
|
|
166
|
+
if isinstance(client, dict):
|
|
167
|
+
return client
|
|
168
|
+
assemblyline_parser = AssemblyLineParser()
|
|
169
|
+
assemblyline_parser.get_submission(request['attribute'], client)
|
|
170
|
+
return assemblyline_parser.finalize_results()
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def introspection():
|
|
174
|
+
return mispattributes
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def version():
|
|
178
|
+
moduleinfo['config'] = moduleconfig
|
|
179
|
+
return moduleinfo
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
from assemblyline_client import Client, ClientError
|
|
5
|
+
from urllib.parse import urljoin
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
moduleinfo = {
|
|
9
|
+
'version': 1,
|
|
10
|
+
'author': 'Christian Studer',
|
|
11
|
+
'module-type': ['expansion'],
|
|
12
|
+
'name': 'AssemblyLine Submit',
|
|
13
|
+
'description': 'A module to submit samples and URLs to AssemblyLine for advanced analysis, and return the link of the submission.',
|
|
14
|
+
'logo': 'assemblyline.png',
|
|
15
|
+
'requirements': ['assemblyline_client: Python library to query the AssemblyLine rest API.'],
|
|
16
|
+
'features': 'The module requires the address of the AssemblyLine server you want to query as well as your credentials used for this instance. Credentials include the user-ID and an API key or the password associated to the user-ID.\n\nIf the sample or url is correctly submitted, you get then the link of the submission.',
|
|
17
|
+
'references': ['https://www.cyber.gc.ca/en/assemblyline'],
|
|
18
|
+
'input': 'Sample, or url to submit to AssemblyLine.',
|
|
19
|
+
'output': 'Link of the report generated in AssemblyLine.',
|
|
20
|
+
}
|
|
21
|
+
moduleconfig = ["apiurl", "user_id", "apikey", "password", "verifyssl"]
|
|
22
|
+
mispattributes = {"input": ["attachment", "malware-sample", "url"],
|
|
23
|
+
"output": ["link"]}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def parse_config(apiurl, user_id, config):
|
|
27
|
+
error = {"error": "Please provide your AssemblyLine API key or Password."}
|
|
28
|
+
if config.get('apikey'):
|
|
29
|
+
try:
|
|
30
|
+
return Client(apiurl, apikey=(user_id, config['apikey']), verify=config['verifyssl'])
|
|
31
|
+
except ClientError as e:
|
|
32
|
+
error['error'] = f'Error while initiating a connection with AssemblyLine: {e.__str__()}'
|
|
33
|
+
if config.get('password'):
|
|
34
|
+
try:
|
|
35
|
+
return Client(apiurl, auth=(user_id, config['password']), verify=config['verifyssl'])
|
|
36
|
+
except ClientError as e:
|
|
37
|
+
error['error'] = f'Error while initiating a connection with AssemblyLine: {e.__str__()}'
|
|
38
|
+
return error
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def submit_content(client, filename, data):
|
|
42
|
+
try:
|
|
43
|
+
return client.submit(fname=filename, contents=data.encode())
|
|
44
|
+
except Exception as e:
|
|
45
|
+
return {'error': f'Error while submitting content to AssemblyLine: {e.__str__()}'}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def submit_request(client, request):
|
|
49
|
+
if 'attachment' in request:
|
|
50
|
+
return submit_content(client, request['attachment'], request['data'])
|
|
51
|
+
if 'malware-sample' in request:
|
|
52
|
+
return submit_content(client, request['malware-sample'].split('|')[0], request['data'])
|
|
53
|
+
for feature in ('url', 'domain'):
|
|
54
|
+
if feature in request:
|
|
55
|
+
return submit_url(client, request[feature])
|
|
56
|
+
return {"error": "No valid attribute type for this module has been provided."}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def submit_url(client, url):
|
|
60
|
+
try:
|
|
61
|
+
return client.submit(url=url)
|
|
62
|
+
except Exception as e:
|
|
63
|
+
return {'error': f'Error while submitting url to AssemblyLine: {e.__str__()}'}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def handler(q=False):
|
|
67
|
+
if q is False:
|
|
68
|
+
return q
|
|
69
|
+
request = json.loads(q)
|
|
70
|
+
if not request.get('config'):
|
|
71
|
+
return {"error": "Missing configuration."}
|
|
72
|
+
if not request['config'].get('apiurl'):
|
|
73
|
+
return {"error": "No AssemblyLine server address provided."}
|
|
74
|
+
apiurl = request['config']['apiurl']
|
|
75
|
+
if not request['config'].get('user_id'):
|
|
76
|
+
return {"error": "Please provide your AssemblyLine User ID."}
|
|
77
|
+
user_id = request['config']['user_id']
|
|
78
|
+
client = parse_config(apiurl, user_id, request['config'])
|
|
79
|
+
if isinstance(client, dict):
|
|
80
|
+
return client
|
|
81
|
+
submission = submit_request(client, request)
|
|
82
|
+
if 'error' in submission:
|
|
83
|
+
return submission
|
|
84
|
+
sid = submission['submission']['sid']
|
|
85
|
+
return {
|
|
86
|
+
"results": [{
|
|
87
|
+
"types": "link",
|
|
88
|
+
"categories": "External analysis",
|
|
89
|
+
"values": urljoin(apiurl, f'submission_detail.html?sid={sid}')
|
|
90
|
+
}]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def introspection():
|
|
95
|
+
return mispattributes
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def version():
|
|
99
|
+
moduleinfo["config"] = moduleconfig
|
|
100
|
+
return moduleinfo
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Backscatter.io Module."""
|
|
3
|
+
import json
|
|
4
|
+
try:
|
|
5
|
+
from backscatter import Backscatter
|
|
6
|
+
except ImportError:
|
|
7
|
+
print("Backscatter.io library not installed.")
|
|
8
|
+
|
|
9
|
+
misperrors = {'error': 'Error'}
|
|
10
|
+
mispattributes = {'input': ['ip-src', 'ip-dst'], 'output': ['freetext']}
|
|
11
|
+
moduleinfo = {
|
|
12
|
+
'version': '1',
|
|
13
|
+
'author': 'brandon@backscatter.io',
|
|
14
|
+
'description': 'Backscatter.io module to bring mass-scanning observations into MISP.',
|
|
15
|
+
'module-type': ['expansion', 'hover'],
|
|
16
|
+
'name': 'Backscatter.io',
|
|
17
|
+
'logo': 'backscatter_io.png',
|
|
18
|
+
'requirements': ['backscatter python library'],
|
|
19
|
+
'features': 'The module takes a source or destination IP address as input and displays the information known by backscatter.io.',
|
|
20
|
+
'references': ['https://pypi.org/project/backscatter/'],
|
|
21
|
+
'input': 'IP addresses.',
|
|
22
|
+
'output': 'Text containing a history of the IP addresses especially on scanning based on backscatter.io information .',
|
|
23
|
+
}
|
|
24
|
+
moduleconfig = ['api_key']
|
|
25
|
+
query_playbook = [
|
|
26
|
+
{'inputs': ['ip-src', 'ip-dst'],
|
|
27
|
+
'services': ['observations', 'enrichment'],
|
|
28
|
+
'name': 'generic'}
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def check_query(request):
|
|
33
|
+
"""Check the incoming request for a valid configuration."""
|
|
34
|
+
output = {'success': False}
|
|
35
|
+
config = request.get('config', None)
|
|
36
|
+
if not config:
|
|
37
|
+
misperrors['error'] = "Configuration is missing from the request."
|
|
38
|
+
return output
|
|
39
|
+
for item in moduleconfig:
|
|
40
|
+
if config.get(item, None):
|
|
41
|
+
continue
|
|
42
|
+
misperrors['error'] = "Backscatter.io authentication is missing."
|
|
43
|
+
return output
|
|
44
|
+
if not request.get('ip-src') and request.get('ip-dst'):
|
|
45
|
+
misperrors['error'] = "Unsupported attributes type."
|
|
46
|
+
return output
|
|
47
|
+
profile = {'success': True, 'config': config, 'playbook': 'generic'}
|
|
48
|
+
if 'ip-src' in request:
|
|
49
|
+
profile.update({'value': request.get('ip-src')})
|
|
50
|
+
else:
|
|
51
|
+
profile.update({'value': request.get('ip-dst')})
|
|
52
|
+
return profile
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def handler(q=False):
|
|
56
|
+
"""Handle gathering data."""
|
|
57
|
+
if not q:
|
|
58
|
+
return q
|
|
59
|
+
request = json.loads(q)
|
|
60
|
+
checks = check_query(request)
|
|
61
|
+
if not checks['success']:
|
|
62
|
+
return misperrors
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
bs = Backscatter(checks['config']['api_key'])
|
|
66
|
+
response = bs.get_observations(query=checks['value'], query_type='ip')
|
|
67
|
+
if not response['success']:
|
|
68
|
+
misperrors['error'] = '%s: %s' % (response['error'], response['message'])
|
|
69
|
+
return misperrors
|
|
70
|
+
output = {'results': [{'types': mispattributes['output'], 'values': [str(response)]}]}
|
|
71
|
+
except Exception as e:
|
|
72
|
+
misperrors['error'] = str(e)
|
|
73
|
+
return misperrors
|
|
74
|
+
|
|
75
|
+
return output
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def introspection():
|
|
79
|
+
return mispattributes
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def version():
|
|
83
|
+
moduleinfo['config'] = moduleconfig
|
|
84
|
+
return moduleinfo
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
from dns.resolver import Resolver, NXDOMAIN
|
|
6
|
+
from dns.name import LabelTooLong
|
|
7
|
+
resolver = Resolver()
|
|
8
|
+
resolver.timeout = 1
|
|
9
|
+
resolver.lifetime = 1
|
|
10
|
+
except ImportError:
|
|
11
|
+
sys.exit("dnspython3 in missing. use 'pip install dnspython3' to install it.")
|
|
12
|
+
|
|
13
|
+
misperrors = {'error': 'Error'}
|
|
14
|
+
mispattributes = {'input': ['btc'], 'output': ['text']}
|
|
15
|
+
moduleinfo = {
|
|
16
|
+
'version': '0.1',
|
|
17
|
+
'author': 'Christian Studer',
|
|
18
|
+
'description': 'An expansion hover module to query a special dns blacklist to check if a bitcoin address has been abused.',
|
|
19
|
+
'module-type': ['hover'],
|
|
20
|
+
'name': 'BTC Scam Check',
|
|
21
|
+
'logo': 'bitcoin.png',
|
|
22
|
+
'requirements': ['dnspython3: dns python library'],
|
|
23
|
+
'features': 'The module queries a dns blacklist directly with the bitcoin address and get a response if the address has been abused.',
|
|
24
|
+
'references': ['https://btcblack.it/'],
|
|
25
|
+
'input': 'btc address attribute.',
|
|
26
|
+
'output': 'Text to indicate if the BTC address has been abused.',
|
|
27
|
+
}
|
|
28
|
+
moduleconfig = []
|
|
29
|
+
|
|
30
|
+
url = 'bl.btcblack.it'
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def handler(q=False):
|
|
34
|
+
if q is False:
|
|
35
|
+
return False
|
|
36
|
+
request = json.loads(q)
|
|
37
|
+
btc = request['btc']
|
|
38
|
+
query = f"{btc}.{url}"
|
|
39
|
+
try:
|
|
40
|
+
result = ' - '.join([str(r) for r in resolver.resolve(query, 'TXT')])[1:-1]
|
|
41
|
+
except NXDOMAIN:
|
|
42
|
+
result = f"{btc} is not known as a scam address."
|
|
43
|
+
except LabelTooLong:
|
|
44
|
+
result = f"{btc} is probably not a valid BTC address."
|
|
45
|
+
return {'results': [{'types': mispattributes['output'], 'values': result}]}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def introspection():
|
|
49
|
+
return mispattributes
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def version():
|
|
53
|
+
moduleinfo['config'] = moduleconfig
|
|
54
|
+
return moduleinfo
|