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,57 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import requests
|
|
3
|
+
|
|
4
|
+
misperrors = {'error': 'Error'}
|
|
5
|
+
mispattributes = {'input': ['vulnerability'], 'output': ['text']}
|
|
6
|
+
moduleinfo = {
|
|
7
|
+
'version': '0.4',
|
|
8
|
+
'author': 'Alexandre Dulaunoy',
|
|
9
|
+
'description': 'An expansion hover module to expand information about CVE id.',
|
|
10
|
+
'module-type': ['hover'],
|
|
11
|
+
'name': 'CVE Lookup',
|
|
12
|
+
'logo': 'cve.png',
|
|
13
|
+
'requirements': [],
|
|
14
|
+
'features': 'The module takes a vulnerability attribute as input and queries the CIRCL CVE search API to get information about the vulnerability as it is described in the list of CVEs.',
|
|
15
|
+
'references': ['https://vulnerability.circl.lu/', 'https://cve.mitre.org/'],
|
|
16
|
+
'input': 'Vulnerability attribute.',
|
|
17
|
+
'output': 'Text giving information about the CVE related to the Vulnerability.',
|
|
18
|
+
}
|
|
19
|
+
moduleconfig = ["custom_API"]
|
|
20
|
+
cveapi_url = 'https://vulnerability.circl.lu/api/cve/'
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def check_url(url):
|
|
24
|
+
return "{}/".format(url) if not url.endswith('/') else url
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def handler(q=False):
|
|
28
|
+
if q is False:
|
|
29
|
+
return False
|
|
30
|
+
request = json.loads(q)
|
|
31
|
+
if not request.get('vulnerability'):
|
|
32
|
+
misperrors['error'] = 'Vulnerability id missing'
|
|
33
|
+
return misperrors
|
|
34
|
+
|
|
35
|
+
api_url = check_url(request['config']['custom_API']) if request.get('config') and request['config'].get('custom_API') else cveapi_url
|
|
36
|
+
r = requests.get("{}{}".format(api_url, request.get('vulnerability')))
|
|
37
|
+
if r.status_code == 200:
|
|
38
|
+
vulnerability = json.loads(r.text)
|
|
39
|
+
try:
|
|
40
|
+
summary = vulnerability['containers']['cna']['descriptions'][0]['value']
|
|
41
|
+
except Exception:
|
|
42
|
+
summary = 'Non existing CVE'
|
|
43
|
+
else:
|
|
44
|
+
misperrors['error'] = 'API not accessible'
|
|
45
|
+
return misperrors['error']
|
|
46
|
+
|
|
47
|
+
r = {'results': [{'types': mispattributes['output'], 'values': summary}]}
|
|
48
|
+
return r
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def introspection():
|
|
52
|
+
return mispattributes
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def version():
|
|
56
|
+
moduleinfo['config'] = moduleconfig
|
|
57
|
+
return moduleinfo
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import requests
|
|
3
|
+
from . import check_input_attribute, standard_error_message
|
|
4
|
+
from collections import defaultdict
|
|
5
|
+
from pymisp import MISPAttribute, MISPEvent, MISPObject
|
|
6
|
+
|
|
7
|
+
misperrors = {'error': 'Error'}
|
|
8
|
+
mispattributes = {'input': ['vulnerability'], 'format': 'misp_standard'}
|
|
9
|
+
moduleinfo = {
|
|
10
|
+
'version': '2',
|
|
11
|
+
'author': 'Christian Studer',
|
|
12
|
+
'description': 'An expansion module to query the CIRCL CVE search API for more information about a vulnerability (CVE).',
|
|
13
|
+
'module-type': ['expansion', 'hover'],
|
|
14
|
+
'name': 'CVE Advanced Lookup',
|
|
15
|
+
'logo': 'cve.png',
|
|
16
|
+
'requirements': [],
|
|
17
|
+
'features': 'The module takes a vulnerability attribute as input and queries the CIRCL CVE search API to gather additional information.\n\nThe result of the query is then parsed to return additional information about the vulnerability, like its cvss score or some references, as well as the potential related weaknesses and attack patterns.\n\nThe vulnerability additional data is returned in a vulnerability MISP object, and the related additional information are put into weakness and attack-pattern MISP objects.',
|
|
18
|
+
'references': ['https://vulnerability.circl.lu', 'https://cve/mitre.org/'],
|
|
19
|
+
'input': 'Vulnerability attribute.',
|
|
20
|
+
'output': 'Additional information about the vulnerability, such as its cvss score, some references, or the related weaknesses and attack patterns.',
|
|
21
|
+
}
|
|
22
|
+
moduleconfig = ["custom_API"]
|
|
23
|
+
cveapi_url = 'https://cvepremium.circl.lu/api/'
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class VulnerabilityParser():
|
|
27
|
+
def __init__(self, attribute, api_url):
|
|
28
|
+
misp_attribute = MISPAttribute()
|
|
29
|
+
misp_attribute.from_dict(**attribute)
|
|
30
|
+
misp_event = MISPEvent()
|
|
31
|
+
misp_event.add_attribute(**misp_attribute)
|
|
32
|
+
self.__misp_attribute = misp_attribute
|
|
33
|
+
self.__misp_event = misp_event
|
|
34
|
+
self.__api_url = api_url
|
|
35
|
+
self.references = defaultdict(list)
|
|
36
|
+
self.__capec_features = ('id', 'name', 'summary', 'prerequisites', 'solutions')
|
|
37
|
+
self.__vulnerability_mapping = {
|
|
38
|
+
'id': 'id', 'summary': 'summary',
|
|
39
|
+
'Modified': 'modified', 'cvss3': 'cvss-score',
|
|
40
|
+
'cvss3-vector': 'cvss-string'
|
|
41
|
+
}
|
|
42
|
+
self.__vulnerability_multiple_mapping = {
|
|
43
|
+
'vulnerable_configuration': 'vulnerable-configuration',
|
|
44
|
+
'vulnerable_configuration_cpe_2_2': 'vulnerable-configuration',
|
|
45
|
+
'references': 'references'
|
|
46
|
+
}
|
|
47
|
+
self.__weakness_mapping = {
|
|
48
|
+
'name': 'name', 'description_summary': 'description',
|
|
49
|
+
'status': 'status', 'weaknessabs': 'weakness-abs'
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def api_url(self) -> str:
|
|
54
|
+
return self.__api_url
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def capec_features(self) -> tuple:
|
|
58
|
+
return self.__capec_features
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def misp_attribute(self) -> MISPAttribute:
|
|
62
|
+
return self.__misp_attribute
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def misp_event(self) -> MISPEvent:
|
|
66
|
+
return self.__misp_event
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def vulnerability_mapping(self) -> dict:
|
|
70
|
+
return self.__vulnerability_mapping
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def vulnerability_multiple_mapping(self) -> dict:
|
|
74
|
+
return self.__vulnerability_multiple_mapping
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def weakness_mapping(self) -> dict:
|
|
78
|
+
return self.__weakness_mapping
|
|
79
|
+
|
|
80
|
+
def get_result(self):
|
|
81
|
+
if self.references:
|
|
82
|
+
self.__build_references()
|
|
83
|
+
event = json.loads(self.misp_event.to_json())
|
|
84
|
+
results = {key: event[key] for key in ('Attribute', 'Object') if (key in event and event[key])}
|
|
85
|
+
return {'results': results}
|
|
86
|
+
|
|
87
|
+
def parse_vulnerability_information(self, vulnerability):
|
|
88
|
+
vulnerability_object = MISPObject('vulnerability')
|
|
89
|
+
for feature, relation in self.vulnerability_mapping.items():
|
|
90
|
+
if vulnerability.get(feature):
|
|
91
|
+
vulnerability_object.add_attribute(relation, vulnerability[feature])
|
|
92
|
+
if 'Published' in vulnerability:
|
|
93
|
+
vulnerability_object.add_attribute('published', vulnerability['Published'])
|
|
94
|
+
vulnerability_object.add_attribute('state', 'Published')
|
|
95
|
+
for feature, relation in self.vulnerability_multiple_mapping.items():
|
|
96
|
+
if feature in vulnerability:
|
|
97
|
+
for value in vulnerability[feature]:
|
|
98
|
+
if isinstance(value, dict):
|
|
99
|
+
value = value['title']
|
|
100
|
+
vulnerability_object.add_attribute(relation, value)
|
|
101
|
+
vulnerability_object.add_reference(self.misp_attribute.uuid, 'related-to')
|
|
102
|
+
self.misp_event.add_object(vulnerability_object)
|
|
103
|
+
if 'cwe' in vulnerability and vulnerability['cwe'] not in ('Unknown', 'NVD-CWE-noinfo'):
|
|
104
|
+
self.__parse_weakness(vulnerability['cwe'], vulnerability_object.uuid)
|
|
105
|
+
if 'capec' in vulnerability:
|
|
106
|
+
self.__parse_capec(vulnerability['capec'], vulnerability_object.uuid)
|
|
107
|
+
|
|
108
|
+
def __build_references(self):
|
|
109
|
+
for object_uuid, references in self.references.items():
|
|
110
|
+
for misp_object in self.misp_event.objects:
|
|
111
|
+
if misp_object.uuid == object_uuid:
|
|
112
|
+
for reference in references:
|
|
113
|
+
misp_object.add_reference(**reference)
|
|
114
|
+
break
|
|
115
|
+
|
|
116
|
+
def __parse_capec(self, capec_values, vulnerability_uuid):
|
|
117
|
+
for capec in capec_values:
|
|
118
|
+
capec_object = MISPObject('attack-pattern')
|
|
119
|
+
for feature in self.capec_features:
|
|
120
|
+
capec_object.add_attribute(feature, capec[feature])
|
|
121
|
+
for related_weakness in capec['related_weakness']:
|
|
122
|
+
capec_object.add_attribute('related-weakness', f"CWE-{related_weakness}")
|
|
123
|
+
self.misp_event.add_object(capec_object)
|
|
124
|
+
self.references[vulnerability_uuid].append(
|
|
125
|
+
{
|
|
126
|
+
'referenced_uuid': capec_object.uuid,
|
|
127
|
+
'relationship_type': 'targeted-by'
|
|
128
|
+
}
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
def __parse_weakness(self, cwe_value, vulnerability_uuid):
|
|
132
|
+
cwe_string, cwe_id = cwe_value.split('-')[:2]
|
|
133
|
+
cwe = requests.get(f'{self.api_url}cwe/{cwe_id}')
|
|
134
|
+
if cwe.status_code == 200:
|
|
135
|
+
cwe = cwe.json()
|
|
136
|
+
weakness_object = MISPObject('weakness')
|
|
137
|
+
weakness_object.add_attribute('id', f'{cwe_string}-{cwe_id}')
|
|
138
|
+
for feature, relation in self.weakness_mapping.items():
|
|
139
|
+
if cwe.get(feature):
|
|
140
|
+
weakness_object.add_attribute(relation, cwe[feature])
|
|
141
|
+
self.misp_event.add_object(weakness_object)
|
|
142
|
+
self.references[vulnerability_uuid].append(
|
|
143
|
+
{
|
|
144
|
+
'referenced_uuid': weakness_object.uuid,
|
|
145
|
+
'relationship_type': 'weakened-by'
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def check_url(url):
|
|
151
|
+
return f"{url}/" if not url.endswith('/') else url
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def handler(q=False):
|
|
155
|
+
if q is False:
|
|
156
|
+
return False
|
|
157
|
+
request = json.loads(q)
|
|
158
|
+
if not request.get('attribute') or not check_input_attribute(request['attribute']):
|
|
159
|
+
return {'error': f'{standard_error_message}, which should contain at least a type, a value and an uuid.'}
|
|
160
|
+
attribute = request['attribute']
|
|
161
|
+
if attribute.get('type') != 'vulnerability':
|
|
162
|
+
misperrors['error'] = 'Vulnerability id missing.'
|
|
163
|
+
return misperrors
|
|
164
|
+
api_url = check_url(request['config']['custom_API']) if request.get('config', {}).get('custom_API') else cveapi_url
|
|
165
|
+
r = requests.get(f"{api_url}cve/{attribute['value']}")
|
|
166
|
+
if r.status_code == 200:
|
|
167
|
+
vulnerability = r.json()
|
|
168
|
+
if not vulnerability:
|
|
169
|
+
misperrors['error'] = 'Non existing CVE'
|
|
170
|
+
return misperrors['error']
|
|
171
|
+
else:
|
|
172
|
+
misperrors['error'] = 'API not accessible'
|
|
173
|
+
return misperrors['error']
|
|
174
|
+
parser = VulnerabilityParser(attribute, api_url)
|
|
175
|
+
parser.parse_vulnerability_information(vulnerability)
|
|
176
|
+
return parser.get_result()
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def introspection():
|
|
180
|
+
return mispattributes
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def version():
|
|
184
|
+
moduleinfo['config'] = moduleconfig
|
|
185
|
+
return moduleinfo
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
'''
|
|
4
|
+
Cytomic Orion MISP Module
|
|
5
|
+
An expansion module to enrich attributes in MISP and share indicators of compromise with Cytomic Orion
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
'''
|
|
9
|
+
|
|
10
|
+
from . import check_input_attribute, standard_error_message
|
|
11
|
+
from pymisp import MISPAttribute, MISPEvent, MISPObject
|
|
12
|
+
import json
|
|
13
|
+
import requests
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
misperrors = {'error': 'Error'}
|
|
17
|
+
mispattributes = {'input': ['md5'], 'format': 'misp_standard'}
|
|
18
|
+
moduleinfo = {
|
|
19
|
+
'version': '0.3',
|
|
20
|
+
'author': 'Koen Van Impe',
|
|
21
|
+
'description': 'An expansion module to enrich attributes in MISP by quering the Cytomic Orion API',
|
|
22
|
+
'module-type': ['expansion'],
|
|
23
|
+
'name': 'Cytomic Orion Lookup',
|
|
24
|
+
'logo': 'cytomic_orion.png',
|
|
25
|
+
'requirements': ['Access (license) to Cytomic Orion'],
|
|
26
|
+
'features': 'This module takes an MD5 hash and searches for occurrences of this hash in the Cytomic Orion database. Returns observed files and machines.',
|
|
27
|
+
'references': ['https://www.vanimpe.eu/2020/03/10/integrating-misp-and-cytomic-orion/', 'https://www.cytomicmodel.com/solutions/'],
|
|
28
|
+
'input': 'MD5, hash of the sample / malware to search for.',
|
|
29
|
+
'output': 'MISP objects with sightings of the hash in Cytomic Orion. Includes files and machines.',
|
|
30
|
+
}
|
|
31
|
+
moduleconfig = ['api_url', 'token_url', 'clientid', 'clientsecret', 'clientsecret', 'username', 'password', 'upload_timeframe', 'upload_tag', 'delete_tag', 'upload_ttlDays', 'upload_threat_level_id', 'limit_upload_events', 'limit_upload_attributes']
|
|
32
|
+
# There are more config settings in this module than used by the enrichment
|
|
33
|
+
# There is also a PyMISP module which reuses the module config, and requires additional configuration, for example used for pushing indicators to the API
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class CytomicParser():
|
|
37
|
+
def __init__(self, attribute, config_object):
|
|
38
|
+
self.misp_event = MISPEvent()
|
|
39
|
+
self.attribute = MISPAttribute()
|
|
40
|
+
self.attribute.from_dict(**attribute)
|
|
41
|
+
self.misp_event.add_attribute(**self.attribute)
|
|
42
|
+
|
|
43
|
+
self.config_object = config_object
|
|
44
|
+
|
|
45
|
+
if self.config_object:
|
|
46
|
+
self.token = self.get_token()
|
|
47
|
+
else:
|
|
48
|
+
sys.exit('Missing configuration')
|
|
49
|
+
|
|
50
|
+
def get_token(self):
|
|
51
|
+
try:
|
|
52
|
+
scope = self.config_object['scope']
|
|
53
|
+
grant_type = self.config_object['grant_type']
|
|
54
|
+
username = self.config_object['username']
|
|
55
|
+
password = self.config_object['password']
|
|
56
|
+
token_url = self.config_object['token_url']
|
|
57
|
+
clientid = self.config_object['clientid']
|
|
58
|
+
clientsecret = self.config_object['clientsecret']
|
|
59
|
+
|
|
60
|
+
if scope and grant_type and username and password:
|
|
61
|
+
data = {'scope': scope, 'grant_type': grant_type, 'username': username, 'password': password}
|
|
62
|
+
|
|
63
|
+
if token_url and clientid and clientsecret:
|
|
64
|
+
access_token_response = requests.post(token_url, data=data, verify=False, allow_redirects=False, auth=(clientid, clientsecret))
|
|
65
|
+
tokens = json.loads(access_token_response.text)
|
|
66
|
+
if 'access_token' in tokens:
|
|
67
|
+
return tokens['access_token']
|
|
68
|
+
else:
|
|
69
|
+
self.result = {'error': 'No token received.'}
|
|
70
|
+
return
|
|
71
|
+
else:
|
|
72
|
+
self.result = {'error': 'No token_url, clientid or clientsecret supplied.'}
|
|
73
|
+
return
|
|
74
|
+
else:
|
|
75
|
+
self.result = {'error': 'No scope, grant_type, username or password supplied.'}
|
|
76
|
+
return
|
|
77
|
+
except Exception:
|
|
78
|
+
self.result = {'error': 'Unable to connect to token_url.'}
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
def get_results(self):
|
|
82
|
+
if hasattr(self, 'result'):
|
|
83
|
+
return self.result
|
|
84
|
+
event = json.loads(self.misp_event.to_json())
|
|
85
|
+
results = {key: event[key] for key in ('Attribute', 'Object')}
|
|
86
|
+
return {'results': results}
|
|
87
|
+
|
|
88
|
+
def parse(self, searchkey):
|
|
89
|
+
|
|
90
|
+
if self.token:
|
|
91
|
+
|
|
92
|
+
endpoint_fileinformation = self.config_object['endpoint_fileinformation']
|
|
93
|
+
endpoint_machines = self.config_object['endpoint_machines']
|
|
94
|
+
endpoint_machines_client = self.config_object['endpoint_machines_client']
|
|
95
|
+
query_machines = self.config_object['query_machines']
|
|
96
|
+
query_machine_info = self.config_object['query_machine_info']
|
|
97
|
+
|
|
98
|
+
# Update endpoint URLs
|
|
99
|
+
query_endpoint_fileinformation = endpoint_fileinformation.format(md5=searchkey)
|
|
100
|
+
query_endpoint_machines = endpoint_machines.format(md5=searchkey)
|
|
101
|
+
|
|
102
|
+
# API calls
|
|
103
|
+
api_call_headers = {'Authorization': 'Bearer ' + self.token}
|
|
104
|
+
result_query_endpoint_fileinformation = requests.get(query_endpoint_fileinformation, headers=api_call_headers, verify=False)
|
|
105
|
+
json_result_query_endpoint_fileinformation = json.loads(result_query_endpoint_fileinformation.text)
|
|
106
|
+
|
|
107
|
+
if json_result_query_endpoint_fileinformation:
|
|
108
|
+
|
|
109
|
+
cytomic_object = MISPObject('cytomic-orion-file')
|
|
110
|
+
|
|
111
|
+
cytomic_object.add_attribute('fileName', type='text', value=json_result_query_endpoint_fileinformation['fileName'])
|
|
112
|
+
cytomic_object.add_attribute('fileSize', type='text', value=json_result_query_endpoint_fileinformation['fileSize'])
|
|
113
|
+
cytomic_object.add_attribute('last-seen', type='datetime', value=json_result_query_endpoint_fileinformation['lastSeen'])
|
|
114
|
+
cytomic_object.add_attribute('first-seen', type='datetime', value=json_result_query_endpoint_fileinformation['firstSeen'])
|
|
115
|
+
cytomic_object.add_attribute('classification', type='text', value=json_result_query_endpoint_fileinformation['classification'])
|
|
116
|
+
cytomic_object.add_attribute('classificationName', type='text', value=json_result_query_endpoint_fileinformation['classificationName'])
|
|
117
|
+
self.misp_event.add_object(**cytomic_object)
|
|
118
|
+
|
|
119
|
+
result_query_endpoint_machines = requests.get(query_endpoint_machines, headers=api_call_headers, verify=False)
|
|
120
|
+
json_result_query_endpoint_machines = json.loads(result_query_endpoint_machines.text)
|
|
121
|
+
|
|
122
|
+
if query_machines and json_result_query_endpoint_machines and len(json_result_query_endpoint_machines) > 0:
|
|
123
|
+
for machine in json_result_query_endpoint_machines:
|
|
124
|
+
|
|
125
|
+
if query_machine_info and machine['muid']:
|
|
126
|
+
query_endpoint_machines_client = endpoint_machines_client.format(muid=machine['muid'])
|
|
127
|
+
result_endpoint_machines_client = requests.get(query_endpoint_machines_client, headers=api_call_headers, verify=False)
|
|
128
|
+
json_result_endpoint_machines_client = json.loads(result_endpoint_machines_client.text)
|
|
129
|
+
|
|
130
|
+
if json_result_endpoint_machines_client:
|
|
131
|
+
|
|
132
|
+
cytomic_machine_object = MISPObject('cytomic-orion-machine')
|
|
133
|
+
|
|
134
|
+
clienttag = [{'name': json_result_endpoint_machines_client['clientName']}]
|
|
135
|
+
|
|
136
|
+
cytomic_machine_object.add_attribute('machineName', type='target-machine', value=json_result_endpoint_machines_client['machineName'], Tag=clienttag)
|
|
137
|
+
cytomic_machine_object.add_attribute('machineMuid', type='text', value=machine['muid'])
|
|
138
|
+
cytomic_machine_object.add_attribute('clientName', type='target-org', value=json_result_endpoint_machines_client['clientName'], Tag=clienttag)
|
|
139
|
+
cytomic_machine_object.add_attribute('clientId', type='text', value=machine['clientId'])
|
|
140
|
+
cytomic_machine_object.add_attribute('machinePath', type='text', value=machine['lastPath'])
|
|
141
|
+
cytomic_machine_object.add_attribute('first-seen', type='datetime', value=machine['firstSeen'])
|
|
142
|
+
cytomic_machine_object.add_attribute('last-seen', type='datetime', value=machine['lastSeen'])
|
|
143
|
+
cytomic_machine_object.add_attribute('creationDate', type='datetime', value=json_result_endpoint_machines_client['creationDate'])
|
|
144
|
+
cytomic_machine_object.add_attribute('clientCreationDateUTC', type='datetime', value=json_result_endpoint_machines_client['clientCreationDateUTC'])
|
|
145
|
+
cytomic_machine_object.add_attribute('lastSeenUtc', type='datetime', value=json_result_endpoint_machines_client['lastSeenUtc'])
|
|
146
|
+
self.misp_event.add_object(**cytomic_machine_object)
|
|
147
|
+
else:
|
|
148
|
+
self.result = {'error': 'No (valid) token.'}
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def handler(q=False):
|
|
153
|
+
if q is False:
|
|
154
|
+
return False
|
|
155
|
+
request = json.loads(q)
|
|
156
|
+
|
|
157
|
+
if not request.get('attribute'):
|
|
158
|
+
return {'error': 'Unsupported input.'}
|
|
159
|
+
|
|
160
|
+
if not request.get('attribute') or not check_input_attribute(request['attribute']):
|
|
161
|
+
return {'error': f'{standard_error_message}, which should contain at least a type, a value and an uuid.'}
|
|
162
|
+
attribute = request['attribute']
|
|
163
|
+
if not any(input_type == attribute['type'] for input_type in mispattributes['input']):
|
|
164
|
+
return {'error': 'Unsupported attribute type.'}
|
|
165
|
+
|
|
166
|
+
if not request.get('config'):
|
|
167
|
+
return {'error': 'Missing configuration'}
|
|
168
|
+
|
|
169
|
+
config_object = {
|
|
170
|
+
'clientid': request["config"].get("clientid"),
|
|
171
|
+
'clientsecret': request["config"].get("clientsecret"),
|
|
172
|
+
'scope': 'orion.api',
|
|
173
|
+
'password': request["config"].get("password"),
|
|
174
|
+
'username': request["config"].get("username"),
|
|
175
|
+
'grant_type': 'password',
|
|
176
|
+
'token_url': request["config"].get("token_url"),
|
|
177
|
+
'endpoint_fileinformation': '{api_url}{endpoint}'.format(api_url=request["config"].get("api_url"), endpoint='/forensics/md5/{md5}/info'),
|
|
178
|
+
'endpoint_machines': '{api_url}{endpoint}'.format(api_url=request["config"].get("api_url"), endpoint='/forensics/md5/{md5}/muids'),
|
|
179
|
+
'endpoint_machines_client': '{api_url}{endpoint}'.format(api_url=request["config"].get("api_url"), endpoint='/forensics/muid/{muid}/info'),
|
|
180
|
+
'query_machines': True,
|
|
181
|
+
'query_machine_info': True
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
cytomic_parser = CytomicParser(attribute, config_object)
|
|
185
|
+
cytomic_parser.parse(attribute['value'])
|
|
186
|
+
|
|
187
|
+
return cytomic_parser.get_results()
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def introspection():
|
|
191
|
+
return mispattributes
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def version():
|
|
195
|
+
moduleinfo['config'] = moduleconfig
|
|
196
|
+
return moduleinfo
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
original_path = sys.path
|
|
6
|
+
sys.path = original_path[1:]
|
|
7
|
+
import dns.resolver
|
|
8
|
+
sys.path = original_path
|
|
9
|
+
resolver = dns.resolver.Resolver()
|
|
10
|
+
resolver.timeout = 0.2
|
|
11
|
+
resolver.lifetime = 0.2
|
|
12
|
+
except ImportError:
|
|
13
|
+
print("dnspython3 is missing, use 'pip install dnspython3' to install it.")
|
|
14
|
+
sys.exit(0)
|
|
15
|
+
|
|
16
|
+
misperrors = {'error': 'Error'}
|
|
17
|
+
mispattributes = {'input': ['domain', 'domain|ip', 'hostname', 'hostname|port'], 'output': ['text']}
|
|
18
|
+
moduleinfo = {
|
|
19
|
+
'version': '0.1',
|
|
20
|
+
'author': 'Christian Studer',
|
|
21
|
+
'description': 'Checks Spamhaus DBL for a domain name.',
|
|
22
|
+
'module-type': ['expansion', 'hover'],
|
|
23
|
+
'name': 'DBL Spamhaus Lookup',
|
|
24
|
+
'logo': 'spamhaus.jpg',
|
|
25
|
+
'requirements': ['dnspython3: DNS python3 library'],
|
|
26
|
+
'features': 'This modules takes a domain or a hostname in input and queries the Domain Block List provided by Spamhaus to determine what kind of domain it is.\n\nDBL then returns a response code corresponding to a certain classification of the domain we display. If the queried domain is not in the list, it is also mentionned.\n\nPlease note that composite MISP attributes containing domain or hostname are supported as well.',
|
|
27
|
+
'references': ['https://www.spamhaus.org/faq/section/Spamhaus%20DBL'],
|
|
28
|
+
'input': 'Domain or hostname attribute.',
|
|
29
|
+
'output': 'Information about the nature of the input.',
|
|
30
|
+
}
|
|
31
|
+
moduleconfig = []
|
|
32
|
+
|
|
33
|
+
dbl = 'dbl.spamhaus.org'
|
|
34
|
+
dbl_mapping = {'127.0.1.2': 'spam domain',
|
|
35
|
+
'127.0.1.4': 'phish domain',
|
|
36
|
+
'127.0.1.5': 'malware domain',
|
|
37
|
+
'127.0.1.6': 'botnet C&C domain',
|
|
38
|
+
'127.0.1.102': 'abused legit spam',
|
|
39
|
+
'127.0.1.103': 'abused spammed redirector domain',
|
|
40
|
+
'127.0.1.104': 'abused legit phish',
|
|
41
|
+
'127.0.1.105': 'abused legit malware',
|
|
42
|
+
'127.0.1.106': 'abused legit botnet C&C',
|
|
43
|
+
'127.0.1.255': 'IP queries prohibited!'}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def fetch_requested_value(request):
|
|
47
|
+
for attribute_type in mispattributes['input']:
|
|
48
|
+
if request.get(attribute_type):
|
|
49
|
+
return request[attribute_type].split('|')[0]
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def handler(q=False):
|
|
54
|
+
if q is False:
|
|
55
|
+
return False
|
|
56
|
+
request = json.loads(q)
|
|
57
|
+
requested_value = fetch_requested_value(request)
|
|
58
|
+
if requested_value is None:
|
|
59
|
+
misperrors['error'] = "Unsupported attributes type"
|
|
60
|
+
return misperrors
|
|
61
|
+
query = "{}.{}".format(requested_value, dbl)
|
|
62
|
+
try:
|
|
63
|
+
query_result = resolver.resolve(query, 'A')[0]
|
|
64
|
+
result = "{} - {}".format(requested_value, dbl_mapping[str(query_result)])
|
|
65
|
+
except dns.resolver.NXDOMAIN as e:
|
|
66
|
+
result = e.msg
|
|
67
|
+
except Exception:
|
|
68
|
+
return {'error': 'Not able to reach dbl.spamhaus.org or something went wrong'}
|
|
69
|
+
return {'results': [{'types': mispattributes.get('output'), 'values': result}]}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def introspection():
|
|
73
|
+
return mispattributes
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def version():
|
|
77
|
+
moduleinfo['config'] = moduleconfig
|
|
78
|
+
return moduleinfo
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import dns.resolver
|
|
3
|
+
|
|
4
|
+
misperrors = {'error': 'Error'}
|
|
5
|
+
mispattributes = {'input': ['hostname', 'domain', 'domain|ip'], 'output': ['ip-src',
|
|
6
|
+
'ip-dst']}
|
|
7
|
+
moduleinfo = {
|
|
8
|
+
'version': '0.3',
|
|
9
|
+
'author': 'Alexandre Dulaunoy',
|
|
10
|
+
'description': 'jj',
|
|
11
|
+
'module-type': ['expansion', 'hover'],
|
|
12
|
+
'name': 'DNS Resolver',
|
|
13
|
+
'logo': '',
|
|
14
|
+
'requirements': ['dnspython3: DNS python3 library'],
|
|
15
|
+
'features': 'The module takes a domain of hostname attribute as input, and tries to resolve it. If no error is encountered, the IP address that resolves the domain is returned, otherwise the origin of the error is displayed.\n\nThe address of the DNS resolver to use is also configurable, but if no configuration is set, we use the Google public DNS address (8.8.8.8).\n\nPlease note that composite MISP attributes containing domain or hostname are supported as well.',
|
|
16
|
+
'references': [],
|
|
17
|
+
'input': 'Domain or hostname attribute.',
|
|
18
|
+
'output': 'IP address resolving the input.',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
moduleconfig = ['nameserver']
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def handler(q=False):
|
|
25
|
+
if q is False:
|
|
26
|
+
return False
|
|
27
|
+
request = json.loads(q)
|
|
28
|
+
if request.get('hostname'):
|
|
29
|
+
toquery = request['hostname']
|
|
30
|
+
elif request.get('domain'):
|
|
31
|
+
toquery = request['domain']
|
|
32
|
+
elif request.get('domain|ip'):
|
|
33
|
+
toquery = request['domain|ip'].split('|')[0]
|
|
34
|
+
else:
|
|
35
|
+
return False
|
|
36
|
+
r = dns.resolver.Resolver()
|
|
37
|
+
r.timeout = 2
|
|
38
|
+
r.lifetime = 2
|
|
39
|
+
|
|
40
|
+
if request.get('config'):
|
|
41
|
+
if request['config'].get('nameserver'):
|
|
42
|
+
nameservers = []
|
|
43
|
+
nameservers.append(request['config'].get('nameserver'))
|
|
44
|
+
r.nameservers = nameservers
|
|
45
|
+
else:
|
|
46
|
+
r.nameservers = ['8.8.8.8']
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
answer = r.resolve(toquery, 'A')
|
|
50
|
+
except dns.resolver.NXDOMAIN:
|
|
51
|
+
misperrors['error'] = "NXDOMAIN"
|
|
52
|
+
return misperrors
|
|
53
|
+
except dns.exception.Timeout:
|
|
54
|
+
misperrors['error'] = "Timeout"
|
|
55
|
+
return misperrors
|
|
56
|
+
except Exception as e:
|
|
57
|
+
misperrors['error'] = f'DNS resolving error {e}'
|
|
58
|
+
return misperrors
|
|
59
|
+
|
|
60
|
+
r = {'results': [{'types': mispattributes['output'],
|
|
61
|
+
'values':[str(answer[0])]}]}
|
|
62
|
+
return r
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def introspection():
|
|
66
|
+
return mispattributes
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def version():
|
|
70
|
+
moduleinfo['config'] = moduleconfig
|
|
71
|
+
return moduleinfo
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import binascii
|
|
3
|
+
import np
|
|
4
|
+
import docx
|
|
5
|
+
import io
|
|
6
|
+
|
|
7
|
+
misperrors = {'error': 'Error'}
|
|
8
|
+
mispattributes = {'input': ['attachment'],
|
|
9
|
+
'output': ['freetext', 'text']}
|
|
10
|
+
moduleinfo = {
|
|
11
|
+
'version': '0.1',
|
|
12
|
+
'author': 'Sascha Rommelfangen',
|
|
13
|
+
'description': 'Module to extract freetext from a .docx document.',
|
|
14
|
+
'module-type': ['expansion'],
|
|
15
|
+
'name': 'DOCX Enrich',
|
|
16
|
+
'logo': 'docx.png',
|
|
17
|
+
'requirements': ['docx python library'],
|
|
18
|
+
'features': 'The module reads the text contained in a .docx document. The result is passed to the freetext import parser so IoCs can be extracted out of it.',
|
|
19
|
+
'references': [],
|
|
20
|
+
'input': 'Attachment attribute containing a .docx document.',
|
|
21
|
+
'output': 'Text and freetext parsed from the document.',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
moduleconfig = []
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def handler(q=False):
|
|
28
|
+
if q is False:
|
|
29
|
+
return False
|
|
30
|
+
q = json.loads(q)
|
|
31
|
+
filename = q['attachment']
|
|
32
|
+
try:
|
|
33
|
+
docx_array = np.frombuffer(binascii.a2b_base64(q['data']), np.uint8)
|
|
34
|
+
except Exception as e:
|
|
35
|
+
print(e)
|
|
36
|
+
err = "Couldn't fetch attachment (JSON 'data' is empty). Are you using the 'Query enrichment' action?"
|
|
37
|
+
misperrors['error'] = err
|
|
38
|
+
print(err)
|
|
39
|
+
return misperrors
|
|
40
|
+
|
|
41
|
+
doc_content = ""
|
|
42
|
+
doc_file = io.BytesIO(docx_array)
|
|
43
|
+
try:
|
|
44
|
+
doc = docx.Document(doc_file)
|
|
45
|
+
for para in doc.paragraphs:
|
|
46
|
+
print(para.text)
|
|
47
|
+
doc_content = doc_content + "\n" + para.text
|
|
48
|
+
tables = doc.tables
|
|
49
|
+
for table in tables:
|
|
50
|
+
for row in table.rows:
|
|
51
|
+
for cell in row.cells:
|
|
52
|
+
for para in cell.paragraphs:
|
|
53
|
+
print(para.text)
|
|
54
|
+
doc_content = doc_content + "\n" + para.text
|
|
55
|
+
print(doc_content)
|
|
56
|
+
return {'results': [{'types': ['freetext'], 'values': doc_content, 'comment': ".docx-to-text from file " + filename},
|
|
57
|
+
{'types': ['text'], 'values': doc_content, 'comment': ".docx-to-text from file " + filename}]}
|
|
58
|
+
except Exception as e:
|
|
59
|
+
print(e)
|
|
60
|
+
err = "Couldn't analyze file as .docx. Error was: " + str(e)
|
|
61
|
+
misperrors['error'] = err
|
|
62
|
+
return misperrors
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def introspection():
|
|
66
|
+
return mispattributes
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def version():
|
|
70
|
+
moduleinfo['config'] = moduleconfig
|
|
71
|
+
return moduleinfo
|