From 7967542be67a1773b008e99bd123ea91a22dff53 Mon Sep 17 00:00:00 2001 From: Jean-Louis Huynen Date: Tue, 26 Oct 2021 15:11:20 +0200 Subject: [PATCH] add: [passive-ssh] initial commit --- misp_modules/modules/expansion/__init__.py | 4 +- misp_modules/modules/expansion/passive-ssh.py | 140 ++++++++++++++++++ 2 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 misp_modules/modules/expansion/passive-ssh.py diff --git a/misp_modules/modules/expansion/__init__.py b/misp_modules/modules/expansion/__init__.py index 2723736..a817c2a 100644 --- a/misp_modules/modules/expansion/__init__.py +++ b/misp_modules/modules/expansion/__init__.py @@ -17,7 +17,7 @@ __all__ = ['cuckoo_submit', 'vmray_submit', 'bgpranking', 'circl_passivedns', 'c 'virustotal_public', 'apiosintds', 'urlscan', 'securitytrails', 'apivoid', 'assemblyline_submit', 'assemblyline_query', 'ransomcoindb', 'malwarebazaar', 'lastline_query', 'lastline_submit', 'sophoslabs_intelix', 'cytomic_orion', 'censys_enrich', - 'trustar_enrich', 'recordedfuture', 'html_to_markdown', 'socialscan'] + 'trustar_enrich', 'recordedfuture', 'html_to_markdown', 'socialscan', 'passive-ssh'] minimum_required_fields = ('type', 'uuid', 'value') @@ -27,4 +27,4 @@ standard_error_message = 'This module requires an "attribute" field as input' def check_input_attribute(attribute, requirements=minimum_required_fields): - return all(feature in attribute for feature in requirements) + return all(feature in attribute for feature in requirements) \ No newline at end of file diff --git a/misp_modules/modules/expansion/passive-ssh.py b/misp_modules/modules/expansion/passive-ssh.py new file mode 100644 index 0000000..bf70ec9 --- /dev/null +++ b/misp_modules/modules/expansion/passive-ssh.py @@ -0,0 +1,140 @@ +import json +import requests +from . import check_input_attribute, standard_error_message +from collections import defaultdict +from pymisp import MISPEvent, MISPObject + +misperrors = {'error': 'Error'} + +mispattributes = {'input': ['ip-src', 'ip-dst', 'ssh-fingerprint'], + 'format': 'misp_standard'} + +moduleinfo = {'version': '1', 'author': 'Jean-Louis Huynen', + 'description': 'An expansion module to enrich, SSH key fingerprints and IP addresses with information collected by passive-ssh', + 'module-type': ['expansion', 'hover']} + +moduleconfig = ["custom_api_url", "api_user", "api_key"] + +passivessh_url = 'https://passivessh.circl.lu/' + +host_query = '/host/ssh' +fingerprint_query = '/fingerprint/all' + + +class PassivesshParser(): + def __init__(self, attribute, passivesshresult): + self.attribute = attribute + self.passivesshresult = passivesshresult + self.misp_event = MISPEvent() + self.misp_event.add_attribute(**attribute) + self.references = defaultdict(list) + + def get_result(self): + if self.references: + self.__build_references() + event = json.loads(self.misp_event.to_json()) + results = {key: event[key] for key in ( + 'Attribute', 'Object') if (key in event and event[key])} + return {'results': results} + + def parse_passivessh_information(self): + passivessh_object = MISPObject('passive-ssh') + if 'first_seen' in self.passivesshresult: + passivessh_object.add_attribute( + 'first_seen', **{'type': 'datetime', 'value': self.passivesshresult['first_seen']}) + if 'last_seen' in self.passivesshresult: + passivessh_object.add_attribute( + 'last_seen', **{'type': 'datetime', 'value': self.passivesshresult['last_seen']}) + if 'base64' in self.passivesshresult: + passivessh_object.add_attribute( + 'base64', **{'type': 'text', 'value': self.passivesshresult['base64']}) + if 'keys' in self.passivesshresult: + for key in self.passivesshresult['keys']: + passivessh_object.add_attribute( + 'fingerprint', **{'type': 'ssh-fingerprint', 'value': key['fingerprint']}) + if 'hosts' in self.passivesshresult: + for host in self.passivesshresult['hosts']: + passivessh_object.add_attribute( + 'host', **{'type': 'ip-dst', 'value': host}) + + passivessh_object.add_reference(self.attribute['uuid'], 'related-to') + self.misp_event.add_object(passivessh_object) + + def __build_references(self): + for object_uuid, references in self.references.items(): + for misp_object in self.misp_event.objects: + if misp_object.uuid == object_uuid: + for reference in references: + misp_object.add_reference(**reference) + break + + +def check_url(url): + return "{}/".format(url) if not url.endswith('/') else url + + +def handler(q=False): + + if q is False: + return False + request = json.loads(q) + + api_url = check_url(request['config']['custom_api_url']) if request['config'].get( + 'custom_api_url') else passivessh_url + + if request['config'].get('api_user'): + api_user = request['config'].get('api_user') + else: + misperrors['error'] = 'passive-ssh user required' + return misperrors + if request['config'].get('api_key'): + api_key = request['config'].get('api_key') + else: + misperrors['error'] = 'passive-ssh password required' + return misperrors + + if not request.get('attribute') or not check_input_attribute(request['attribute']): + return {'error': f'{standard_error_message}, which should contain at least a type, a value and an uuid.'} + attribute = request['attribute'] + if attribute.get('type') == 'ip-src': + type = host_query + pass + elif attribute.get('type') == 'ip-dst': + type = host_query + pass + elif attribute.get('type') == 'ssh-fingerprint': + type = fingerprint_query + pass + else: + misperrors['error'] = 'ip is missing.' + return misperrors + + r = requests.get("{}{}/{}".format(api_url, type, + attribute['value']), auth=(api_user, api_key)) + + if r.status_code == 200: + passivesshresult = r.json() + if not passivesshresult: + misperrors['error'] = 'Empty result' + return misperrors + elif r.status_code == 404: + misperrors['error'] = 'Non existing hash' + return misperrors + else: + misperrors['error'] = 'API not accessible' + return misperrors + + parser = PassivesshParser(attribute, passivesshresult) + parser.parse_passivessh_information() + result = parser.get_result() + + return result + + +def introspection(): + return mispattributes + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo