-
Notifications
You must be signed in to change notification settings - Fork 232
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #488 from sebdraven/master
Module Yeti
- Loading branch information
Showing
4 changed files
with
203 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"description": "Module to process a query on Yeti.", | ||
"logo": "", | ||
"requirements": ["pyeti", "API key "], | ||
"input": "A domain, hostname,IP, sha256,sha1, md5, url of MISP attribute.", | ||
"output": "MISP attributes and objects fetched from the Yeti instances.", | ||
"references": ["https://github.com/yeti-platform/yeti", "https://github.com/sebdraven/pyeti"], | ||
"features": "This module add context and links between observables using yeti" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
import json | ||
import logging | ||
|
||
try: | ||
import pyeti | ||
except ImportError: | ||
print("pyeti module not installed.") | ||
|
||
from pymisp import MISPEvent, MISPObject | ||
|
||
misperrors = {'error': 'Error'} | ||
|
||
mispattributes = {'input': ['AS', 'ip-src', 'ip-dst', 'hostname', 'domain', 'sha256', 'sha1', 'md5', 'url'], | ||
'format': 'misp_standard' | ||
} | ||
# possible module-types: 'expansion', 'hover' or both | ||
moduleinfo = {'version': '1', 'author': 'Sebastien Larinier @sebdraven', | ||
'description': 'Query on yeti', | ||
'module-type': ['expansion', 'hover']} | ||
|
||
moduleconfig = ['apikey', 'url'] | ||
|
||
|
||
class Yeti(): | ||
|
||
def __init__(self, url, key, attribute): | ||
self.misp_mapping = {'Ip': 'ip-dst', 'Domain': 'domain', 'Hostname': 'hostname', 'Url': 'url', | ||
'AutonomousSystem': 'AS', 'File': 'sha256'} | ||
self.yeti_client = pyeti.YetiApi(url=url, api_key=key) | ||
self.attribute = attribute | ||
self.misp_event = MISPEvent() | ||
self.misp_event.add_attribute(**attribute) | ||
|
||
def search(self, value): | ||
obs = self.yeti_client.observable_search(value=value) | ||
if obs: | ||
return obs[0] | ||
|
||
def get_neighboors(self, obs_id): | ||
neighboors = self.yeti_client.neighbors_observables(obs_id) | ||
if neighboors and 'objs' in neighboors: | ||
links_by_id = {link['dst']['id']: (link['description'], 'dst') for link in neighboors['links'] | ||
if link['dst']['id'] != obs_id} | ||
links_by_id.update({link['src']['id']: (link['description'], 'src') for link in neighboors['links'] | ||
if link['src']['id'] != obs_id}) | ||
|
||
for n in neighboors['objs']: | ||
yield n, links_by_id[n['id']] | ||
|
||
def parse_yeti_result(self): | ||
obs = self.search(self.attribute['value']) | ||
|
||
for obs_to_add, link in self.get_neighboors(obs['id']): | ||
object_misp_domain_ip = self.__get_object_domain_ip(obs_to_add) | ||
if object_misp_domain_ip: | ||
self.misp_event.add_object(object_misp_domain_ip) | ||
continue | ||
object_misp_url = self.__get_object_url(obs_to_add) | ||
if object_misp_url: | ||
self.misp_event.add_object(object_misp_url) | ||
continue | ||
if link[0] == 'NS record': | ||
object_ns_record = self.__get_object_ns_record(obs_to_add, link[1]) | ||
if object_ns_record: | ||
self.misp_event.add_object(object_ns_record) | ||
continue | ||
self.__get_attribute(obs_to_add, link[0]) | ||
|
||
def get_result(self): | ||
event = json.loads(self.misp_event.to_json()) | ||
results = {key: event[key] for key in ('Attribute', 'Object') if key in event} | ||
return results | ||
|
||
def __get_attribute(self, obs_to_add, link): | ||
|
||
try: | ||
type_attr = self.misp_mapping[obs_to_add['type']] | ||
value = None | ||
if obs_to_add['type'] == 'File': | ||
value = obs_to_add['value'].split(':')[1] | ||
else: | ||
value = obs_to_add['value'] | ||
attr = self.misp_event.add_attribute(value=value, type=type_attr) | ||
attr.comment = '%s: %s' % (link, self.attribute['value']) | ||
except KeyError: | ||
logging.error('type not found %s' % obs_to_add['type']) | ||
return | ||
|
||
for t in obs_to_add['tags']: | ||
self.misp_event.add_attribute_tag(t['name'], attr['uuid']) | ||
|
||
def __get_object_domain_ip(self, obj_to_add): | ||
if (obj_to_add['type'] == 'Ip' and self.attribute['type'] in ['hostname', 'domain']) or \ | ||
(obj_to_add['type'] in ('Hostname', 'Domain') and self.attribute['type'] in ('ip-src', 'ip-dst')): | ||
domain_ip_object = MISPObject('domain-ip') | ||
domain_ip_object.add_attribute(self.__get_relation(obj_to_add), | ||
obj_to_add['value']) | ||
domain_ip_object.add_attribute(self.__get_relation(self.attribute, is_yeti_object=False), | ||
self.attribute['value']) | ||
domain_ip_object.add_reference(self.attribute['uuid'], 'related_to') | ||
|
||
return domain_ip_object | ||
|
||
def __get_object_url(self, obj_to_add): | ||
if (obj_to_add['type'] == 'Url' and self.attribute['type'] in ['hostname', 'domain', 'ip-src', 'ip-dst']) or ( | ||
obj_to_add['type'] in ('Hostname', 'Domain', 'Ip') and self.attribute['type'] == 'url' | ||
): | ||
url_object = MISPObject('url') | ||
obj_relation = self.__get_relation(obj_to_add) | ||
if obj_relation: | ||
url_object.add_attribute(obj_relation, obj_to_add['value']) | ||
obj_relation = self.__get_relation(self.attribute, is_yeti_object=False) | ||
if obj_relation: | ||
url_object.add_attribute(obj_relation, | ||
self.attribute['value']) | ||
url_object.add_reference(self.attribute['uuid'], 'related_to') | ||
|
||
return url_object | ||
|
||
def __get_object_ns_record(self, obj_to_add, link): | ||
queried_domain = None | ||
ns_domain = None | ||
object_dns_record = MISPObject('dns-record') | ||
if link == 'dst': | ||
queried_domain = self.attribute['value'] | ||
ns_domain = obj_to_add['value'] | ||
elif link == 'src': | ||
queried_domain = obj_to_add['value'] | ||
ns_domain = self.attribute['value'] | ||
if queried_domain and ns_domain: | ||
object_dns_record.add_attribute('queried-domain', queried_domain) | ||
object_dns_record.add_attribute('ns-record', ns_domain) | ||
object_dns_record.add_reference(self.attribute['uuid'], 'related_to') | ||
|
||
return object_dns_record | ||
|
||
def __get_relation(self, obj, is_yeti_object=True): | ||
if is_yeti_object: | ||
type_attribute = self.misp_mapping[obj['type']] | ||
else: | ||
type_attribute = obj['type'] | ||
if type_attribute == 'ip-src' or type_attribute == 'ip-dst': | ||
return 'ip' | ||
elif 'domain' == type_attribute: | ||
return 'domain' | ||
elif 'hostname' == type_attribute: | ||
return 'domain' | ||
elif type_attribute == 'url': | ||
return type_attribute | ||
|
||
|
||
def handler(q=False): | ||
if q is False: | ||
return False | ||
|
||
apikey = None | ||
yeti_url = None | ||
yeti_client = None | ||
|
||
request = json.loads(q) | ||
attribute = request['attribute'] | ||
if attribute['type'] not in mispattributes['input']: | ||
return {'error': 'Unsupported attributes type'} | ||
|
||
if 'config' in request and 'url' in request['config']: | ||
yeti_url = request['config']['url'] | ||
if 'config' in request and 'apikey' in request['config']: | ||
apikey = request['config']['apikey'] | ||
if apikey and yeti_url: | ||
yeti_client = Yeti(yeti_url, apikey, attribute) | ||
|
||
if yeti_client: | ||
yeti_client.parse_yeti_result() | ||
return {'results': yeti_client.get_result()} | ||
else: | ||
misperrors['error'] = 'Yeti Config Error' | ||
return misperrors | ||
|
||
|
||
def version(): | ||
moduleinfo['config'] = moduleconfig | ||
return moduleinfo | ||
|
||
|
||
def introspection(): | ||
return mispattributes |