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,243 @@
|
|
|
1
|
+
import dnsdb2
|
|
2
|
+
import json
|
|
3
|
+
from . import check_input_attribute, standard_error_message
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from pymisp import MISPEvent, MISPObject, Distribution
|
|
6
|
+
|
|
7
|
+
misperrors = {'error': 'Error'}
|
|
8
|
+
standard_query_input = [
|
|
9
|
+
'hostname',
|
|
10
|
+
'domain',
|
|
11
|
+
'ip-src',
|
|
12
|
+
'ip-dst'
|
|
13
|
+
]
|
|
14
|
+
flex_query_input = [
|
|
15
|
+
'btc',
|
|
16
|
+
'dkim',
|
|
17
|
+
'email',
|
|
18
|
+
'email-src',
|
|
19
|
+
'email-dst',
|
|
20
|
+
'domain|ip',
|
|
21
|
+
'hex',
|
|
22
|
+
'mac-address',
|
|
23
|
+
'mac-eui-64',
|
|
24
|
+
'other',
|
|
25
|
+
'pattern-filename',
|
|
26
|
+
'target-email',
|
|
27
|
+
'text',
|
|
28
|
+
'uri',
|
|
29
|
+
'url',
|
|
30
|
+
'whois-registrant-email',
|
|
31
|
+
]
|
|
32
|
+
mispattributes = {
|
|
33
|
+
'input': standard_query_input + flex_query_input,
|
|
34
|
+
'format': 'misp_standard'
|
|
35
|
+
}
|
|
36
|
+
moduleinfo = {
|
|
37
|
+
'version': '0.5',
|
|
38
|
+
'author': 'Christophe Vandeplas',
|
|
39
|
+
'description': 'Module to access Farsight DNSDB Passive DNS.',
|
|
40
|
+
'module-type': ['expansion', 'hover'],
|
|
41
|
+
'name': 'Farsight DNSDB Lookup',
|
|
42
|
+
'logo': 'farsight.png',
|
|
43
|
+
'requirements': ['An access to the Farsight Passive DNS API (apikey)'],
|
|
44
|
+
'features': 'This module takes a domain, hostname or IP address MISP attribute as input to query the Farsight Passive DNS API.\n The results of rdata and rrset lookups are then returned and parsed into passive-dns objects.\n\nAn API key is required to submit queries to the API.\n It is also possible to define a custom server URL, and to set a limit of results to get.\n This limit is set for each lookup, which means we can have an up to the limit number of passive-dns objects resulting from an rdata query about an IP address, but an up to the limit number of passive-dns objects for each lookup queries about a domain or a hostname (== twice the limit).',
|
|
45
|
+
'references': ['https://www.farsightsecurity.com/', 'https://docs.dnsdb.info/dnsdb-api/'],
|
|
46
|
+
'input': 'A domain, hostname or IP address MISP attribute.',
|
|
47
|
+
'output': 'Passive-dns objects, resulting from the query on the Farsight Passive DNS API.',
|
|
48
|
+
}
|
|
49
|
+
moduleconfig = ['apikey', 'server', 'limit', 'flex_queries']
|
|
50
|
+
|
|
51
|
+
DEFAULT_DNSDB_SERVER = 'https://api.dnsdb.info'
|
|
52
|
+
DEFAULT_LIMIT = 10
|
|
53
|
+
DEFAULT_DISTRIBUTION_SETTING = Distribution.your_organisation_only.value
|
|
54
|
+
TYPE_TO_FEATURE = {
|
|
55
|
+
"btc": "Bitcoin address",
|
|
56
|
+
"dkim": "domainkeys identified mail",
|
|
57
|
+
"domain": "domain name",
|
|
58
|
+
"domain|ip": "domain name / IP address",
|
|
59
|
+
"hex": "value in hexadecimal format",
|
|
60
|
+
"hostname": "hostname",
|
|
61
|
+
"mac-address": "MAC address",
|
|
62
|
+
"mac-eui-64": "MAC EUI-64 address",
|
|
63
|
+
"pattern-filename": "pattern in the name of a file",
|
|
64
|
+
"target-email": "attack target email",
|
|
65
|
+
"uri": "Uniform Resource Identifier",
|
|
66
|
+
"url": "Uniform Resource Locator",
|
|
67
|
+
"whois-registrant-email": "email of a domain's registrant"
|
|
68
|
+
}
|
|
69
|
+
TYPE_TO_FEATURE.update(
|
|
70
|
+
dict.fromkeys(
|
|
71
|
+
("ip-src", "ip-dst"),
|
|
72
|
+
"IP address"
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
TYPE_TO_FEATURE.update(
|
|
76
|
+
dict.fromkeys(
|
|
77
|
+
("email", "email-src", "email-dst"),
|
|
78
|
+
"email address"
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
TYPE_TO_FEATURE.update(
|
|
82
|
+
dict.fromkeys(
|
|
83
|
+
("other", "text"),
|
|
84
|
+
"text"
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class FarsightDnsdbParser():
|
|
90
|
+
def __init__(self, attribute):
|
|
91
|
+
self.attribute = attribute
|
|
92
|
+
self.misp_event = MISPEvent()
|
|
93
|
+
self.misp_event.add_attribute(**attribute)
|
|
94
|
+
self.passivedns_mapping = {
|
|
95
|
+
'bailiwick': {'type': 'domain', 'object_relation': 'bailiwick'},
|
|
96
|
+
'count': {'type': 'counter', 'object_relation': 'count'},
|
|
97
|
+
'raw_rdata': {'type': 'text', 'object_relation': 'raw_rdata'},
|
|
98
|
+
'rdata': {'type': 'text', 'object_relation': 'rdata'},
|
|
99
|
+
'rrname': {'type': 'text', 'object_relation': 'rrname'},
|
|
100
|
+
'rrtype': {'type': 'text', 'object_relation': 'rrtype'},
|
|
101
|
+
'time_first': {'type': 'datetime', 'object_relation': 'time_first'},
|
|
102
|
+
'time_last': {'type': 'datetime', 'object_relation': 'time_last'},
|
|
103
|
+
'zone_time_first': {'type': 'datetime', 'object_relation': 'zone_time_first'},
|
|
104
|
+
'zone_time_last': {'type': 'datetime', 'object_relation': 'zone_time_last'}
|
|
105
|
+
}
|
|
106
|
+
self.comment = 'Result from a %s lookup on DNSDB about the %s: %s'
|
|
107
|
+
|
|
108
|
+
def parse_passivedns_results(self, query_response):
|
|
109
|
+
for query_type, results in query_response.items():
|
|
110
|
+
comment = self.comment % (query_type, TYPE_TO_FEATURE[self.attribute['type']], self.attribute['value'])
|
|
111
|
+
for result in results:
|
|
112
|
+
passivedns_object = MISPObject('passive-dns')
|
|
113
|
+
passivedns_object.distribution = DEFAULT_DISTRIBUTION_SETTING
|
|
114
|
+
if result.get('rdata') and isinstance(result['rdata'], list):
|
|
115
|
+
for rdata in result.pop('rdata'):
|
|
116
|
+
passivedns_object.add_attribute(**self._parse_attribute(comment, 'rdata', rdata))
|
|
117
|
+
for feature, value in result.items():
|
|
118
|
+
passivedns_object.add_attribute(**self._parse_attribute(comment, feature, value))
|
|
119
|
+
if result.get('time_first'):
|
|
120
|
+
passivedns_object.first_seen = result['time_first']
|
|
121
|
+
if result.get('time_last'):
|
|
122
|
+
passivedns_object.last_seen = result['time_last']
|
|
123
|
+
passivedns_object.add_reference(self.attribute['uuid'], 'related-to')
|
|
124
|
+
self.misp_event.add_object(passivedns_object)
|
|
125
|
+
|
|
126
|
+
def get_results(self):
|
|
127
|
+
event = json.loads(self.misp_event.to_json())
|
|
128
|
+
results = {key: event[key] for key in ('Attribute', 'Object')}
|
|
129
|
+
return {'results': results}
|
|
130
|
+
|
|
131
|
+
def _parse_attribute(self, comment, feature, value):
|
|
132
|
+
attribute = {'value': value, 'comment': comment, 'distribution': DEFAULT_DISTRIBUTION_SETTING}
|
|
133
|
+
attribute.update(self.passivedns_mapping[feature])
|
|
134
|
+
return attribute
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def handler(q=False):
|
|
138
|
+
if q is False:
|
|
139
|
+
return False
|
|
140
|
+
request = json.loads(q)
|
|
141
|
+
if not request.get('config') or not request['config'].get('apikey'):
|
|
142
|
+
misperrors['error'] = 'Farsight DNSDB apikey is missing'
|
|
143
|
+
return misperrors
|
|
144
|
+
if not request.get('attribute') or not check_input_attribute(request['attribute']):
|
|
145
|
+
return {'error': f'{standard_error_message}, which should contain at least a type, a value and an uuid.'}
|
|
146
|
+
attribute = request['attribute']
|
|
147
|
+
if attribute['type'] not in mispattributes['input']:
|
|
148
|
+
return {'error': 'Unsupported attributes type'}
|
|
149
|
+
config = request['config']
|
|
150
|
+
if not config.get('server'):
|
|
151
|
+
config['server'] = DEFAULT_DNSDB_SERVER
|
|
152
|
+
client_args = {feature: config[feature] for feature in ('apikey', 'server')}
|
|
153
|
+
client = dnsdb2.Client(**client_args)
|
|
154
|
+
to_query, args = parse_input(attribute, config)
|
|
155
|
+
try:
|
|
156
|
+
response = to_query(client, *args)
|
|
157
|
+
except dnsdb2.DnsdbException as e:
|
|
158
|
+
return {'error': e.__str__()}
|
|
159
|
+
except dnsdb2.exceptions.QueryError:
|
|
160
|
+
return {'error': 'Communication error occurs while executing a query, or the server reports an error due to invalid arguments.'}
|
|
161
|
+
if not response:
|
|
162
|
+
return {'error': f"Empty results on Farsight DNSDB for the {TYPE_TO_FEATURE[attribute['type']]}: {attribute['value']}."}
|
|
163
|
+
parser = FarsightDnsdbParser(attribute)
|
|
164
|
+
parser.parse_passivedns_results(response)
|
|
165
|
+
return parser.get_results()
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def parse_input(attribute, config):
|
|
169
|
+
lookup_args = {
|
|
170
|
+
'limit': config['limit'] if config.get('limit') else DEFAULT_LIMIT,
|
|
171
|
+
'offset': 0,
|
|
172
|
+
'ignore_limited': True,
|
|
173
|
+
'humantime': True
|
|
174
|
+
}
|
|
175
|
+
if attribute.get('first_seen'):
|
|
176
|
+
lookup_args['time_first_after'] = parse_timestamp(attribute['first_seen'])
|
|
177
|
+
attribute_type = attribute['type']
|
|
178
|
+
if attribute_type in flex_query_input:
|
|
179
|
+
return flex_queries, (lookup_args, attribute['value'])
|
|
180
|
+
flex = add_flex_queries(config.get('flex_queries'))
|
|
181
|
+
to_query = lookup_ip if 'ip-' in attribute_type else lookup_name
|
|
182
|
+
return to_query, (lookup_args, attribute['value'], flex)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def parse_timestamp(str_date):
|
|
186
|
+
datetime_date = datetime.strptime(str_date, '%Y-%m-%dT%H:%M:%S.%f%z')
|
|
187
|
+
return str(int(datetime_date.timestamp()))
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def add_flex_queries(flex):
|
|
191
|
+
if not flex:
|
|
192
|
+
return False
|
|
193
|
+
if flex in ('True', 'true', True, '1', 1):
|
|
194
|
+
return True
|
|
195
|
+
return False
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def flex_queries(client, lookup_args, name):
|
|
199
|
+
response = {}
|
|
200
|
+
name = name.replace('@', '.')
|
|
201
|
+
for feature in ('rdata', 'rrnames'):
|
|
202
|
+
to_call = getattr(client, f'flex_{feature}_regex')
|
|
203
|
+
results = list(to_call(name, **lookup_args))
|
|
204
|
+
for result in list(to_call(name.replace('.', '\\.'), **lookup_args)):
|
|
205
|
+
if result not in results:
|
|
206
|
+
results.append(result)
|
|
207
|
+
if results:
|
|
208
|
+
response[f'flex_{feature}'] = results
|
|
209
|
+
return response
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def lookup_name(client, lookup_args, name, flex):
|
|
213
|
+
response = {}
|
|
214
|
+
# RRSET = entries in the left-hand side of the domain name related labels
|
|
215
|
+
rrset_response = list(client.lookup_rrset(name, **lookup_args))
|
|
216
|
+
if rrset_response:
|
|
217
|
+
response['rrset'] = rrset_response
|
|
218
|
+
# RDATA = entries on the right-hand side of the domain name related labels
|
|
219
|
+
rdata_response = list(client.lookup_rdata_name(name, **lookup_args))
|
|
220
|
+
if rdata_response:
|
|
221
|
+
response['rdata'] = rdata_response
|
|
222
|
+
if flex:
|
|
223
|
+
response.update(flex_queries(client, lookup_args, name))
|
|
224
|
+
return response
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def lookup_ip(client, lookup_args, ip, flex):
|
|
228
|
+
response = {}
|
|
229
|
+
res = list(client.lookup_rdata_ip(ip, **lookup_args))
|
|
230
|
+
if res:
|
|
231
|
+
response['rdata'] = res
|
|
232
|
+
if flex:
|
|
233
|
+
response.update(flex_queries(client, lookup_args, ip))
|
|
234
|
+
return response
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def introspection():
|
|
238
|
+
return mispattributes
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def version():
|
|
242
|
+
moduleinfo['config'] = moduleconfig
|
|
243
|
+
return moduleinfo
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import geoip2.database
|
|
3
|
+
import sys
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
log = logging.getLogger('geoip_asn')
|
|
7
|
+
log.setLevel(logging.DEBUG)
|
|
8
|
+
ch = logging.StreamHandler(sys.stdout)
|
|
9
|
+
ch.setLevel(logging.DEBUG)
|
|
10
|
+
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
11
|
+
ch.setFormatter(formatter)
|
|
12
|
+
log.addHandler(ch)
|
|
13
|
+
|
|
14
|
+
misperrors = {'error': 'Error'}
|
|
15
|
+
mispattributes = {'input': ['ip-src', 'ip-dst', 'domain|ip'], 'output': ['freetext']}
|
|
16
|
+
moduleconfig = ['local_geolite_db']
|
|
17
|
+
# possible module-types: 'expansion', 'hover' or both
|
|
18
|
+
moduleinfo = {
|
|
19
|
+
'version': '0.1',
|
|
20
|
+
'author': 'GlennHD',
|
|
21
|
+
'description': 'Query a local copy of the Maxmind Geolite ASN database (MMDB format)',
|
|
22
|
+
'module-type': ['expansion', 'hover'],
|
|
23
|
+
'name': 'GeoIP ASN Lookup',
|
|
24
|
+
'logo': 'maxmind.png',
|
|
25
|
+
'requirements': ["A local copy of Maxmind's Geolite database"],
|
|
26
|
+
'features': "The module takes an IP address attribute as input and queries a local copy of the Maxmind's Geolite database to get information about the related AS number.",
|
|
27
|
+
'references': ['https://www.maxmind.com/en/home'],
|
|
28
|
+
'input': 'An IP address MISP attribute.',
|
|
29
|
+
'output': 'Text containing information about the AS number of the IP address.',
|
|
30
|
+
'descrption': "An expansion module to query a local copy of Maxmind's Geolite database with an IP address, in order to get information about its related AS number.",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def handler(q=False):
|
|
35
|
+
if q is False:
|
|
36
|
+
return False
|
|
37
|
+
request = json.loads(q)
|
|
38
|
+
|
|
39
|
+
if not request.get('config') or not request['config'].get('local_geolite_db'):
|
|
40
|
+
return {'error': 'Please specify the path of your local copy of the Maxmind Geolite ASN database'}
|
|
41
|
+
path_to_geolite = request['config']['local_geolite_db']
|
|
42
|
+
|
|
43
|
+
if request.get('ip-dst'):
|
|
44
|
+
toquery = request['ip-dst']
|
|
45
|
+
elif request.get('ip-src'):
|
|
46
|
+
toquery = request['ip-src']
|
|
47
|
+
elif request.get('domain|ip'):
|
|
48
|
+
toquery = request['domain|ip'].split('|')[1]
|
|
49
|
+
else:
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
reader = geoip2.database.Reader(path_to_geolite)
|
|
54
|
+
except FileNotFoundError:
|
|
55
|
+
return {'error': f'Unable to locate the GeoLite database you specified ({path_to_geolite}).'}
|
|
56
|
+
log.debug(toquery)
|
|
57
|
+
try:
|
|
58
|
+
answer = reader.asn(toquery)
|
|
59
|
+
stringmap = 'ASN=' + str(answer.autonomous_system_number) + ', AS Org=' + str(answer.autonomous_system_organization)
|
|
60
|
+
except Exception as e:
|
|
61
|
+
misperrors['error'] = f"GeoIP resolving error: {e}"
|
|
62
|
+
return misperrors
|
|
63
|
+
|
|
64
|
+
r = {'results': [{'types': mispattributes['output'], 'values': stringmap}]}
|
|
65
|
+
|
|
66
|
+
return r
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def introspection():
|
|
70
|
+
return mispattributes
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def version():
|
|
74
|
+
moduleinfo['config'] = moduleconfig
|
|
75
|
+
return moduleinfo
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import geoip2.database
|
|
3
|
+
import sys
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
log = logging.getLogger('geoip_city')
|
|
7
|
+
log.setLevel(logging.DEBUG)
|
|
8
|
+
ch = logging.StreamHandler(sys.stdout)
|
|
9
|
+
ch.setLevel(logging.DEBUG)
|
|
10
|
+
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
11
|
+
ch.setFormatter(formatter)
|
|
12
|
+
log.addHandler(ch)
|
|
13
|
+
|
|
14
|
+
misperrors = {'error': 'Error'}
|
|
15
|
+
mispattributes = {'input': ['ip-src', 'ip-dst', 'domain|ip'], 'output': ['freetext']}
|
|
16
|
+
moduleconfig = ['local_geolite_db']
|
|
17
|
+
# possible module-types: 'expansion', 'hover' or both
|
|
18
|
+
moduleinfo = {
|
|
19
|
+
'version': '0.1',
|
|
20
|
+
'author': 'GlennHD',
|
|
21
|
+
'description': "An expansion module to query a local copy of Maxmind's Geolite database with an IP address, in order to get information about the city where it is located.",
|
|
22
|
+
'module-type': ['expansion', 'hover'],
|
|
23
|
+
'name': 'GeoIP City Lookup',
|
|
24
|
+
'logo': 'maxmind.png',
|
|
25
|
+
'requirements': ["A local copy of Maxmind's Geolite database"],
|
|
26
|
+
'features': "The module takes an IP address attribute as input and queries a local copy of the Maxmind's Geolite database to get information about the city where this IP address is located.",
|
|
27
|
+
'references': ['https://www.maxmind.com/en/home'],
|
|
28
|
+
'input': 'An IP address MISP attribute.',
|
|
29
|
+
'output': 'Text containing information about the city where the IP address is located.',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def handler(q=False):
|
|
34
|
+
if q is False:
|
|
35
|
+
return False
|
|
36
|
+
request = json.loads(q)
|
|
37
|
+
|
|
38
|
+
if not request.get('config') or not request['config'].get('local_geolite_db'):
|
|
39
|
+
return {'error': 'Please specify the path of your local copy of Maxminds Geolite database'}
|
|
40
|
+
path_to_geolite = request['config']['local_geolite_db']
|
|
41
|
+
|
|
42
|
+
if request.get('ip-dst'):
|
|
43
|
+
toquery = request['ip-dst']
|
|
44
|
+
elif request.get('ip-src'):
|
|
45
|
+
toquery = request['ip-src']
|
|
46
|
+
elif request.get('domain|ip'):
|
|
47
|
+
toquery = request['domain|ip'].split('|')[1]
|
|
48
|
+
else:
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
reader = geoip2.database.Reader(path_to_geolite)
|
|
53
|
+
except FileNotFoundError:
|
|
54
|
+
return {'error': f'Unable to locate the GeoLite database you specified ({path_to_geolite}).'}
|
|
55
|
+
log.debug(toquery)
|
|
56
|
+
try:
|
|
57
|
+
answer = reader.city(toquery)
|
|
58
|
+
stringmap = 'Continent=' + str(answer.continent.name) + ', Country=' + str(answer.country.name) + ', Subdivision=' + str(answer.subdivisions.most_specific.name) + ', City=' + str(answer.city.name)
|
|
59
|
+
|
|
60
|
+
except Exception as e:
|
|
61
|
+
misperrors['error'] = f"GeoIP resolving error: {e}"
|
|
62
|
+
return misperrors
|
|
63
|
+
|
|
64
|
+
r = {'results': [{'types': mispattributes['output'], 'values': stringmap}]}
|
|
65
|
+
|
|
66
|
+
return r
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def introspection():
|
|
70
|
+
return mispattributes
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def version():
|
|
74
|
+
moduleinfo['config'] = moduleconfig
|
|
75
|
+
return moduleinfo
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import geoip2.database
|
|
3
|
+
import sys
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
log = logging.getLogger('geoip_country')
|
|
7
|
+
log.setLevel(logging.DEBUG)
|
|
8
|
+
ch = logging.StreamHandler(sys.stdout)
|
|
9
|
+
ch.setLevel(logging.DEBUG)
|
|
10
|
+
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
11
|
+
ch.setFormatter(formatter)
|
|
12
|
+
log.addHandler(ch)
|
|
13
|
+
|
|
14
|
+
misperrors = {'error': 'Error'}
|
|
15
|
+
mispattributes = {'input': ['ip-src', 'ip-dst', 'domain|ip'], 'output': ['freetext']}
|
|
16
|
+
moduleconfig = ['local_geolite_db']
|
|
17
|
+
# possible module-types: 'expansion', 'hover' or both
|
|
18
|
+
moduleinfo = {
|
|
19
|
+
'version': '0.2',
|
|
20
|
+
'author': 'Andreas Muehlemann',
|
|
21
|
+
'description': 'Query a local copy of Maxminds Geolite database, updated for MMDB format',
|
|
22
|
+
'module-type': ['expansion', 'hover'],
|
|
23
|
+
'name': 'GeoIP Country Lookup',
|
|
24
|
+
'logo': 'maxmind.png',
|
|
25
|
+
'requirements': ["A local copy of Maxmind's Geolite database"],
|
|
26
|
+
'features': "This module takes an IP address MISP attribute as input and queries a local copy of the Maxmind's Geolite database to get information about the location of this IP address.\n\nPlease note that composite attributes domain|ip are also supported.",
|
|
27
|
+
'references': ['https://www.maxmind.com/en/home'],
|
|
28
|
+
'input': 'An IP address MISP Attribute.',
|
|
29
|
+
'output': 'Text containing information about the location of the IP address.',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def handler(q=False):
|
|
34
|
+
if q is False:
|
|
35
|
+
return False
|
|
36
|
+
request = json.loads(q)
|
|
37
|
+
|
|
38
|
+
if not request.get('config') or not request['config'].get('local_geolite_db'):
|
|
39
|
+
return {'error': 'Please specify the path of your local copy of Maxminds Geolite database'}
|
|
40
|
+
path_to_geolite = request['config']['local_geolite_db']
|
|
41
|
+
|
|
42
|
+
if request.get('ip-dst'):
|
|
43
|
+
toquery = request['ip-dst']
|
|
44
|
+
elif request.get('ip-src'):
|
|
45
|
+
toquery = request['ip-src']
|
|
46
|
+
elif request.get('domain|ip'):
|
|
47
|
+
toquery = request['domain|ip'].split('|')[1]
|
|
48
|
+
else:
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
reader = geoip2.database.Reader(path_to_geolite)
|
|
53
|
+
except FileNotFoundError:
|
|
54
|
+
return {'error': f'Unable to locate the GeoLite database you specified ({path_to_geolite}).'}
|
|
55
|
+
log.debug(toquery)
|
|
56
|
+
try:
|
|
57
|
+
answer = reader.country(toquery)
|
|
58
|
+
except Exception as e:
|
|
59
|
+
misperrors['error'] = f"GeoIP resolving error: {e}"
|
|
60
|
+
return misperrors
|
|
61
|
+
|
|
62
|
+
r = {'results': [{'types': mispattributes['output'], 'values': [answer.country.iso_code]}]}
|
|
63
|
+
|
|
64
|
+
return r
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def introspection():
|
|
68
|
+
return mispattributes
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def version():
|
|
72
|
+
moduleinfo['config'] = moduleconfig
|
|
73
|
+
return moduleinfo
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# import requests
|
|
2
|
+
import json
|
|
3
|
+
from pymisp import MISPObject, MISPAttribute, MISPEvent
|
|
4
|
+
from . import check_input_attribute, checking_error, standard_error_message
|
|
5
|
+
from pysafebrowsing import SafeBrowsing
|
|
6
|
+
|
|
7
|
+
misperrors = {'error': 'Error'}
|
|
8
|
+
mispattributes = {'input': ['url'], 'format': 'misp_standard'}
|
|
9
|
+
moduleinfo = {
|
|
10
|
+
'version': '0.1',
|
|
11
|
+
'author': 'Stephanie S',
|
|
12
|
+
'description': 'Google safe browsing expansion module',
|
|
13
|
+
'module-type': ['expansion', 'hover'],
|
|
14
|
+
'name': 'Google Safe Browsing Lookup',
|
|
15
|
+
'logo': '',
|
|
16
|
+
'requirements': [],
|
|
17
|
+
'features': '',
|
|
18
|
+
'references': [],
|
|
19
|
+
'input': '',
|
|
20
|
+
'output': '',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
moduleconfig = ['api_key']
|
|
24
|
+
|
|
25
|
+
def handler(q=False):
|
|
26
|
+
if q is False:
|
|
27
|
+
return False
|
|
28
|
+
request = json.loads(q)
|
|
29
|
+
|
|
30
|
+
if "config" not in request or "api_key" not in request["config"]:
|
|
31
|
+
return {"error": "Google Safe Browsing API key is missing"}
|
|
32
|
+
if not request.get('attribute') or not check_input_attribute(request['attribute'], requirements=('type', 'value')):
|
|
33
|
+
return {'error': f'{standard_error_message}, {checking_error}.'}
|
|
34
|
+
if request['attribute']['type'] not in mispattributes['input']:
|
|
35
|
+
return {'error': 'Unsupported attribute type.'}
|
|
36
|
+
|
|
37
|
+
api_key = request["config"]["api_key"]
|
|
38
|
+
url = request["attribute"]["value"]
|
|
39
|
+
|
|
40
|
+
s = SafeBrowsing(api_key)
|
|
41
|
+
try:
|
|
42
|
+
response = s.lookup_urls([url])
|
|
43
|
+
|
|
44
|
+
event = MISPEvent()
|
|
45
|
+
obj = MISPObject('google-safe-browsing')
|
|
46
|
+
event.add_attribute(**request['attribute'])
|
|
47
|
+
|
|
48
|
+
if (response[url]['malicious'] != False):
|
|
49
|
+
# gsb threat types: THREAT_TYPE_UNSPECIFIED, MALWARE, SOCIAL_ENGINEERING, UNWANTED_SOFTWARE, POTENTIALLY_HARMFUL_APPLICATION
|
|
50
|
+
gsb_circl_threat_taxonomy = {"MALWARE": 'malware', "SOCIAL_ENGINEERING": 'social-engineering'}
|
|
51
|
+
|
|
52
|
+
threats = response[url]['threats']
|
|
53
|
+
malicious = response[url]['malicious']
|
|
54
|
+
platforms = response[url]['platforms']
|
|
55
|
+
|
|
56
|
+
malicious_attribute = obj.add_attribute('malicious', **{'type': 'boolean', 'value': malicious})
|
|
57
|
+
malicious_attribute.add_tag(f'ioc:artifact-state="malicious"')
|
|
58
|
+
threat_attribute = obj.add_attribute('threats', **{'type': 'text', 'value': str(" ".join(threats))})
|
|
59
|
+
for threat in threats:
|
|
60
|
+
# If the threat exists as a key in taxonomy_dict, add that tag
|
|
61
|
+
if (gsb_circl_threat_taxonomy.get(threat) is not None):
|
|
62
|
+
threat_attribute.add_tag(f'circl:incident="{gsb_circl_threat_taxonomy.get(threat)}"')
|
|
63
|
+
else:
|
|
64
|
+
threat_attribute.add_tag(f'threat-type:{str(threat).lower()}')
|
|
65
|
+
obj.add_attribute('platforms', **{'type': 'text', 'value': str(" ".join(platforms))})
|
|
66
|
+
|
|
67
|
+
else:
|
|
68
|
+
malicious_attribute = obj.add_attribute('malicious', **{'type': 'boolean', 'value': 0}) # 0 == False
|
|
69
|
+
malicious_attribute.add_tag(f'ioc:artifact-state="not-malicious"')
|
|
70
|
+
|
|
71
|
+
obj.add_reference(request['attribute']['uuid'], "describes")
|
|
72
|
+
event.add_object(obj)
|
|
73
|
+
|
|
74
|
+
# Avoid serialization issue
|
|
75
|
+
event = json.loads(event.to_json())
|
|
76
|
+
return {"results": {'Object': event['Object'], 'Attribute': event['Attribute']}}
|
|
77
|
+
|
|
78
|
+
except Exception as error:
|
|
79
|
+
return {"error": "An error occurred: " + str(error)}
|
|
80
|
+
|
|
81
|
+
def introspection():
|
|
82
|
+
return mispattributes
|
|
83
|
+
|
|
84
|
+
def version():
|
|
85
|
+
moduleinfo['config'] = moduleconfig
|
|
86
|
+
return moduleinfo
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import random
|
|
3
|
+
import time
|
|
4
|
+
try:
|
|
5
|
+
from googleapi import google
|
|
6
|
+
except ImportError:
|
|
7
|
+
print("GoogleAPI not installed. Command : pip install git+https://github.com/abenassi/Google-Search-API")
|
|
8
|
+
|
|
9
|
+
misperrors = {'error': 'Error'}
|
|
10
|
+
mispattributes = {'input': ['url'], 'output': ['text']}
|
|
11
|
+
moduleinfo = {
|
|
12
|
+
'author': 'Oun & Gindt',
|
|
13
|
+
'module-type': ['hover'],
|
|
14
|
+
'name': 'Google Search',
|
|
15
|
+
'description': 'An expansion hover module to expand google search information about an URL',
|
|
16
|
+
'version': '1.0',
|
|
17
|
+
'logo': 'google.png',
|
|
18
|
+
'requirements': ['The python Google Search API library'],
|
|
19
|
+
'features': 'The module takes an url as input to query the Google search API. The result of the query is then return as raw text.',
|
|
20
|
+
'references': ['https://github.com/abenassi/Google-Search-API'],
|
|
21
|
+
'input': 'An url attribute.',
|
|
22
|
+
'output': 'Text containing the result of a Google search on the input url.',
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def sleep(retry):
|
|
27
|
+
time.sleep(random.uniform(0, min(40, 0.01 * 2 ** retry)))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def handler(q=False):
|
|
31
|
+
if q is False:
|
|
32
|
+
return False
|
|
33
|
+
request = json.loads(q)
|
|
34
|
+
if not request.get('url'):
|
|
35
|
+
return {'error': "Unsupported attributes type"}
|
|
36
|
+
num_page = 1
|
|
37
|
+
res = ""
|
|
38
|
+
# The googleapi module sets a random useragent. The output depends on the useragent.
|
|
39
|
+
# It's better to retry 3 times.
|
|
40
|
+
for retry in range(3):
|
|
41
|
+
search_results = google.search(request['url'], num_page)
|
|
42
|
+
if len(search_results) > 0:
|
|
43
|
+
break
|
|
44
|
+
sleep(retry)
|
|
45
|
+
for i, search_result in enumerate(search_results):
|
|
46
|
+
res += "("+str(i+1)+")" + '\t'
|
|
47
|
+
res += json.dumps(search_result.description, ensure_ascii=False)
|
|
48
|
+
res += '\n\n'
|
|
49
|
+
return {'results': [{'types': mispattributes['output'], 'values':res}]}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def introspection():
|
|
53
|
+
return mispattributes
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def version():
|
|
57
|
+
return moduleinfo
|