diff --git a/idea/misp.py b/idea/misp.py index d9ebd956c1f9f577b7ded7bf52cfedc12ba24b07..a744ed2f2c2a095986bd6e027e5b6ef35135cca0 100644 --- a/idea/misp.py +++ b/idea/misp.py @@ -3,307 +3,557 @@ # Copyright (c) 2018, CESNET, z. s. p. o. # Use of this source is governed by an ISC license, see LICENSE file. +from pymisp import MISPEvent, MISPObject, NewAttributeError from uuid import uuid4 import time import itertools from _datetime import datetime - -misp_to_idea_dictionary = {} - -# Every list on current index corresponds to idea_category on the same index -misp_tags = [['circl:incident-classification="spam"'], - ['veris:action:malware:variety="Exploit vuln"', 'veris:action:malware:variety="Backdoor"', - 'ecsirt:intrusions="backdoor"'], - ['circl:incident-classification="phishing"', 'europol-incident:information-gathering="phishing"', - 'enisa:nefarious-activity-abuse="phishing-attacks"'], - ['circl:incident-classification="scan"'], - ['veris:action:malware:variety="Rootkit"'], - ['europol-incident:availability="dos-ddos"', 'ms-caro-malware:malware-type="DDoS"'], - ['circl:incident-classification="denial-of-service"'], - ['circl:incident-classification="malware"']] - -idea_categories = ["Abusive.Spam", "Attempt.Exploit", "Fraud.Phishing", "Recon.Scanning", "Malware.Rootkit", - "Availability.DDoS", "Availability.DoS", "Malware"] - -# prepare category translation dictionary -for index, tag_list in enumerate(misp_tags): - for tag in tag_list: - misp_to_idea_dictionary[tag] = idea_categories[index] - -useful_hashes = ["md5", "sha1", "sha224", "sha256", "sha384", "sha512", "sha512/224", "sha512/256", "filename|md5", - "filename|sha1", "filename|sha224", "filename|sha256", "filename|sha384", "filename|sha512", - "filename|sha512/224", "filename|sha512/256"] - -useful_ip_objects = ["ip-port", "domain-ip", "netflow", "network-connection", "network-socket"] - - -def get_date_from_timestamp(timestamp): - date_and_time = convert_epoch_to_utc(timestamp) - # returns only date from datetime, date stops at index 9 (2018-05-16T06:20:35Z) - return date_and_time[0:9] - - -def convert_epoch_to_utc(timestamp): +import re + + +category_to_taxonomy = { + 'Abusive.Spam': ['ecsirt:abusive-content="spam"', 'rsit:abusive-content="spam"'], + 'Abusive.Harassment': ['ecsirt:abusive-content="harmful-speech"', 'rsit:abusive-content="harmful-speech"'], + 'Abusive.Child': ['ecsirt:abusive-content="violence"', 'rsit:abusive-content="violence"'], + 'Abusive.Sexual': ['ecsirt:abusive-content="violence"', 'rsit:abusive-content="violence"'], + 'Abusive.Violence': ['ecsirt:abusive-content="violence"', 'rsit:abusive-content="violence"'], + 'Malware.Virus': ['ecsirt:malicious-code="virus"', 'rsit:malicious-code="virus"'], + 'Malware.Worm': ['ecsirt:malicious-code="worm"', 'rsit:malicious-code="worm"'], + 'Malware.Trojan': ['ecsirt:malicious-code="trojan"', 'rsit:malicious-code="trojan"'], + 'Malware.Spyware': ['ecsirt:malicious-code="spyware"', 'rsit:malicious-code="spyware"'], + 'Malware.Dialer': ['ecsirt:malicious-code="dialer"', 'rsit:malicious-code="dialer"'], + 'Malware.Rootkit': ['ecsirt:malicious-code="rootkit"', 'rsit:malicious-code="rootkit"'], + 'Recon.Scanning': ['ecsirt:information-gathering="scanner"', 'rsit:information-gathering="scanner"'], + 'Recon.Sniffing': ['ecsirt:information-gathering="sniffing"', 'rsit:information-gathering="sniffing"'], + 'Recon.SocialEngineering': ['ecsirt:information-gathering="social-engineering"', + 'rsit:information-gathering="social-engineering"'], + 'Recon.Searching': ['ecsirt:information-gathering', 'rsit:information-gathering'], + 'Attempt.Exploit': ['ecsirt:intrusion-attempts="exploit"', 'rsit:intrusion-attempts="exploit"'], + 'Attempt.Login': ['ecsirt:intrusion-attempts="brute-force"', 'rsit:intrusion-attempts="brute-force"'], + 'Attempt.NewSignature': ['ecsirt:intrusion-attempts', 'rsit:intrusion-attempts'], + 'Intrusion.AdminCompromise': ['ecsirt:intrusions="unprivileged-account-compromise"', + 'rsit:intrusions="unprivileged-account-compromise"'], + 'Intrusion.UserCompromise': ['ecsirt:intrusions="unprivileged-account-compromise"', + 'rsit:intrusions="unprivileged-account-compromise"'], + 'Intrusion.AppCompromise': ['ecsirt:intrusions="application-compromise"', + 'rsit:intrusions="application-compromise"'], + 'Intrusion.Botnet': ['ecsirt:intrusions="bot"', 'rsit:intrusions="bot"'], + 'Availability.DoS': ['ecsirt:availability="dos"', 'rsit:availability="dos"'], + 'Availability.DDoS': ['ecsirt:availability="ddos"', 'rsit:availability="ddos"'], + 'Availability.Sabotage': ['ecsirt:availability="sabotage"', 'rsit:availability="sabotage"'], + 'Availability.Outage': ['ecsirt:availability="outage"', 'rsit:availability="outage"'], + 'Information.UnauthorizedAccess': ['ecsirt:information-content-security="Unauthorised-information-access"', + 'rsit:information-content-security="Unauthorised-information-access"'], + 'Information.UnauthorizedModification': + ['ecsirt:information-content-security="Unauthorised-information-modification"', + 'rsit:information-content-security="Unauthorised-information-modification"'], + 'Fraud.UnauthorizedUsage': ['ecsirt:fraud="unauthorized-use-of-resources"', + 'rsit:fraud="unauthorized-use-of-resources"'], + 'Fraud.Copyright': ['ecsirt:fraud="copyright"', 'rsit:fraud="copyright"'], + 'Fraud.Masquerade': ['ecsirt:fraud="masquerade"', 'rsit:fraud="masquerade"'], + 'Fraud.Phishing': ['ecsirt:fraud="phishing"', 'ecsirt:fraud="phishing"'], + 'Fraud.Scam': ['ecsirt:fraud', 'rsit:fraud'], + 'Vulnerable.Open': ['ecsirt:vulnerable="vulnerable-service"', 'rsit:vulnerable="vulnerable-service"'], + 'Vulnerable.Config': ['ecsirt:vulnerable="vulnerable-service"', 'rsit:vulnerable="vulnerable-service"'], + 'Anomaly.Traffic': ['ecsirt:other="other"', 'rsit:other="other"'], + 'Anomaly.Connection': ['ecsirt:other="other"', 'rsit:other="other"'], + 'Anomaly.Protocol': ['ecsirt:other="other"', 'rsit:other="other"'], + 'Anomaly.System': ['ecsirt:other="other"', 'rsit:other="other"'], + 'Anomaly.Application': ['ecsirt:other="other"', 'rsit:other="other"'], + 'Anomaly.Behaviour': ['ecsirt:other="other"', 'rsit:other="other"'], + 'Other': ['ecsirt:other="other"', 'rsit:other="other"'], + 'Test': ['ecsirt:test="test"', 'rsit:test="test"'] +} + + +class MispToIdea(object): """ - Converts Unix timestamp to to UTC datetime - :param timestamp: Unix timestamp - :return: UTC datetime in string + Converts MISP event in MISP core format to IDEA event """ - return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(int(timestamp))) - -def get_ip_version(ip_addr): - """ - Gets verstion of IP address - :param ip_addr: the IP address - :return: version of IP address - """ - return "IP6" if ":" in ip_addr else "IP4" - - -def get_ip_addr(attrib, ip_only=True): - """ - Gets IP address value from event's attribute - :param attrib: the attribute - :param ip_only: gets only IP address - :return: returns IP address, role of IP address (Source|Target), port, hostname or IP address only if ip_only == True - """ - role = "Source" if "src" in attrib['type'] else "Target" - if "ip-src" == attrib['type'] or "ip-dst" == attrib['type']: - return attrib['value'] if ip_only is True else (attrib['value'], role, None, None) - elif "ip-src|port" == attrib['type'] or "ip-dst|port" == attrib['type']: - split_attrib = attrib['value'].split('|') - if len(split_attrib) == 1: - split_attrib = attrib['value'].split(':') - return split_attrib[0] if ip_only else (split_attrib[0], role, split_attrib[1], None) - else: - # attrib['type'] == "domain|ip" - split_attrib = attrib['value'].split('|') - return split_attrib[1] if ip_only else (split_attrib[1], "Target", None, split_attrib[0]) - - -# https://stackoverflow.com/questions/9807634/find-all-occurrences-of-a-key-in-nested-python-dictionaries-and-lists -def find(key, value): - """ - find gradually all values by key - :param key: searched key - :param value: searched object (dict|list) - :return: found value by key - """ - if isinstance(value, dict): - for k, v in value.items(): - if k == key: - yield v - elif isinstance(v, dict): - for result in find(key, v): - yield result - elif isinstance(v, list): - for d in v: - for result in find(key, d): + useful_hashes = ["md5", "sha1", "sha224", "sha256", "sha384", "sha512", "sha512/224", "sha512/256", "filename|md5", + "filename|sha1", "filename|sha224", "filename|sha256", "filename|sha384", "filename|sha512", + "filename|sha512/224", "filename|sha512/256"] + useful_ip_objects = ["ip-port", "domain-ip", "netflow", "network-connection", "network-socket"] + _taxonomy_to_category_created = False + taxonomy_to_category = {} + + def __init__(self): + if not self._taxonomy_to_category_created: + # create taxonomy_to_category conversion dictionary + for idea_category, misp_taxonomies in category_to_taxonomy.items(): + # take ECSIRT category and assign corresponding IDEA category + self.taxonomy_to_category[misp_taxonomies[0]] = idea_category + # take RSIT category and assign corresponding IDEA category + self.taxonomy_to_category[misp_taxonomies[1]] = idea_category + self._taxonomy_to_category_created = True + + self._attach_counter = 0 + self._source = [] + self._target = [] + self._attach = [] + + @staticmethod + def convert_epoch_to_utc(timestamp): + """ + Converts Unix timestamp to to UTC datetime + :param timestamp: Unix timestamp + :return: UTC datetime in string + """ + return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(int(timestamp))) + + @staticmethod + def get_date_from_timestamp(timestamp): + date_and_time = __class__.convert_epoch_to_utc(timestamp) + # returns only date from datetime, date stops at index 9 (2018-05-16T06:20:35Z) + return date_and_time[0:9] + + @staticmethod + def get_ip_version(ip_addr): + """ + Gets verstion of IP address + :param ip_addr: the IP address + :return: version of IP address + """ + return "IP6" if ":" in ip_addr else "IP4" + + @staticmethod + def get_ip_addr(attrib, ip_only=True): + """ + Gets IP address value from event's attribute + :param attrib: the attribute + :param ip_only: gets only IP address + :return: returns IP address, role of IP address (Source|Target), port, hostname or IP address only if + ip_only == True + """ + role = "Source" if "src" in attrib['type'] else "Target" + if "ip-src" == attrib['type'] or "ip-dst" == attrib['type']: + return attrib['value'] if ip_only is True else (attrib['value'], role, None, None) + elif "ip-src|port" == attrib['type'] or "ip-dst|port" == attrib['type']: + split_attrib = attrib['value'].split('|') + if len(split_attrib) == 1: + split_attrib = attrib['value'].split(':') + return split_attrib[0] if ip_only else (split_attrib[0], role, split_attrib[1], None) + else: + # attrib['type'] == "domain|ip" + split_attrib = attrib['value'].split('|') + return split_attrib[1] if ip_only else (split_attrib[1], "Target", None, split_attrib[0]) + + @classmethod + # https://stackoverflow.com/questions/9807634/find-all-occurrences-of-a-key-in-nested-python-dictionaries-and-lists + def find(cls, key, value): + """ + find gradually all values by key + :param key: searched key + :param value: searched object (dict|list) + :return: found value by key + """ + if isinstance(value, dict): + for k, v in value.items(): + if k == key: + yield v + elif isinstance(v, dict): + for result in cls.find(key, v): yield result - elif isinstance(value, list): - for v in value: - for result in find(key, v): - yield result + elif isinstance(v, list): + for d in v: + for result in cls.find(key, d): + yield result + elif isinstance(value, list): + for v in value: + for result in cls.find(key, v): + yield result + def process_misp_attribute(self, attrib): + """ + Process MISP attribute and get all useful data from it and insert it into corresponding IDEA object list + :param attrib: the attribute, which will be processed + :return: None + """ + # if attribute contains ip address, get it with other potential attributes(port, hostname) and append it to + # Source or Target based on IP address type + if "ip" in attrib['type']: + ip_addr, role, port, hostname = self.get_ip_addr(attrib, False) + new_description = {self.get_ip_version(ip_addr): [ip_addr]} + if port: + new_description['Port'] = [int(port)] + if hostname: + new_description['Hostname'] = [hostname] + if role == "Source": + self._source.append(new_description) + if role == "Target": + self._target.append(new_description) + + # if attrib is hash, create new attach + elif attrib['type'] in __class__.useful_hashes: + new_attach = {'Handle': "attach" + str(self._attach_counter)} + self._attach_counter += 1 + if "|" in attrib['type']: + new_attach['FileName'] = [attrib['value'].split("|")[0]] + new_attach['Hash'] = [attrib['type'].split("|")[1] + ":" + attrib['value'].split("|")[1]] + else: + new_attach['Hash'] = [attrib['type'] + ":" + attrib['value']] + self._attach.append(new_attach) + + elif attrib['type'] == "url": + new_attach_name = "attach" + str(self._attach_counter) + self._attach_counter += 1 + self._attach.append({'Handle': new_attach_name, 'Ref': [attrib['value']]}) + + elif attrib['type'] == "email-src": + self._source.append({'Email': [attrib['value']]}) + + elif attrib['type'] == "email-dst": + self._target.append({'Email': [attrib['value']]}) + + @staticmethod + def append_value_or_create_list(key, value, idea_object): + """ + Append value to array retrieved by key from idea_object, if key does not exist, create key in idea_object + and assign array with inserted value to this key + :param key: search key + :param value: appended value + :param idea_object: updated object + :return: None + """ + try: + idea_object[key].append(value) + except KeyError: + idea_object[key] = [value] + + def process_misp_object(self, misp_object): + """ + Process MISP object and get all useful data from it and insert it into corresponding IDEA object array + :param misp_object: the object, which will be processed + :return: None + """ + # if object name is in useful_ip_objects, all its attributes will be filled into source or target + if misp_object['name'] in __class__.useful_ip_objects: + source = {} + target = {} + proto_dict = {} + for attrib in misp_object['Attribute']: + if attrib['type'] == "ip-dst": + ip_addr = self.get_ip_addr(attrib) + self.append_value_or_create_list(self.get_ip_version(ip_addr), ip_addr, target) + elif attrib['type'] == "ip-src": + ip_addr = self.get_ip_addr(attrib) + self.append_value_or_create_list(self.get_ip_version(ip_addr), ip_addr, source) + elif attrib['type'] == "src-port" or attrib['object_relation'] == "src-port": + source['Port'] = [int(attrib['value'])] + elif attrib['type'] == "dst-port" or attrib['object_relation'] == "dst-port": + target['Port'] = [int(attrib['value'])] + elif attrib['object_relation'] == "hostname-src": + source['Hostname'] = [attrib['value']] + elif attrib['object_relation'] == "hostname-dst" or attrib['type'] == "hostname": + target['Hostname'] = [attrib['value']] + elif attrib['object_relation'] == "protocol": + source['Proto'] = [attrib['value']] + target['Proto'] = [attrib['value']] + elif attrib['object_relation'].startswith("layer"): + # means layer3-protocol, layer4-protocol or layer7-protocol + # save its value to number of layer + proto_dict[attrib['object_relation'][5]] = attrib['value'] + elif attrib['type'] == "src-as" or attrib['object_relation'] == "src-as": + source['ASN'] = [int(attrib['value'])] + elif attrib['type'] == "dst-as" or attrib['object_relation'] == "dst-as": + target['ASN'] = [int(attrib['value'])] + elif attrib['type'] == "domain": + # won't overwrite previous value of hostname-dst, hostname-dst can occur in object of type + # network-connection and network-socket, but domain occurs only in other object types + target['Hostname'] = [attrib['value']] + + if proto_dict: + # sort protocols by layer + proto_list = [proto_dict.get("3", "")] + [proto_dict.get("4", "")] + [proto_dict.get("7", "")] + # remove empty strings from proto list + proto_list = [proto for proto in proto_list if proto != ""] + source['Proto'] = proto_list + target['Proto'] = proto_list + + if source: + self._source.append(source) + if target: + self._target.append(target) + + elif misp_object['name'] == "file": + new_attach = {'Handle': "attach" + str(self._attach_counter)} + self._attach_counter += 1 + for attrib in misp_object['Attribute']: + if attrib['type'] in __class__.useful_hashes: + try: + new_attach['Hash'].append(attrib['type'] + ":" + attrib['value']) + except KeyError: + new_attach['Hash'] = [attrib['type'] + ":" + attrib['value']] + elif attrib['type'] == "filename": + try: + new_attach['FileName'].append(attrib['value']) + except KeyError: + new_attach['FileName'] = [attrib['value']] + if new_attach.get('Hash') or new_attach.get('FileName'): + self._attach.append(new_attach) + + def process_source_or_target_object(self, misp_object): + """ + Process source or target object, and retrieved data insert to IDEA source or target object and append it to + corresponding object array + :param misp_object: the object, which will be processed + :return: None + """ + converted_object = {} + for misp_attrib in misp_object['Attribute']: + if misp_attrib['object_relation'] not in ("Note", "Vulnerability", "Reference"): + self.append_value_or_create_list(misp_attrib['object_relation'], misp_attrib['value'], converted_object) + else: + if misp_attrib['object_relation'] == "Note": + converted_object['Note'] = misp_attrib['value'] + elif misp_attrib['object_relation'] == "Vulnerability": + self.append_value_or_create_list("Ref", "cve:" + misp_attrib['value'], + converted_object) + elif misp_attrib['object_relation'] == "Reference": + self.append_value_or_create_list("Ref", misp_attrib['value'], converted_object) + if misp_object['name'] == "source": + self._source.append(converted_object) + else: + self._target.append(converted_object) + + def process_attach_object(self, misp_object): + """ + Process attach object, and retrieved data insert to IDEA attach object and append it to attach array + :param misp_object: the object, which will be processed + :return: None + """ + converted_object = {"Handle": "att" + str(self._attach_counter)} + self._attach_counter += 1 + for attach_attrib in misp_object['Attribute']: + if attach_attrib['object_relation'] in ("Note", "ContentType", "ContentCharset", "ContentEncoding", + "Content"): + converted_object[attach_attrib['object_relation']] = attach_attrib['value'] + elif attach_attrib['object_relation'] == "FileName": + self.append_value_or_create_list("FileName", attach_attrib['value'], converted_object) + elif attach_attrib['object_relation'] == "Reference": + self.append_value_or_create_list("Ref", attach_attrib['value'], converted_object) + elif attach_attrib['object_relation'] == "Vulnerability": + self.append_value_or_create_list("Ref", "cve" + attach_attrib['value'], converted_object) + elif attach_attrib['object_relation'] == "Size": + converted_object['Size'] = int(attach_attrib['value']) + else: + self.append_value_or_create_list("Hash", attach_attrib['type'] + ":" + attach_attrib['value'], + converted_object) + self._attach.append(converted_object) + + def to_idea(self, misp_event, idea_id=None, test=False, origdata=False): + """ + Creates whole IDEA message from MISP event + :param misp_event: the misp event + :param idea_id: uuid of IDEA message (when needs to be preset) + :param test: add Test into IDEA['Category'] + :param origdata: add original data to attachment + :return: new converted IDEA message + """ + idea_event = { + 'Format': "IDEA0", + 'ID': str(idea_id) if idea_id is not None else str(uuid4()), + 'Category': [], + 'Description': misp_event['info'] + } + + # fill in the IDEA event Category + for tag in misp_event.get('Tag', []): + try: + new_category = self.taxonomy_to_category[tag['name']] + if new_category not in idea_event['Category']: + idea_event['Category'].append(new_category) + except KeyError: + pass + + # cannot determine IDEA Category, cannot convert + if not idea_event['Category']: + return None + if test and "Test" not in idea_event['Category']: + idea_event['Category'].append("Test") + + if not int(misp_event['publish_timestamp']): + idea_event['CreateTime'] = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ") + else: + idea_event['CreateTime'] = self.convert_epoch_to_utc(misp_event['publish_timestamp']) -def process_misp_attribute(attrib, attach_counter): + timestamps = itertools.chain(self.find('timestamp', misp_event['Attribute']), + self.find('timestamp', misp_event['Object'])) + try: + oldest_timestamp = min(map(int, timestamps)) + idea_event['DetectTime'] = self.convert_epoch_to_utc(oldest_timestamp) + except ValueError: + # no object and attributes --> min() ValueError for empty sequence + idea_event['DetectTime'] = misp_event['date'] + "T00:00:00Z" + + # fill in info about organizations + idea_event['Node'] = [ + { + 'Name': misp_event['Orgc']['name'], + 'Note': "MISP organization id (created event): " + misp_event['Orgc']['id'] + }, + { + 'Name': misp_event['Org']['name'], + 'Note': "MISP organization id (reported event): " + misp_event['Org']['id'] + }] + + # check all attributes for all potentially useful data + for attrib in misp_event['Attribute']: + self.process_misp_attribute(attrib) + + # check all objects for all potentially useful data + for misp_object in misp_event['Object']: + if misp_object['name'] in ("source", "target"): + # process the object + self.process_source_or_target_object(misp_object) + elif misp_object['name'] == "attach": + self.process_attach_object(misp_object) + else: + self.process_misp_object(misp_object) + + if origdata: + idea_event['Attach'].append({ + 'Handle': "att" + str(self._attach_counter), + 'Note': "original data", + 'Content': misp_event + }) + + if self._source: + idea_event['Source'] = self._source + if self._target: + idea_event['Target'] = self._target + if self._attach: + idea_event['Attach'] = self._attach + + # prepare instance for another conversion + self.__init__() + + return idea_event + + +class IdeaToMisp(object): """ - Process MISP attribute and get all useful data from it - :param attrib: the attribute - :param attach_counter: IDEA attachment counter - :return: IDEA key (Source| Target | Attach) and its data + Converts IDEA event to MISP event in MISP core format """ - # if attribute contains ip address, get it with other potential attributes(port, hostname) and append it to - # Source or Target based on IP address type - if "ip" in attrib['type']: - ip_addr, role, port, hostname = get_ip_addr(attrib, False) - new_description = {get_ip_version(ip_addr): ip_addr} - if port: - new_description['Port'] = [int(port)] - if hostname: - new_description['Hostname'] = [hostname] - return role, new_description - - # if attrib is hash, create new attach - elif attrib['type'] in useful_hashes: - new_attach = {'Handle': "attach" + str(attach_counter)} - if "|" in attrib['type']: - new_attach['Filename'] = [attrib['value'].split("|")[0]] - new_attach['Hash'] = [attrib['type'].split("|")[1] + ":" + attrib['value'].split("|")[1]] - else: - new_attach['Hash'] = [attrib['type'] + ":" + attrib['value']] - return "Attach", new_attach - elif attrib['type'] == "url": - return "Attach", {'Handle': "attach" + str(attach_counter), 'Ref': [attrib['value']]} + re_cve = re.compile("cve:", re.IGNORECASE) + severity_conversion = ["", "low", "medium", "high"] + + def __init__(self): + """ + Initializes class and prepares instance variables + """ + self._idea_event = None + self._new_event = None + + def process_basic_info(self): + """ + Process basic info of MISP event such distribution, threat_level_id, info, Tags, and standalone attributes + :return: None + """ + self._new_event = MISPEvent() + # add basic info about event + self._new_event.distribution = 1 - elif attrib['type'] == "email-src": - return "Source", [attrib['value']] - - elif attrib['type'] == "email-dst": - return "Target", [attrib['value']] - - return None, None - - -def process_misp_object(misp_object, attach_counter): - """ - Process MISP object and get all useful data from it - :param misp_object: the object - :param attach_counter: IDEA attachment counter - :return: IDEA key (Source| Target | Attach) and its data - """ - # if object name is in useful_ip_objects, all its attributes will be filled into source or target - if misp_object['name'] in useful_ip_objects: - source = {} - target = {} - proto_dict = {} - for attrib in misp_object['Attribute']: - if attrib['type'] == "ip-dst": - ip_addr = get_ip_addr(attrib) - target[get_ip_version(ip_addr)] = ip_addr - elif attrib['type'] == "ip-src": - ip_addr = get_ip_addr(attrib) - source[get_ip_version(ip_addr)] = ip_addr - elif attrib['type'] == "src-port" or attrib['object_relation'] == "src-port": - source['Port'] = [int(attrib['value'])] - elif attrib['type'] == "dst-port" or attrib['object_relation'] == "dst-port": - target['Port'] = [int(attrib['value'])] - elif attrib['type'] == "hostname-src": - source['Hostname'] = [attrib['value']] - elif attrib['type'] == "hostname-dst": - target['Hostname'] = [attrib['value']] - elif attrib['type'] == "protocol": - source['Proto'] = [attrib['value']] - target['Proto'] = [attrib['value']] - elif attrib['type'].startswith("layer"): - # means layer3-protocol, layer4-protocol or layer7-protocol - # save its value to number of layer - proto_dict[attrib['type'][5]] = attrib['value'] - elif attrib['type'] == "src-as" or attrib['object_relation'] == "src-as": - source['ASN'] = [int(attrib['value'])] - elif attrib['type'] == "dst-as" or attrib['object_relation'] == "dst-as": - target['ASN'] = [int(attrib['value'])] - elif attrib['type'] == "domain": - # won't be overwrite previous value of hostname-dst, hostname-dst can occur in object of type - # network-connection and network-socket, but domain occurs only in other object types - target['Hostname'] = [attrib['value']] - - if proto_dict: - # sort protocols by layer - proto_list = [proto_dict.get("3", "")] + [proto_dict.get("4", "")] + [proto_dict.get("7", "")] - # remove empty strings from proto list - proto_list = [proto for proto in proto_list if proto != ""] - source['Proto'] = proto_list - target['Proto'] = proto_list - - if source: - yield "Source", source - if target: - yield "Target", target - - elif misp_object['name'] == "file": - new_attach = {'Handle': "attach" + str(attach_counter)} - for attrib in misp_object['Attribute']: - if attrib['type'] in useful_hashes: - try: - new_attach['Hash'].append(attrib['type'] + ":" + attrib['value']) - except KeyError: - new_attach['Hash'] = [attrib['type'] + ":" + attrib['value']] - elif attrib['type'] == "filename": - try: - new_attach['Filename'].append(attrib['value']) - except KeyError: - new_attach['Filename'] = [attrib['value']] - if new_attach.get('Hash') or new_attach.get('Filename'): - yield "Attach", new_attach - else: - return None, None - - -def to_idea(misp_event, idea_id=None, test=False, origdata=False): - """ - Creates whole IDEA message from MISP event - :param misp_event: the misp event - :param idea_id: uuid of IDEA message (when needs to be preset) - :param test: add Test into IDEA['Category'] - :param origdata: add original data to attachment - :return: new converted IDEA message - """ - idea_event = { - 'Format': "IDEA0", - 'ID': str(idea_id) if idea_id is not None else str(uuid4()), - 'Category': [], - 'Description': misp_event['info'], - 'Source': [], - 'Target': [], - 'Attach': [] - } - - # fill in the IDEA event Category - for tag in misp_event.get('Tag', []): try: - new_category = misp_to_idea_dictionary[tag['name']] - if new_category not in idea_event['Category']: - idea_event['Category'].append(new_category) + self._new_event.threat_level_id = __class__.severity_conversion.index( + self._idea_event['_CESNET']['EventSeverity']) except KeyError: - pass - - # cannot determine IDEA Category, cannot convert - if not idea_event['Category']: - return None - if test: - idea_event['Category'].append("Test") - - if not int(misp_event['publish_timestamp']): - idea_event['CreateTime'] = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ") - else: - idea_event['CreateTime'] = convert_epoch_to_utc(misp_event['publish_timestamp']) - - attach_counter = 0 - - timestamps = itertools.chain(find('timestamp', misp_event['Attribute']), - find('timestamp', misp_event['Object'])) - try: - oldest_timestamp = min(map(int, timestamps)) - idea_event['DetectTime'] = convert_epoch_to_utc(oldest_timestamp) - except ValueError: - # no object and attributes --> min() ValueError for empty sequence - idea_event['DetectTime'] = misp_event['date'] + "T00:00:00Z" - - # fill in info about organizations - idea_event['Node'] = [ - { - 'Name': misp_event['Orgc']['name'], - 'Note': "MISP organization id (created event): " + misp_event['Orgc']['id'] - }, - { - 'Name': misp_event['Org']['name'], - 'Note': "MISP organization id (reported event): " + misp_event['Org']['id'] - }] - - # check all attributes for all potentially useful data - for attrib in misp_event['Attribute']: - key, new_data = process_misp_attribute(attrib, attach_counter) - if key and new_data: - idea_event[key].append(new_data) - if key == "Attach": - attach_counter += 1 - - # check all objects for all potentially useful data - for misp_object in misp_event['Object']: - for key, new_data in process_misp_object(misp_object, attach_counter): - idea_event[key].append(new_data) - if key == "Attach": - attach_counter += 1 - - if origdata: - idea_event['Attach'].append({ - 'Handle': "att" + str(attach_counter), - 'Note': "original data", - 'Content': misp_event - }) - attach_counter += 1 - - return idea_event + # EventSeverity not in IDEA message --> 0 = Undefined + self._new_event.threat_level_id = 1 + + self._new_event.analysis = 2 + # [0:10] is for taking date only from DetectTime (2019-01-26xxxxxxx) + self._new_event.date = datetime.strptime(self._idea_event['DetectTime'][0:10], "%Y-%m-%d") + + if self._idea_event.get('Description'): + # add Description as event info and add Note as well if present + try: + self._new_event.info = self._idea_event['Description'] + ". " + self._idea_event['Note'] + except KeyError: + self._new_event.info = self._idea_event['Description'] + elif self._idea_event.get('Note'): + # or atleast Note if Description is not present + self._new_event.info = self._idea_event['Note'] + + # convert all categories in IDEA to taxonomies + for idea_category in self._idea_event['Category']: + # add both taxonomy ECSIRT and RSIT + self._new_event.add_tag(category_to_taxonomy[idea_category][0]) + self._new_event.add_tag(category_to_taxonomy[idea_category][1]) + + for reference in self._idea_event.get('Ref', []): + # if reference is cve, add MISP attribute as vulnerability, otherwise add MISP attribute as link + if __class__.re_cve.search(reference): + self._new_event.add_attribute(category="External analysis", type="vulnerability", + value=__class__.re_cve.split(reference)[1]) + else: + self._new_event.add_attribute(category="External analysis", type="link", + value=reference) + + @classmethod + def add_attribute_to_object(cls, key, value, misp_object): + """ + Add one value (new MISP attribute) to source or target or attach object + :param key: name of attribute relation + :param value: inserted value + :param misp_object: object, which will the attribute be inserted into + :return: the object with inserted attribute + """ + try: + misp_object.add_attribute(key, value=value) + except NewAttributeError: + if key == "Ref": + if __class__.re_cve.search(value): + misp_object.add_attribute("Vulnerability", value=cls.re_cve.split(value)[1]) + else: + misp_object.add_attribute("Reference", value=value) + if misp_object.name == "attach" and key == "Hash": + hash_method = value.split(":", 1)[0] + # FIXME may not be fixed properly + hash_value = value.split(":", 1)[1] + misp_object.add_attribute(hash_method.lower(), value=hash_value) + return misp_object + + def process_one_idea_object(self, object_name): + """ + Process IDEA's Source or Target or Attach and convert it into MISP object + :param object_name: defines processing of IDEA's 'Source' or 'Target' or 'Attach' + :return: None + """ + for idea_obj in self._idea_event.get(object_name, []): + new_object = MISPObject(name=object_name.lower(), strict=True, standalone=False, + misp_objects_path_custom='object_templates') + for key, value in idea_obj.items(): + if isinstance(value, list): + for list_value in value: + new_object = self.add_attribute_to_object(key, list_value, new_object) + else: + new_object = self.add_attribute_to_object(key, value, new_object) + + self._new_event.add_object(new_object) + + def process_all_idea_objects(self): + """ + Calls processing of Source, Target and Attach objects + :return: None + """ + self.process_one_idea_object('Source') + self.process_one_idea_object('Target') + self.process_one_idea_object('Attach') + + def to_misp(self, idea_event): + """ + Converts IDEA message into MISP core format message + :param idea_event: the idea event, which will be converted + :return: instance of MISPEvent (with converted data) + """ + self._idea_event = idea_event + # fill new event with basic info and set event as published + self.process_basic_info() + self._new_event.publish() + + self.process_all_idea_objects() + + return self._new_event diff --git a/misp_object_templates/attach/definition.json b/misp_object_templates/attach/definition.json new file mode 100644 index 0000000000000000000000000000000000000000..b559e02995a05f711da11676d6a9d13c4ba0abb4 --- /dev/null +++ b/misp_object_templates/attach/definition.json @@ -0,0 +1,121 @@ +{ + "name": "attach", + "meta-category": "misc", + "description": "Event attachment", + "uuid": "f5a964ac-5782-4c3e-8056-9b2783c987a8", + "version": 1, + "attributes": { + "FileName": { + "misp-attribute": "filename", + "description": "Name of file", + "ui-priority": 1, + "categories": ["Payload delivery"], + "multiple": true + }, + "md5": { + "misp-attribute": "md5", + "description": "[Insecure] MD5 hash (128 bits)", + "ui-priority": 0, + "categories": ["Payload delivery"], + "multiple": true + }, + "sha1": { + "misp-attribute": "sha1", + "description": "[Insecure] Secure Hash Algorithm 1 (160 bits)", + "ui-priority": 0, + "categories": ["Payload delivery"], + "multiple": true + }, + "sha224": { + "misp-attribute": "sha224", + "description": "Secure Hash Algorithm 2 (224 bits)", + "ui-priority": 0, + "categories": ["Payload delivery"], + "multiple": true + }, + "sha256": { + "misp-attribute": "sha256", + "description": "Secure Hash Algorithm 2 (256 bits)", + "ui-priority": 0, + "categories": ["Payload delivery"], + "multiple": true + }, + "sha384": { + "misp-attribute": "sha384", + "description": "Secure Hash Algorithm 2 (384 bits)", + "ui-priority": 0, + "categories": ["Payload delivery"], + "multiple": true + }, + "sha512": { + "misp-attribute": "sha512", + "description": "Secure Hash Algorithm 2 (512 bits)", + "ui-priority": 0, + "categories": ["Payload delivery"], + "multiple": true + }, + "sha512/224": { + "misp-attribute": "sha512/224", + "description": "Secure Hash Algorithm 2 (224 bits)", + "ui-priority": 0, + "categories": ["Payload delivery"], + "multiple": true + }, + "Size": { + "misp-attribute": "size-in-bytes", + "description": "Length of the content", + "ui-priority": 0, + "categories": ["Other"], + "multiple": false + }, + "Vulnerability": { + "misp-attribute": "vulnerability", + "description": "Reference to known source, related to vulnerability, specific to this attachment", + "ui-priority": 0, + "categories": ["External analysis"], + "multiple": true + }, + "Reference": { + "misp-attribute": "other", + "description": "Reference to known source, related to attack, specific to this attachment", + "ui-priority": 0, + "categories": ["External analysis"], + "multiple": true + }, + "Note": { + "misp-attribute": "text", + "description": "Free text human readable additional note", + "ui-priority": 0, + "categories": ["Payload delivery"], + "multiple": false + }, + "ContentType": { + "misp-attribute": "text", + "description": "Internet Media Type of the attachment", + "ui-priority": 1, + "categories": ["Payload delivery"], + "multiple": false + }, + "ContentCharset": { + "misp-attribute": "text", + "description": "Name of the content character set", + "ui-priority": 0, + "categories": ["Payload delivery"], + "multiple": false + }, + "ContentEncoding": { + "misp-attribute": "text", + "description": "Encoding of the content", + "ui-priority": 0, + "categories": ["Payload delivery"], + "multiple": false + }, + "Content": { + "misp-attribute": "text", + "description": "Attachment content", + "ui-priority": 1, + "categories": ["Payload delivery"], + "multiple": false + } + } +} \ No newline at end of file diff --git a/misp_object_templates/source/definition.json b/misp_object_templates/source/definition.json new file mode 100644 index 0000000000000000000000000000000000000000..1df52b3d787e58446795d09ccc4c913063488be6 --- /dev/null +++ b/misp_object_templates/source/definition.json @@ -0,0 +1,104 @@ +{ + "name": "source", + "meta-category": "network", + "description": "Description of the source of the event", + "uuid": "63cf1c78-4afe-49be-baff-2c101a942000", + "version": 1, + "attributes": { + "Hostname": { + "misp-attribute": "hostname", + "description": "Participating hostname", + "ui-priority": 0, + "categories": ["Network activity"], + "multiple": true + }, + "IP4": { + "misp-attribute": "ip-src", + "description": "Source IPv4 address", + "ui-priority": 1, + "categories": ["Network activity"], + "multiple": true + }, + "MAC": { + "misp-attribute": "mac-address", + "description": "Source MAC adress", + "ui-priority": 0, + "categories": ["Network activity"], + "multiple": true + }, + "IP6": { + "misp-attribute": "ip-src", + "description": "Source IPv6 address", + "ui-priority": 1, + "categories": ["Network activity"], + "multiple": true + }, + "Port": { + "misp-attribute": "port", + "description": "Source port", + "ui-priority": 1, + "categories": ["Other"], + "multiple": true + }, + "Proto": { + "misp-attribute": "text", + "description": "Name of used protocol", + "ui-priority": 1, + "categories": ["Network activity"], + "multiple": true, + "values_list": [ + "tcp", + "udp", + "icmp", + "ip", + "arp", + "http", + "https", + "ftp" + ], + "required": false + }, + "URL": { + "misp-attribute": "url", + "description": "Source URL", + "ui-priority": 0, + "categories": ["Network activity"], + "multiple": true + }, + "Email": { + "misp-attribute": "email-src", + "description": "Source email adress", + "ui-priority": 0, + "categories": ["Payload delivery"], + "multiple": true + }, + "Note": { + "misp-attribute": "text", + "description": "Free text human readable additional note", + "ui-priority": 0, + "categories": ["Payload delivery"], + "multiple": false + }, + "ASN": { + "misp-attribute": "AS", + "description": "Autonomous system number of this source", + "ui-priority": 0, + "categories": ["Network activity"], + "multiple": true + }, + "Vulnerability": { + "misp-attribute": "vulnerability", + "description": "Reference to known source, related to vulnerability, specific to this source", + "ui-priority": 0, + "categories": ["External analysis"], + "multiple": true + }, + "Reference": { + "misp-attribute": "other", + "description": "Reference to known source, related to attack, specific to this source", + "ui-priority": 0, + "categories": ["External analysis"], + "multiple": true + } + } +} \ No newline at end of file diff --git a/misp_object_templates/target/definition.json b/misp_object_templates/target/definition.json new file mode 100644 index 0000000000000000000000000000000000000000..7336da9829da9016af34f6c0e18167e927ca1224 --- /dev/null +++ b/misp_object_templates/target/definition.json @@ -0,0 +1,104 @@ +{ + "name": "target", + "meta-category": "network", + "description": "Description of the target of the event", + "uuid": "b251672f-3463-47d6-a20f-19d04c383195", + "version": 1, + "attributes": { + "Hostname": { + "misp-attribute": "hostname", + "description": "Participating hostname", + "ui-priority": 0, + "categories": ["Network activity"], + "multiple": true + }, + "IP4": { + "misp-attribute": "ip-dst", + "description": "Target IPv4 address", + "ui-priority": 1, + "categories": ["Network activity"], + "multiple": true + }, + "MAC": { + "misp-attribute": "mac-address", + "description": "Target MAC adress", + "ui-priority": 0, + "categories": ["Network activity"], + "multiple": true + }, + "IP6": { + "misp-attribute": "ip-dst", + "description": "Target IPv6 address", + "ui-priority": 1, + "categories": ["Network activity"], + "multiple": true + }, + "Port": { + "misp-attribute": "port", + "description": "Target port", + "ui-priority": 1, + "categories": ["Other"], + "multiple": true + }, + "Proto": { + "misp-attribute": "text", + "description": "Name of used protocol", + "ui-priority": 1, + "categories": ["Network activity"], + "multiple": true, + "values_list": [ + "tcp", + "udp", + "icmp", + "ip", + "arp", + "http", + "https", + "ftp" + ], + "required": false + }, + "URL": { + "misp-attribute": "url", + "description": "Target URL", + "ui-priority": 0, + "categories": ["Network activity"], + "multiple": true + }, + "Email": { + "misp-attribute": "email-dst", + "description": "Target email adress", + "ui-priority": 0, + "categories": ["Payload delivery"], + "multiple": true + }, + "Note": { + "misp-attribute": "text", + "description": "Free text human readable additional note", + "ui-priority": 0, + "categories": ["Payload delivery"], + "multiple": false + }, + "ASN": { + "misp-attribute": "AS", + "description": "Autonomous system number of this target", + "ui-priority": 0, + "categories": ["Network activity"], + "multiple": true + }, + "Vulnerability": { + "misp-attribute": "vulnerability", + "description": "Reference to known source, related to vulnerability, specific to this target", + "ui-priority": 0, + "categories": ["External analysis"], + "multiple": true + }, + "Reference": { + "misp-attribute": "other", + "description": "Reference to known source, related to attack, specific to this target", + "ui-priority": 0, + "categories": ["External analysis"], + "multiple": true + } + } +} \ No newline at end of file