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,140 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import requests
|
|
3
|
+
from . import check_input_attribute, standard_error_message
|
|
4
|
+
from pymisp import MISPEvent, MISPObject
|
|
5
|
+
|
|
6
|
+
misperrors = {'error': 'Error'}
|
|
7
|
+
mispattributes = {'input': ['cpe'], 'format': 'misp_standard'}
|
|
8
|
+
moduleinfo = {
|
|
9
|
+
'version': '2',
|
|
10
|
+
'author': 'Christian Studer',
|
|
11
|
+
'description': 'An expansion module to query the CVE search API with a cpe code to get its related vulnerabilities.',
|
|
12
|
+
'module-type': ['expansion', 'hover'],
|
|
13
|
+
'name': 'CPE Lookup',
|
|
14
|
+
'logo': 'cve.png',
|
|
15
|
+
'requirements': [],
|
|
16
|
+
'features': 'The module takes a cpe attribute as input and queries the CVE search API to get its related vulnerabilities. \nThe list of vulnerabilities is then parsed and returned as vulnerability objects.\n\nUsers can use their own CVE search API url by defining a value to the custom_API_URL parameter. If no custom API url is given, the default vulnerability.circl.lu api url is used.\n\nIn order to limit the amount of data returned by CVE serach, users can also the limit parameter. With the limit set, the API returns only the requested number of vulnerabilities, sorted from the highest cvss score to the lowest one.',
|
|
17
|
+
'references': ['https://vulnerability.circl.lu/api/'],
|
|
18
|
+
'input': 'CPE attribute.',
|
|
19
|
+
'output': 'The vulnerabilities related to the CPE.',
|
|
20
|
+
}
|
|
21
|
+
moduleconfig = ["custom_API_URL", "limit"]
|
|
22
|
+
cveapi_url = 'https://cvepremium.circl.lu/api/query'
|
|
23
|
+
DEFAULT_LIMIT = 10
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class VulnerabilitiesParser():
|
|
27
|
+
def __init__(self, attribute):
|
|
28
|
+
self.attribute = attribute
|
|
29
|
+
self.misp_event = MISPEvent()
|
|
30
|
+
self.misp_event.add_attribute(**attribute)
|
|
31
|
+
self.vulnerability_mapping = {
|
|
32
|
+
'id': {
|
|
33
|
+
'type': 'vulnerability',
|
|
34
|
+
'object_relation': 'id'
|
|
35
|
+
},
|
|
36
|
+
'summary': {
|
|
37
|
+
'type': 'text',
|
|
38
|
+
'object_relation': 'summary'
|
|
39
|
+
},
|
|
40
|
+
'vulnerable_configuration': {
|
|
41
|
+
'type': 'cpe',
|
|
42
|
+
'object_relation': 'vulnerable-configuration'
|
|
43
|
+
},
|
|
44
|
+
'vulnerable_configuration_cpe_2_2': {
|
|
45
|
+
'type': 'cpe',
|
|
46
|
+
'object_relation': 'vulnerable-configuration'
|
|
47
|
+
},
|
|
48
|
+
'Modified': {
|
|
49
|
+
'type': 'datetime',
|
|
50
|
+
'object_relation': 'modified'
|
|
51
|
+
},
|
|
52
|
+
'Published': {
|
|
53
|
+
'type': 'datetime',
|
|
54
|
+
'object_relation': 'published'
|
|
55
|
+
},
|
|
56
|
+
'references': {
|
|
57
|
+
'type': 'link',
|
|
58
|
+
'object_relation': 'references'
|
|
59
|
+
},
|
|
60
|
+
'cvss': {
|
|
61
|
+
'type': 'float',
|
|
62
|
+
'object_relation': 'cvss-score'
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
def parse_vulnerabilities(self, vulnerabilities):
|
|
67
|
+
for vulnerability in vulnerabilities:
|
|
68
|
+
vulnerability_object = MISPObject('vulnerability')
|
|
69
|
+
for feature in ('id', 'summary', 'Modified', 'Published', 'cvss'):
|
|
70
|
+
if vulnerability.get(feature):
|
|
71
|
+
attribute = {'value': vulnerability[feature]}
|
|
72
|
+
attribute.update(self.vulnerability_mapping[feature])
|
|
73
|
+
vulnerability_object.add_attribute(**attribute)
|
|
74
|
+
if vulnerability.get('Published'):
|
|
75
|
+
vulnerability_object.add_attribute(**{
|
|
76
|
+
'type': 'text',
|
|
77
|
+
'object_relation': 'state',
|
|
78
|
+
'value': 'Published'
|
|
79
|
+
})
|
|
80
|
+
for feature in ('references', 'vulnerable_configuration', 'vulnerable_configuration_cpe_2_2'):
|
|
81
|
+
if vulnerability.get(feature):
|
|
82
|
+
for value in vulnerability[feature]:
|
|
83
|
+
if isinstance(value, dict):
|
|
84
|
+
value = value['title']
|
|
85
|
+
attribute = {'value': value}
|
|
86
|
+
attribute.update(self.vulnerability_mapping[feature])
|
|
87
|
+
vulnerability_object.add_attribute(**attribute)
|
|
88
|
+
vulnerability_object.add_reference(self.attribute['uuid'], 'related-to')
|
|
89
|
+
self.misp_event.add_object(vulnerability_object)
|
|
90
|
+
|
|
91
|
+
def get_result(self):
|
|
92
|
+
event = json.loads(self.misp_event.to_json())
|
|
93
|
+
results = {key: event[key] for key in ('Attribute', 'Object')}
|
|
94
|
+
return {'results': results}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def check_url(url):
|
|
98
|
+
return url if url.endswith('/') else f"{url}/"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def handler(q=False):
|
|
102
|
+
if q is False:
|
|
103
|
+
return False
|
|
104
|
+
request = json.loads(q)
|
|
105
|
+
if not request.get('attribute') or not check_input_attribute(request['attribute']):
|
|
106
|
+
return {'error': f'{standard_error_message}, which should contain at least a type, a value and an uuid.'}
|
|
107
|
+
attribute = request['attribute']
|
|
108
|
+
if attribute.get('type') != 'cpe':
|
|
109
|
+
return {'error': 'Wrong input attribute type.'}
|
|
110
|
+
config = request['config']
|
|
111
|
+
url = check_url(config['custom_API_URL']) if config.get('custom_API_URL') else cveapi_url
|
|
112
|
+
limit = int(config['limit']) if config.get('limit') else DEFAULT_LIMIT
|
|
113
|
+
params = {
|
|
114
|
+
"retrieve": "cves",
|
|
115
|
+
"dict_filter": {
|
|
116
|
+
"vulnerable_configuration": attribute['value']
|
|
117
|
+
},
|
|
118
|
+
"limit": limit,
|
|
119
|
+
"sort": "cvss",
|
|
120
|
+
"sort_dir": "DESC"
|
|
121
|
+
}
|
|
122
|
+
response = requests.post(url, json=params)
|
|
123
|
+
if response.status_code == 200:
|
|
124
|
+
vulnerabilities = response.json()['data']
|
|
125
|
+
if not vulnerabilities:
|
|
126
|
+
return {'error': 'No related vulnerability for this CPE.'}
|
|
127
|
+
else:
|
|
128
|
+
return {'error': 'API not accessible.'}
|
|
129
|
+
parser = VulnerabilitiesParser(attribute)
|
|
130
|
+
parser.parse_vulnerabilities(vulnerabilities)
|
|
131
|
+
return parser.get_result()
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def introspection():
|
|
135
|
+
return mispattributes
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def version():
|
|
139
|
+
moduleinfo['config'] = moduleconfig
|
|
140
|
+
return moduleinfo
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import ipaddress
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
import pycountry
|
|
5
|
+
import requests
|
|
6
|
+
from pymisp import MISPEvent, MISPObject
|
|
7
|
+
|
|
8
|
+
from . import check_input_attribute, standard_error_message
|
|
9
|
+
|
|
10
|
+
mispattributes = {"input": ["ip-dst", "ip-src"], "format": "misp_standard"}
|
|
11
|
+
moduleinfo = {
|
|
12
|
+
"version": "2.1",
|
|
13
|
+
"author": "Shivam Sandbhor <shivam@crowdsec.net>",
|
|
14
|
+
"description": "Module to access CrowdSec CTI API.",
|
|
15
|
+
"module-type": ["hover", "expansion"],
|
|
16
|
+
"name": "CrowdSec CTI",
|
|
17
|
+
"logo": "crowdsec.png",
|
|
18
|
+
"requirements": [
|
|
19
|
+
"A CrowdSec CTI API key. Get yours by following https://docs.crowdsec.net/docs/cti_api/getting_started/#getting-an-api-key"
|
|
20
|
+
],
|
|
21
|
+
"features": "This module enables IP lookup from CrowdSec CTI API. It provides information about the IP, such as what kind of attacks it has been participant of as seen by CrowdSec's network. It also includes enrichment by CrowdSec like background noise score, aggressivity over time etc.",
|
|
22
|
+
"references": [
|
|
23
|
+
"https://www.crowdsec.net/",
|
|
24
|
+
"https://docs.crowdsec.net/docs/cti_api/getting_started",
|
|
25
|
+
"https://app.crowdsec.net/",
|
|
26
|
+
],
|
|
27
|
+
"input": "An IP address.",
|
|
28
|
+
"output": "IP Lookup information from CrowdSec CTI API",
|
|
29
|
+
}
|
|
30
|
+
moduleconfig = [
|
|
31
|
+
"api_key",
|
|
32
|
+
"add_reputation_tag",
|
|
33
|
+
"add_behavior_tag",
|
|
34
|
+
"add_classification_tag",
|
|
35
|
+
"add_mitre_technique_tag",
|
|
36
|
+
"add_cve_tag",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def handler(q=False):
|
|
41
|
+
if q is False:
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
request = json.loads(q)
|
|
45
|
+
if not request.get("config"):
|
|
46
|
+
return {"error": "Missing CrowdSec Config"}
|
|
47
|
+
|
|
48
|
+
if not request["config"].get("api_key"):
|
|
49
|
+
return {"error": "Missing CrowdSec API key"}
|
|
50
|
+
|
|
51
|
+
if not request.get("attribute") or not check_input_attribute(request["attribute"]):
|
|
52
|
+
return {
|
|
53
|
+
"error": f"{standard_error_message}, which should contain at least a type, a value and an uuid."
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if request["attribute"].get("type") not in mispattributes["input"]:
|
|
57
|
+
return {
|
|
58
|
+
"error": f"Wrong input type. Please choose on of the following: {', '.join(mispattributes['input'])}"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return _handler_v2(request)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _get_boolean_config(request_data, config: str, default_config: bool):
|
|
65
|
+
if request_data["config"].get(config) is None:
|
|
66
|
+
return default_config
|
|
67
|
+
raw_config = request_data["config"].get(config).lower()
|
|
68
|
+
# falsy values, return False
|
|
69
|
+
if raw_config in ["false", "0", "no", "off"]:
|
|
70
|
+
return False
|
|
71
|
+
# truthy values, return True
|
|
72
|
+
if raw_config in ["true", "1", "yes", "on"]:
|
|
73
|
+
return True
|
|
74
|
+
return default_config
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _handler_v2(request_data):
|
|
78
|
+
attribute = request_data["attribute"]
|
|
79
|
+
ip = attribute["value"]
|
|
80
|
+
# Validate IP
|
|
81
|
+
try:
|
|
82
|
+
ipaddress.ip_address(ip)
|
|
83
|
+
except ValueError:
|
|
84
|
+
return {
|
|
85
|
+
"error": f"IP ({ip}) is not valid for calling CrowdSec CTI. Please provide a valid IP address."
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
crowdsec_cti = requests.get(
|
|
89
|
+
f"https://cti.api.crowdsec.net/v2/smoke/{ip}",
|
|
90
|
+
headers={
|
|
91
|
+
"x-api-key": request_data["config"]["api_key"],
|
|
92
|
+
"User-Agent": "crowdsec-misp/v2.1.1",
|
|
93
|
+
},
|
|
94
|
+
)
|
|
95
|
+
crowdsec_cti.raise_for_status()
|
|
96
|
+
crowdsec_cti = crowdsec_cti.json()
|
|
97
|
+
|
|
98
|
+
add_reputation_tag = _get_boolean_config(request_data, "add_reputation_tag", True)
|
|
99
|
+
add_behavior_tag = _get_boolean_config(request_data, "add_behavior_tag", True)
|
|
100
|
+
add_classification_tag = _get_boolean_config(
|
|
101
|
+
request_data, "add_classification_tag", True
|
|
102
|
+
)
|
|
103
|
+
add_mitre_technique_tag = _get_boolean_config(
|
|
104
|
+
request_data, "add_mitre_technique_tag", True
|
|
105
|
+
)
|
|
106
|
+
add_cve_tag = _get_boolean_config(request_data, "add_cve_tag", True)
|
|
107
|
+
|
|
108
|
+
misp_event = MISPEvent()
|
|
109
|
+
misp_attribute = misp_event.add_attribute(**attribute)
|
|
110
|
+
crowdsec_context_object = MISPObject("crowdsec-ip-context")
|
|
111
|
+
crowdsec_context_object.from_dict(
|
|
112
|
+
first_seen=crowdsec_cti["history"]["first_seen"],
|
|
113
|
+
last_seen=crowdsec_cti["history"]["last_seen"],
|
|
114
|
+
)
|
|
115
|
+
ip_attribute = crowdsec_context_object.add_attribute("ip", crowdsec_cti["ip"])
|
|
116
|
+
reputation = crowdsec_cti["reputation"] or "unknown"
|
|
117
|
+
crowdsec_context_object.add_attribute("reputation", reputation)
|
|
118
|
+
if add_reputation_tag:
|
|
119
|
+
tag = f'crowdsec:reputation="{reputation}"'
|
|
120
|
+
ip_attribute.add_tag(tag)
|
|
121
|
+
crowdsec_context_object.add_attribute("ip-range", crowdsec_cti["ip_range"])
|
|
122
|
+
crowdsec_context_object.add_attribute(
|
|
123
|
+
"ip-range-score", crowdsec_cti["ip_range_score"]
|
|
124
|
+
)
|
|
125
|
+
crowdsec_context_object.add_attribute(
|
|
126
|
+
"country", get_country_name_from_alpha_2(crowdsec_cti["location"]["country"])
|
|
127
|
+
)
|
|
128
|
+
crowdsec_context_object.add_attribute(
|
|
129
|
+
"country-code", crowdsec_cti["location"]["country"]
|
|
130
|
+
)
|
|
131
|
+
if crowdsec_cti["location"].get("city"):
|
|
132
|
+
crowdsec_context_object.add_attribute("city", crowdsec_cti["location"]["city"])
|
|
133
|
+
crowdsec_context_object.add_attribute(
|
|
134
|
+
"latitude", crowdsec_cti["location"]["latitude"]
|
|
135
|
+
)
|
|
136
|
+
crowdsec_context_object.add_attribute(
|
|
137
|
+
"longitude", crowdsec_cti["location"]["longitude"]
|
|
138
|
+
)
|
|
139
|
+
crowdsec_context_object.add_attribute("as-name", crowdsec_cti["as_name"])
|
|
140
|
+
crowdsec_context_object.add_attribute("as-num", crowdsec_cti["as_num"])
|
|
141
|
+
if crowdsec_cti.get("reverse_dns") is not None:
|
|
142
|
+
crowdsec_context_object.add_attribute(
|
|
143
|
+
"reverse-dns", crowdsec_cti["reverse_dns"]
|
|
144
|
+
)
|
|
145
|
+
crowdsec_context_object.add_attribute(
|
|
146
|
+
"background-noise", crowdsec_cti["background_noise_score"]
|
|
147
|
+
)
|
|
148
|
+
for behavior in crowdsec_cti["behaviors"]:
|
|
149
|
+
crowdsec_context_object.add_attribute(
|
|
150
|
+
"behaviors", behavior["label"], comment=behavior["description"]
|
|
151
|
+
)
|
|
152
|
+
if add_behavior_tag:
|
|
153
|
+
tag = f'crowdsec:behavior="{behavior["name"]}"'
|
|
154
|
+
ip_attribute.add_tag(tag)
|
|
155
|
+
for technique in crowdsec_cti["mitre_techniques"]:
|
|
156
|
+
technique_name = technique["name"]
|
|
157
|
+
mitre_url = (
|
|
158
|
+
f"https://attack.mitre.org/tactics/{technique_name}"
|
|
159
|
+
if technique_name.startswith("TA")
|
|
160
|
+
else f"https://attack.mitre.org/techniques/{technique_name}"
|
|
161
|
+
)
|
|
162
|
+
crowdsec_context_object.add_attribute(
|
|
163
|
+
"mitre-techniques",
|
|
164
|
+
technique["label"],
|
|
165
|
+
comment=f'{technique["description"]} ({mitre_url})',
|
|
166
|
+
)
|
|
167
|
+
if add_mitre_technique_tag:
|
|
168
|
+
tag = f'crowdsec:mitre-technique="{technique_name}"'
|
|
169
|
+
ip_attribute.add_tag(tag)
|
|
170
|
+
|
|
171
|
+
for cve in crowdsec_cti["cves"]:
|
|
172
|
+
cve_url = f"https://nvd.nist.gov/vuln/detail/{cve}"
|
|
173
|
+
crowdsec_context_object.add_attribute("cves", cve, comment=cve_url)
|
|
174
|
+
if add_cve_tag:
|
|
175
|
+
tag = f'crowdsec:cve="{cve}"'
|
|
176
|
+
ip_attribute.add_tag(tag)
|
|
177
|
+
|
|
178
|
+
for feature, values in crowdsec_cti["classifications"].items():
|
|
179
|
+
field = feature[:-1]
|
|
180
|
+
for value in values:
|
|
181
|
+
crowdsec_context_object.add_attribute(
|
|
182
|
+
feature, value["label"], comment=value["description"]
|
|
183
|
+
)
|
|
184
|
+
if add_classification_tag:
|
|
185
|
+
tag = f'crowdsec:{field}="{value["name"]}"'
|
|
186
|
+
ip_attribute.add_tag(tag)
|
|
187
|
+
crowdsec_context_object.add_attribute(
|
|
188
|
+
"attack-details",
|
|
189
|
+
", ".join(
|
|
190
|
+
f"{scenario['name']} - {scenario['label']} ({scenario['description']})"
|
|
191
|
+
for scenario in crowdsec_cti["attack_details"]
|
|
192
|
+
),
|
|
193
|
+
)
|
|
194
|
+
crowdsec_context_object.add_attribute(
|
|
195
|
+
"target-countries",
|
|
196
|
+
", ".join(
|
|
197
|
+
map(get_country_name_from_alpha_2, crowdsec_cti["target_countries"].keys())
|
|
198
|
+
),
|
|
199
|
+
)
|
|
200
|
+
crowdsec_context_object.add_attribute(
|
|
201
|
+
"trust", crowdsec_cti["scores"]["overall"]["trust"]
|
|
202
|
+
)
|
|
203
|
+
scores = []
|
|
204
|
+
for time_period, indicators in crowdsec_cti["scores"].items():
|
|
205
|
+
tp = " ".join(map(str.capitalize, time_period.split("_")))
|
|
206
|
+
indicator = (
|
|
207
|
+
f"{indicator_type.capitalize()}: {indicator_value}"
|
|
208
|
+
for indicator_type, indicator_value in indicators.items()
|
|
209
|
+
)
|
|
210
|
+
scores.append(f"{tp}: {' - '.join(indicator)}")
|
|
211
|
+
crowdsec_context_object.add_attribute("scores", ", ".join(scores))
|
|
212
|
+
crowdsec_context_object.add_reference(misp_attribute.uuid, "related-to")
|
|
213
|
+
misp_event.add_object(crowdsec_context_object)
|
|
214
|
+
|
|
215
|
+
event = json.loads(misp_event.to_json())
|
|
216
|
+
results = {
|
|
217
|
+
key: event[key]
|
|
218
|
+
for key in ("Attribute", "Object")
|
|
219
|
+
if (key in event and event[key])
|
|
220
|
+
}
|
|
221
|
+
return {"results": results}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def get_country_name_from_alpha_2(alpha_2):
|
|
225
|
+
country_info = pycountry.countries.get(alpha_2=alpha_2)
|
|
226
|
+
return country_info.name if country_info else None
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def introspection():
|
|
230
|
+
return mispattributes
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def version():
|
|
234
|
+
moduleinfo["config"] = moduleconfig
|
|
235
|
+
return moduleinfo
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from . import check_input_attribute, standard_error_message
|
|
3
|
+
from falconpy import Intel
|
|
4
|
+
from pymisp import MISPAttribute, MISPEvent
|
|
5
|
+
|
|
6
|
+
moduleinfo = {
|
|
7
|
+
'version': '0.2',
|
|
8
|
+
'author': 'Christophe Vandeplas',
|
|
9
|
+
'description': 'Module to query CrowdStrike Falcon.',
|
|
10
|
+
'module-type': ['expansion', 'hover'],
|
|
11
|
+
'name': 'CrowdStrike Falcon',
|
|
12
|
+
'logo': 'crowdstrike.png',
|
|
13
|
+
'requirements': ['A CrowdStrike API access (API id & key)'],
|
|
14
|
+
'features': 'This module takes a MISP attribute as input to query a CrowdStrike Falcon API. The API returns then the result of the query with some types we map into compatible types we add as MISP attributes.\n\nPlease note that composite attributes composed by at least one of the input types mentionned below (domains, IPs, hostnames) are also supported.',
|
|
15
|
+
'references': ['https://www.crowdstrike.com/products/crowdstrike-falcon-faq/'],
|
|
16
|
+
'input': 'A MISP attribute included in the following list:\n- domain\n- email-attachment\n- email-dst\n- email-reply-to\n- email-src\n- email-subject\n- filename\n- hostname\n- ip-src\n- ip-dst\n- md5\n- mutex\n- regkey\n- sha1\n- sha256\n- uri\n- url\n- user-agent\n- whois-registrant-email\n- x509-fingerprint-md5',
|
|
17
|
+
'output': 'MISP attributes mapped after the CrowdStrike API has been queried, included in the following list:\n- hostname\n- email-src\n- email-subject\n- filename\n- md5\n- sha1\n- sha256\n- ip-dst\n- ip-dst\n- mutex\n- regkey\n- url\n- user-agent\n- x509-fingerprint-md5',
|
|
18
|
+
}
|
|
19
|
+
moduleconfig = ['api_id', 'apikey']
|
|
20
|
+
misperrors = {'error': 'Error'}
|
|
21
|
+
misp_type_in = ['domain', 'email-attachment', 'email-dst', 'email-reply-to', 'email-src', 'email-subject',
|
|
22
|
+
'filename', 'hostname', 'ip', 'ip-src', 'ip-dst', 'md5', 'mutex', 'regkey', 'sha1', 'sha256', 'uri', 'url',
|
|
23
|
+
'user-agent', 'whois-registrant-email', 'x509-fingerprint-md5']
|
|
24
|
+
mapping_out = { # mapping between the MISP attributes type and the compatible CrowdStrike indicator types.
|
|
25
|
+
'domain': {'type': 'hostname', 'to_ids': True},
|
|
26
|
+
'email_address': {'type': 'email-src', 'to_ids': True},
|
|
27
|
+
'email_subject': {'type': 'email-subject', 'to_ids': True},
|
|
28
|
+
'file_name': {'type': 'filename', 'to_ids': True},
|
|
29
|
+
'hash_md5': {'type': 'md5', 'to_ids': True},
|
|
30
|
+
'hash_sha1': {'type': 'sha1', 'to_ids': True},
|
|
31
|
+
'hash_sha256': {'type': 'sha256', 'to_ids': True},
|
|
32
|
+
'ip_address': {'type': 'ip-dst', 'to_ids': True},
|
|
33
|
+
'ip_address_block': {'type': 'ip-dst', 'to_ids': True},
|
|
34
|
+
'mutex_name': {'type': 'mutex', 'to_ids': True},
|
|
35
|
+
'registry': {'type': 'regkey', 'to_ids': True},
|
|
36
|
+
'url': {'type': 'url', 'to_ids': True},
|
|
37
|
+
'user_agent': {'type': 'user-agent', 'to_ids': True},
|
|
38
|
+
'x509_serial': {'type': 'x509-fingerprint-md5', 'to_ids': True},
|
|
39
|
+
|
|
40
|
+
'actors': {'type': 'threat-actor', 'category': 'Attribution'},
|
|
41
|
+
'malware_families': {'type': 'text', 'category': 'Attribution'}
|
|
42
|
+
}
|
|
43
|
+
misp_type_out = [item['type'] for item in mapping_out.values()]
|
|
44
|
+
mispattributes = {'input': misp_type_in, 'format': 'misp_standard'}
|
|
45
|
+
|
|
46
|
+
def handler(q=False):
|
|
47
|
+
if q is False:
|
|
48
|
+
return False
|
|
49
|
+
request = json.loads(q)
|
|
50
|
+
#validate CrowdStrike params
|
|
51
|
+
if (request.get('config')):
|
|
52
|
+
if (request['config'].get('apikey') is None):
|
|
53
|
+
misperrors['error'] = 'CrowdStrike apikey is missing'
|
|
54
|
+
return misperrors
|
|
55
|
+
if (request['config'].get('api_id') is None):
|
|
56
|
+
misperrors['error'] = 'CrowdStrike api_id is missing'
|
|
57
|
+
return misperrors
|
|
58
|
+
|
|
59
|
+
#validate attribute
|
|
60
|
+
if not request.get('attribute') or not check_input_attribute(request['attribute']):
|
|
61
|
+
return {'error': f'{standard_error_message}, which should contain at least a type, a value and an uuid.'}
|
|
62
|
+
attribute = request.get('attribute')
|
|
63
|
+
if not any(input_type == attribute.get('type') for input_type in misp_type_in):
|
|
64
|
+
return {'error': 'Unsupported attribute type.'}
|
|
65
|
+
|
|
66
|
+
client = CSIntelAPI(request['config']['api_id'], request['config']['apikey'])
|
|
67
|
+
|
|
68
|
+
attribute = MISPAttribute()
|
|
69
|
+
attribute.from_dict(**request.get('attribute') )
|
|
70
|
+
r = {"results": []}
|
|
71
|
+
valid_type = False
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
for k in misp_type_in:
|
|
75
|
+
if attribute.type == k:
|
|
76
|
+
# map the MISP type to the CrowdStrike type
|
|
77
|
+
r['results'].append(lookup_indicator(client, attribute))
|
|
78
|
+
valid_type = True
|
|
79
|
+
except Exception as e:
|
|
80
|
+
return {'error': f"{e}"}
|
|
81
|
+
|
|
82
|
+
if not valid_type:
|
|
83
|
+
misperrors['error'] = "Unsupported attributes type"
|
|
84
|
+
return misperrors
|
|
85
|
+
return {'results': r.get('results').pop()}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def lookup_indicator(client, ref_attribute):
|
|
89
|
+
result = client.search_indicator(ref_attribute.value)
|
|
90
|
+
misp_event = MISPEvent()
|
|
91
|
+
misp_event.add_attribute(**ref_attribute)
|
|
92
|
+
|
|
93
|
+
for item in result.get('resources', []):
|
|
94
|
+
for relation in item.get('relations'):
|
|
95
|
+
if mapping_out.get(relation.get('type')):
|
|
96
|
+
r = mapping_out[relation.get('type')].copy()
|
|
97
|
+
r['value'] = relation.get('indicator')
|
|
98
|
+
attribute = MISPAttribute()
|
|
99
|
+
attribute.from_dict(**r)
|
|
100
|
+
misp_event.add_attribute(**attribute)
|
|
101
|
+
for actor in item.get('actors'):
|
|
102
|
+
r = mapping_out.get('actors').copy()
|
|
103
|
+
r['value'] = actor
|
|
104
|
+
attribute = MISPAttribute()
|
|
105
|
+
attribute.from_dict(**r)
|
|
106
|
+
misp_event.add_attribute(**attribute)
|
|
107
|
+
if item.get('malware_families'):
|
|
108
|
+
r = mapping_out.get('malware_families').copy()
|
|
109
|
+
r['value'] = f"malware_families: {' | '.join(item.get('malware_families'))}"
|
|
110
|
+
attribute = MISPAttribute()
|
|
111
|
+
attribute.from_dict(**r)
|
|
112
|
+
misp_event.add_attribute(**attribute)
|
|
113
|
+
|
|
114
|
+
event = json.loads(misp_event.to_json())
|
|
115
|
+
return {'Object': event.get('Object', []), 'Attribute': event.get('Attribute', [])}
|
|
116
|
+
|
|
117
|
+
def introspection():
|
|
118
|
+
return mispattributes
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def version():
|
|
122
|
+
moduleinfo['config'] = moduleconfig
|
|
123
|
+
return moduleinfo
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class CSIntelAPI():
|
|
127
|
+
def __init__(self, custid=None, custkey=None):
|
|
128
|
+
# customer id and key should be passed when obj is created
|
|
129
|
+
self.falcon = Intel(client_id=custid, client_secret=custkey)
|
|
130
|
+
|
|
131
|
+
def search_indicator(self, query):
|
|
132
|
+
r = self.falcon.query_indicator_entities(q=query)
|
|
133
|
+
# 400 - bad request
|
|
134
|
+
if r.get('status_code') == 400:
|
|
135
|
+
raise Exception('HTTP Error 400 - Bad request.')
|
|
136
|
+
|
|
137
|
+
# 404 - oh shit
|
|
138
|
+
if r.get('status_code') == 404:
|
|
139
|
+
raise Exception('HTTP Error 404 - awww snap.')
|
|
140
|
+
|
|
141
|
+
# catch all?
|
|
142
|
+
if r.get('status_code') != 200:
|
|
143
|
+
raise Exception('HTTP Error: ' + str(r.get('status_code')))
|
|
144
|
+
|
|
145
|
+
if len(r.get('body').get('errors')):
|
|
146
|
+
raise Exception('API Error: ' + ' | '.join(r.get('body').get('errors')))
|
|
147
|
+
|
|
148
|
+
return r.get('body', {})
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import io
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import requests
|
|
6
|
+
import sys
|
|
7
|
+
import urllib.parse
|
|
8
|
+
import zipfile
|
|
9
|
+
|
|
10
|
+
from requests.exceptions import RequestException
|
|
11
|
+
|
|
12
|
+
log = logging.getLogger("cuckoo_submit")
|
|
13
|
+
log.setLevel(logging.DEBUG)
|
|
14
|
+
sh = logging.StreamHandler(sys.stdout)
|
|
15
|
+
sh.setLevel(logging.DEBUG)
|
|
16
|
+
fmt = logging.Formatter(
|
|
17
|
+
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
18
|
+
)
|
|
19
|
+
sh.setFormatter(fmt)
|
|
20
|
+
log.addHandler(sh)
|
|
21
|
+
|
|
22
|
+
moduleinfo = {
|
|
23
|
+
'version': '0.1',
|
|
24
|
+
'author': 'Evert Kors',
|
|
25
|
+
'description': 'Submit files and URLs to Cuckoo Sandbox',
|
|
26
|
+
'module-type': ['expansion', 'hover'],
|
|
27
|
+
'name': 'Cuckoo Submit',
|
|
28
|
+
'logo': 'cuckoo.png',
|
|
29
|
+
'requirements': ['Access to a Cuckoo Sandbox API and an API key if the API requires it. (api_url and api_key)'],
|
|
30
|
+
'features': 'The module takes a malware-sample, attachment, url or domain and submits it to Cuckoo Sandbox.\n The returned task id can be used to retrieve results when the analysis completed.',
|
|
31
|
+
'references': ['https://cuckoosandbox.org/', 'https://cuckoo.sh/docs/'],
|
|
32
|
+
'input': 'A malware-sample or attachment for files. A url or domain for URLs.',
|
|
33
|
+
'output': "A text field containing 'Cuckoo task id: <id>'",
|
|
34
|
+
}
|
|
35
|
+
misperrors = {"error": "Error"}
|
|
36
|
+
moduleconfig = ["api_url", "api_key"]
|
|
37
|
+
mispattributes = {
|
|
38
|
+
"input": ["attachment", "malware-sample", "url", "domain"],
|
|
39
|
+
"output": ["text"]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class APIKeyError(RequestException):
|
|
44
|
+
"""Raised if the Cuckoo API returns a 401. This means no or an invalid
|
|
45
|
+
bearer token was supplied."""
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class CuckooAPI(object):
|
|
50
|
+
|
|
51
|
+
def __init__(self, api_url, api_key=""):
|
|
52
|
+
self.api_key = api_key
|
|
53
|
+
if not api_url.startswith("http"):
|
|
54
|
+
api_url = "https://{}".format(api_url)
|
|
55
|
+
|
|
56
|
+
self.api_url = api_url
|
|
57
|
+
|
|
58
|
+
def _post_api(self, endpoint, files=None, data={}):
|
|
59
|
+
data.update({
|
|
60
|
+
"owner": "MISP"
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
response = requests.post(
|
|
65
|
+
urllib.parse.urljoin(self.api_url, endpoint),
|
|
66
|
+
files=files, data=data,
|
|
67
|
+
headers={"Authorization": "Bearer {}".format(self.api_key)}
|
|
68
|
+
)
|
|
69
|
+
except RequestException as e:
|
|
70
|
+
log.error("Failed to submit sample to Cuckoo Sandbox. %s", e)
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
if response.status_code == 401:
|
|
74
|
+
raise APIKeyError("Invalid or no Cuckoo Sandbox API key provided")
|
|
75
|
+
|
|
76
|
+
if response.status_code != 200:
|
|
77
|
+
log.error("Invalid Cuckoo API response")
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
return response.json()
|
|
81
|
+
|
|
82
|
+
def create_task(self, filename, fp):
|
|
83
|
+
response = self._post_api(
|
|
84
|
+
"/tasks/create/file", files={"file": (filename, fp)}
|
|
85
|
+
)
|
|
86
|
+
if not response:
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
return response["task_id"]
|
|
90
|
+
|
|
91
|
+
def create_url(self, url):
|
|
92
|
+
response = self._post_api(
|
|
93
|
+
"/tasks/create/url", data={"url": url}
|
|
94
|
+
)
|
|
95
|
+
if not response:
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
return response["task_id"]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def handler(q=False):
|
|
102
|
+
if q is False:
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
request = json.loads(q)
|
|
106
|
+
|
|
107
|
+
# See if the API URL was provided. The API key is optional, as it can
|
|
108
|
+
# be disabled in the Cuckoo API settings.
|
|
109
|
+
api_url = request["config"].get("api_url")
|
|
110
|
+
api_key = request["config"].get("api_key", "")
|
|
111
|
+
if not api_url:
|
|
112
|
+
misperrors["error"] = "No Cuckoo API URL provided"
|
|
113
|
+
return misperrors
|
|
114
|
+
|
|
115
|
+
url = request.get("url") or request.get("domain")
|
|
116
|
+
data = request.get("data")
|
|
117
|
+
filename = None
|
|
118
|
+
if data:
|
|
119
|
+
data = base64.b64decode(data)
|
|
120
|
+
|
|
121
|
+
if "malware-sample" in request:
|
|
122
|
+
filename = request.get("malware-sample").split("|", 1)[0]
|
|
123
|
+
with zipfile.ZipFile(io.BytesIO(data)) as zipf:
|
|
124
|
+
data = zipf.read(zipf.namelist()[0], pwd=b"infected")
|
|
125
|
+
|
|
126
|
+
elif "attachment" in request:
|
|
127
|
+
filename = request.get("attachment")
|
|
128
|
+
|
|
129
|
+
cuckoo_api = CuckooAPI(api_url=api_url, api_key=api_key)
|
|
130
|
+
task_id = None
|
|
131
|
+
try:
|
|
132
|
+
if url:
|
|
133
|
+
log.debug("Submitting URL to Cuckoo Sandbox %s", api_url)
|
|
134
|
+
task_id = cuckoo_api.create_url(url)
|
|
135
|
+
elif data and filename:
|
|
136
|
+
log.debug("Submitting file to Cuckoo Sandbox %s", api_url)
|
|
137
|
+
task_id = cuckoo_api.create_task(
|
|
138
|
+
filename=filename, fp=io.BytesIO(data)
|
|
139
|
+
)
|
|
140
|
+
except APIKeyError as e:
|
|
141
|
+
misperrors["error"] = "Failed to submit to Cuckoo: {}".format(e)
|
|
142
|
+
return misperrors
|
|
143
|
+
|
|
144
|
+
if not task_id:
|
|
145
|
+
misperrors["error"] = "File or URL submission failed"
|
|
146
|
+
return misperrors
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
"results": [
|
|
150
|
+
{"types": "text", "values": "Cuckoo task id: {}".format(task_id)}
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def introspection():
|
|
156
|
+
return mispattributes
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def version():
|
|
160
|
+
moduleinfo["config"] = moduleconfig
|
|
161
|
+
return moduleinfo
|