def _report_sighting(self, sighting: Sighting): """ Reports a STIX-2 Sighting to OpenCTI, modeled as a `OpenCTI.stix_sighting_relation`. @param sighting The STIX-2 Sighting object to report """ if type(sighting) is not Sighting: self.opencti_helper.log_error( f"Error reporting sighting from Threat Bus. Expected a STIX-2 Sighting: {sighting}" ) return entity_id = self._get_threatbus_entity().get("id", None) resp = self.opencti_helper.api.stix_sighting_relationship.create( fromId=sighting.sighting_of_ref, toId=entity_id, createdBy=entity_id, first_seen=sighting.first_seen.astimezone().strftime( "%Y-%m-%dT%H:%M:%SZ") if sighting.get("first_seen") else None, last_seen=sighting.last_seen.astimezone().strftime( "%Y-%m-%dT%H:%M:%SZ") if sighting.get("last_seen") else None, confidence=50, externalReferences=[sighting.sighting_of_ref], count=1, ) self.opencti_helper.log_info(f"Created sighting {resp}")
async def transform_context(sighting: Sighting, transform_cmd: str) -> Sighting: """ Transforms the context of a sighting using the command configured in `transform_context` @param sighting the sighting as it was reported by VAST @param transform_cmd The command to use to pipe sightings to. Treated as template string: occurrences of '%ioc' in the cmd string get replaced with the matched IoC. @return a copy of the original sighting with the x_threatbus_context field set and transformed accordingly """ context = ( sighting.x_threatbus_sighting_context if ThreatBusSTIX2Constants.X_THREATBUS_SIGHTING_CONTEXT.value in sighting.object_properties() else None ) if not context: logger.error( f"Cannot invoke `transform_context` command because no context data is found in the sighting {sighting}" ) return indicator = ( sighting.x_threatbus_indicator if ThreatBusSTIX2Constants.X_THREATBUS_INDICATOR.value in sighting.object_properties() else None ) if indicator: _, ioc_value = split_object_path_and_value(indicator.pattern) else: # try to find the indicator value instead ioc_value = ( sighting.x_threatbus_indicator_value if ThreatBusSTIX2Constants.X_THREATBUS_INDICATOR_VALUE.value in sighting.object_properties() else None ) if not ioc_value: logger.error( f"Cannot invoke `transform_context` command because no indicator value is found in the sighting {sighting}" ) return transformed_context_raw = await invoke_cmd_for_context( transform_cmd, context, ioc_value ) try: transformed_context = json.loads(transformed_context_raw) # recreate the sighting with the new transformed context ser = json.loads(sighting.serialize()) ser[ ThreatBusSTIX2Constants.X_THREATBUS_SIGHTING_CONTEXT.value ] = transformed_context return parse(json.dumps(ser), allow_custom=True) except Exception as e: logger.error( f"Cannot parse transformed sighting context (expecting JSON): {transformed_context_raw}", e, )
def matcher_result_to_sighting(matcher_result: str) -> Union[Sighting, None]: """ Maps a sighting from the VAST Matcher format to a STIX-2 Sighting. @param matcher_result The raw sighting from VAST @return a valid STIX-2 Sighting that references the IoC from the VAST matcher or None """ if type(matcher_result) is not str: return None try: dct = json.loads(matcher_result) ts = dct.get("ts", None) if type(ts) is str: ts = dateutil_parser.parse(ts) except Exception: return None ref = dct.get("reference", "") context = dct.get("context", {}) ioc_value = dct.get("value", "") # ref = threatbus__indicator--46b3f973-5c03-41fc-9efe-49598a267a35 ref_len = len(threatbus_reference) + len("indicator--") + 36 if not ts or not ref or not len(ref) == ref_len: return None ref = ref[len(threatbus_reference) :] context["source"] = "VAST" return Sighting( last_seen=ts, sighting_of_ref=ref, custom_properties={ ThreatBusSTIX2Constants.X_THREATBUS_SIGHTING_CONTEXT.value: context, ThreatBusSTIX2Constants.X_THREATBUS_INDICATOR_VALUE.value: ioc_value, }, )
def query_result_to_sighting(query_result: str, indicator: Indicator) -> Union[Sighting, None]: """ Creates a STIX-2 Sighting from a VAST query result and the STIX-2 indicator that the query result refers to. @param query_result The VAST query result to convert @param indicator The STIX-2 Indicator that the query result refers to @return a valid STIX-2 Sighting that references the given indicator or None """ global logger if type(query_result) is not str or type(indicator) is not Indicator: return None try: context = json.loads(query_result) ts = context.get("ts", context.get("timestamp", None)) if not ts: logger.error(f"Could not find timestamp") return None ts = dateutil_parser.parse(ts) return Sighting( last_seen=ts, sighting_of_ref=indicator.id, custom_properties={ ThreatBusSTIX2Constants.X_THREATBUS_SIGHTING_CONTEXT.value: context, ThreatBusSTIX2Constants.X_THREATBUS_INDICATOR.value: indicator, ThreatBusSTIX2Constants.X_THREATBUS_MATCH_TYPE.value: MatchType.RETRO, }, ) except Exception as e: logger.error(f"Could not parse result {query_result} as sighting: {e}") return None
def query_result_to_sighting( query_result: str, indicator: Indicator ) -> Union[Sighting, None]: """ Creates a STIX-2 Sighting from a VAST query result and the STIX-2 indicator that the query result refers to. @param query_result The VAST query result to convert @param indicator The STIX-2 Indicator that the query result refers to @return a valid STIX-2 Sighting that references the given indicator or None """ if type(query_result) is not str or type(indicator) is not Indicator: return None try: context = json.loads(query_result) context["source"] = "VAST" ts = context.get("ts", context.get("timestamp", None)) if not ts: return None ts = dateutil_parser.parse(ts) return Sighting( last_seen=ts, sighting_of_ref=indicator.id, custom_properties={ ThreatBusSTIX2Constants.X_THREATBUS_SIGHTING_CONTEXT.value: context, ThreatBusSTIX2Constants.X_THREATBUS_INDICATOR.value: indicator, }, ) except Exception: return None
def test_invalid_indicator_to_vast_query(self): self.assertIsNone(indicator_to_vast_query(None)) self.assertIsNone(indicator_to_vast_query(42)) self.assertIsNone(indicator_to_vast_query(True)) self.assertIsNone(indicator_to_vast_query("hello")) self.assertIsNone( indicator_to_vast_query( Sighting(sighting_of_ref=self.indicator_id)))
def test_invalid_indicator_to_vast_matcher_ioc(self): self.assertIsNone(indicator_to_vast_matcher_ioc(None)) self.assertIsNone(indicator_to_vast_matcher_ioc(42)) self.assertIsNone(indicator_to_vast_matcher_ioc(True)) self.assertIsNone(indicator_to_vast_matcher_ioc("hello")) self.assertIsNone( indicator_to_vast_matcher_ioc( Sighting(sighting_of_ref=self.indicator_id)))
def test_zmq_app_plugin_message_roundtrip(self): """ Backend-agnostic message passing scenario. Sends a fixed amount of messages via the threatbus ZeroMQ app plugin, subscribes to Threat Bus, and checks if the initially sent messages can be retrieved back. """ result_q = queue.Queue() items = 2 topics = ["stix2/indicator", "stix2/sighting"] rec = threading.Thread(target=zmq_receiver.forward, args=(items, topics, result_q), daemon=False) rec.start() ioc = Indicator(pattern_type="stix", pattern="[ipv4-addr:value = '6.6.6.6']") zmq_sender.send( "stix2/indicator", ioc.serialize(), port=13372, bind=False, ) sighting = Sighting(sighting_of_ref=ioc.id) zmq_sender.send( "stix2/sighting", sighting.serialize(), port=13372, bind=False, ) time.sleep(1) self.assertEqual(result_q.qsize(), items) event = result_q.get(timeout=1) self.assertIsNotNone(event) self.assertEqual(parse(event), ioc) result_q.task_done() event = result_q.get(timeout=1) self.assertIsNotNone(event) self.assertEqual(parse(event), sighting) result_q.task_done() self.assertEqual(0, result_q.qsize()) result_q.join() rec.join(timeout=1)
def setUp(self): self.ts = datetime.now().astimezone() self.ioc_id = "indicator--42d31a5b-2da0-4bdd-9823-1723a98fc2fb" self.ioc_value = "example.com" self.ioc = Indicator( id=self.ioc_id, pattern_type="stix", pattern=f"[domain-name:value = '{self.ioc_value}']", ) self.sighting_context = { "ts": "2017-03-03T23:56:09.652643840", "uid": "CMeLkt11aTqwgN4FI9", "id.orig_h": "172.31.130.19", "id.orig_p": 43872, "id.resp_h": "172.31.129.17", "id.resp_p": 20004, "proto": "tcp", "service": None, "duration": 0.025249, "orig_bytes": 311, "resp_bytes": 999, "conn_state": "SF", "local_orig": None, "local_resp": None, "missed_bytes": 0, "history": "ShADadFf", "orig_pkts": 9, "orig_ip_bytes": 787, "resp_pkts": 7, "resp_ip_bytes": 1371, "tunnel_parents": [], "alert": { "signature": "VAST-RETRO Generic IoC match for: 172.31.129.17", "category": "Potentially Bad Traffic", "action": "allowed", }, "event_type": "alert", "_extra": { "vast-ioc": "172.31.129.17" }, "source": "VAST", } self.sighting = Sighting(sighting_of_ref=self.ioc_id) self.snapshot_id = "SNAPSHOT_UUID" self.snapshot = timedelta(days=42, hours=23, minutes=13, seconds=37) self.snapshot_request = SnapshotRequest(MessageType.SIGHTING, self.snapshot_id, self.snapshot) self.snapshot_envelope_indicator = SnapshotEnvelope( MessageType.INDICATOR, self.snapshot_id, self.ioc) self.snapshot_envelope_sighting = SnapshotEnvelope( MessageType.SIGHTING, self.snapshot_id, self.sighting)
def post_stix_store(owner, sighting_data, observed_data_input): client = MongoClient("localhost", 27018) db = client['stix'] stixCollection = db['stix'] now = datetime.utcnow() - timedelta(days=random.randint(0, 30)) search_observables = {} for key, value in observed_data_input.iteritems(): search_observables['stix.objects.0.' + key] = value observable_data = stixCollection.find_one({"$and": [search_observables]}) if (observable_data): observable_id = observable_data["_id"] observed_object = ObservedData( number_observed=observable_data["stix"]["number_observed"] + 1, id=observable_id, created_by_ref=observable_data["stix"]["created_by_ref"], first_observed=observable_data["stix"]["first_observed"], last_observed=now, objects={"0": observed_data_input}) observed_data = { '_id': observed_object.id, '_v': 0, 'stix': observed_object } observed_id = stixCollection.find_one_and_update( {'_id': observed_object.id}, {'$inc': { 'stix.count': 1 }}, {'stix.last_observed': now.strftime("%Y-%m-%dT%H:%M:%SZ")}) else: observed_object = ObservedData(number_observed=1, created_by_ref=owner, first_observed=now, last_observed=now, objects={"0": observed_data_input}) observed_data = { '_id': observed_object.id, '_v': 0, 'stix': observed_object } observed_id = stixCollection.insert_one(observed_data).inserted_id sighting_object = Sighting( count=1, first_seen=now, last_seen=now, sighting_of_ref=sighting_data['indicator_id'], observed_data_refs=[observed_object.id], where_sighted_refs=sighting_data['where_sighted_refs'], created_by_ref=owner, custom_properties={"x_unfetter_asset": sighting_data['asset']}) sighting = {'_id': sighting_object.id, '_v': 0, "stix": sighting_object} sighting_id = stixCollection.insert_one(sighting).inserted_id
def matcher_result_to_sighting(matcher_result: str) -> Union[Sighting, None]: """ Maps a sighting from the VAST Matcher format to a STIX-2 Sighting. @param matcher_result The raw sighting from VAST @return a valid STIX-2 Sighting that references the IoC from the VAST matcher or None """ global logger if type(matcher_result) is not str: return None try: dct = json.loads(matcher_result) event = dct["event"] ts = event.get("ts", event.get("timestamp", None)) if type(ts) is str: ts = dateutil_parser.parse(ts) except Exception as e: logger.error(f"exception: {e}") return None ref = dct["indicator"]["context"] ioc_value = dct["indicator"]["value"] # +36 for the uuid # +2 for the double quotes ref_len = len(THREATBUS_REFERENCE) + len("indicator--") + 36 + 2 if not ts: logger.error("Missing event timestamp in matcher result") return None if ref is not None: if len(ref) != ref_len: logger.error( f"Unexpected length: got {len(ref)}, expected {ref_len}") return None ref = ref[len(THREATBUS_REFERENCE) + 1:-1] if ref is None: # That's the minimal syntactically valid UUIDv4, see RFC 4122 / 4.1.1. ref = f"note--00000000-0000-4000-8000-000000000000" context = event return Sighting( last_seen=ts, sighting_of_ref=ref, custom_properties={ ThreatBusSTIX2Constants.X_THREATBUS_SIGHTING_CONTEXT.value: context, ThreatBusSTIX2Constants.X_THREATBUS_INDICATOR_VALUE.value: ioc_value, ThreatBusSTIX2Constants.X_THREATBUS_MATCH_TYPE.value: MatchType.LIVE, }, )
def test_invalid_indicator_inputs(self): self.assertIsNone( map_indicator_to_broker_event(None, None, self.logger)) self.assertIsNone(map_indicator_to_broker_event(None, "", self.logger)) self.assertIsNone( map_indicator_to_broker_event(None, self.module_namespace, self.logger)) self.assertIsNone( map_indicator_to_broker_event(42, self.module_namespace, self.logger)) self.assertIsNone( map_indicator_to_broker_event(object, self.module_namespace, self.logger)) self.assertIsNone( map_indicator_to_broker_event( Sighting(sighting_of_ref=self.indicator_id), self.module_namespace, self.logger, ))
def stix2_sighting_to_misp(sighting: Sighting): """ Maps the STIX-2 sighting format to a MISP sighting. @param sighting A STIX-2 Sighting object @return the mapped MISP sighting object or None """ if not sighting or type(sighting) != Sighting: return None misp_sighting = pymisp.MISPSighting() source = None if "x_threatbus_source" in sighting.object_properties(): source = str(sighting.x_threatbus_source) misp_sighting.from_dict( id=misp_id(sighting.sighting_of_ref), source=source, type="0", # true positive sighting: https://www.misp-standard.org/rfc/misp-standard-core.html#sighting timestamp=sighting.created, ) return misp_sighting
def stixer(vulns, path): for vuln in vulns: timestamp = datetime.now() vuln_name = vuln['name'] vuln_description = vuln['description'] if args.mitigations: mitigation_description = get_mitigation(vuln['name']) else: mitigation_description = "" snippet_android = vuln['code'] coa_name = vuln_name + "_coa" file_path = PurePath(vuln['file']) target = f"{str(file_path)}:{vuln['line']}" coa = CourseOfAction(type="course-of-action", name=coa_name, description=mitigation_description, x_actions=[{ "mitigation_android": "No snippet avaliable" }], allow_custom=True) vuln = Vulnerability(type="vulnerability", name=vuln_name, description=vuln_description, vulnerable_snippet=snippet_android, allow_custom=True) mitigates = Relationship(coa, 'mitigates', vuln) observed_object = File(name=target) observed_data = ObservedData(first_observed=timestamp, last_observed=timestamp, number_observed=1, objects={0: observed_object}) sight = Sighting(vuln, observed_data_refs=[observed_data]) bundle = Bundle(coa, mitigates, vuln, sight, observed_data, observed_object) with open(f"{path}/{vuln_name}.json", "w") as f: f.write(str(bundle) + "\n")
def map_bundle_to_sightings(indicator: Indicator, observations: List[Dict]): """ # Generate one STIX-2 Sighting per `observed-data` entry in the list of STIX objects @param indicator: the STIX-2 indicators that all observations refer to @param observations: a list of STIX-2 observations to map to Sightings @return iterator over Sighting objects """ for obj in observations: if obj.get("type", None) != "observed-data": continue last = dateutil_parser.parse( obj.get("last_observed", str(datetime.now()))) yield Sighting( last_seen=last, sighting_of_ref=indicator.id, custom_properties={ ThreatBusSTIX2Constants.X_THREATBUS_SIGHTING_CONTEXT.value: obj, ThreatBusSTIX2Constants.X_THREATBUS_INDICATOR.value: indicator, }, )
def post_stix_store(owner, analytic): client = MongoClient("cti-stix-store-repository", 27017) db = client['stix'] stixCollection = db['stix'] now = datetime.datetime.utcnow() print "********" print analytic.car_data sighting_object = Sighting( count=1, first_seen=now, last_seen=now, sighting_of_ref=analytic.car_data['indicator_id'], where_sighted_refs=[owner], created_by_ref=owner, ) sighting = { '_id': sighting_object.id, '_v': 0, "stix": sighting_object } sighting_id = stixCollection.insert_one(sighting).inserted_id
def map_broker_event_to_sighting(broker_data, module_namespace, logger): """ Maps a Broker message, based on the event name, to a STIX-2 indicator or STIX-2 Sighting. @param broker_data The raw data that was received via broker @param module_namespace A Zeek namespace to accept events from """ event = broker.zeek.Event(broker_data) name, args = event.name(), event.args() module_namespace = module_namespace + "::" if module_namespace else "" name = name[name.startswith(module_namespace) and len(module_namespace):] if name != "sighting" or len(args) != 3: if logger: logger.debug(f"Discarding Broker event with unknown type: {name}") return None # convert args to STIX-2 sighting (timestamp, ioc_id, context) = args return Sighting( sighting_of_ref=str(ioc_id), last_seen=timestamp, custom_properties={ ThreatBusSTIX2Constants.X_THREATBUS_SIGHTING_CONTEXT.value: context }, )
def setUp(self): self.created = datetime.now().astimezone() self.indicator_id = "indicator--df0f9a0e-c3b6-4f53-b0cf-5e9c454ee0cc" self.pattern = "[ipv4-addr:value = '6.6.6.6']" self.pattern_type = "stix2" self.operation = Operation.REMOVE self.indicator = Indicator( id=self.indicator_id, pattern=self.pattern, pattern_type=self.pattern_type, created=self.created, valid_from=self.created, modified=self.created, ) self.sighting_source = "VAST" self.sighting_id = "sighting--df0f9a0e-c3b6-4f53-b0cf-5e9c454ee0cc" self.sighting_context = { "ts": "2017-03-03T23:56:09.652643840", "uid": "CMeLkt11aTqwgN4FI9", "id.orig_h": "172.31.130.19", "id.orig_p": 43872, "id.resp_h": "172.31.129.17", "id.resp_p": 20004, "proto": "tcp", "service": None, "duration": 0.025249, "orig_bytes": 311, "resp_bytes": 999, "conn_state": "SF", "local_orig": None, "local_resp": None, "missed_bytes": 0, "history": "ShADadFf", "orig_pkts": 9, "orig_ip_bytes": 787, "resp_pkts": 7, "resp_ip_bytes": 1371, "tunnel_parents": [], "alert": { "signature": "VAST-RETRO Generic IoC match for: 172.31.129.17", "category": "Potentially Bad Traffic", "action": "allowed", }, "event_type": "alert", "_extra": { "vast-ioc": "172.31.129.17" }, } self.sighting = Sighting( id=self.sighting_id, created=self.created, modified=self.created, sighting_of_ref=self.indicator_id, custom_properties={ ThreatBusSTIX2Constants.X_THREATBUS_SIGHTING_CONTEXT.value: self.sighting_context, ThreatBusSTIX2Constants.X_THREATBUS_INDICATOR.value: self.indicator, ThreatBusSTIX2Constants.X_THREATBUS_SOURCE.value: self.sighting_source, }, ) self.snapshot_id = "SNAPSHOT_UUID" self.snapshot = timedelta(days=42, hours=23, minutes=13, seconds=37) self.snapshot_request = SnapshotRequest(MessageType.SIGHTING, self.snapshot_id, self.snapshot) self.snapshot_envelope_indicator = SnapshotEnvelope( MessageType.INDICATOR, self.snapshot_id, self.indicator) self.snapshot_envelope_sighting = SnapshotEnvelope( MessageType.SIGHTING, self.snapshot_id, self.sighting)
def generate_case_bundle(self, case): markings = [] if case["tlp"] == 0: markings.append(TLP_WHITE) if case["tlp"] == 1: markings.append(TLP_GREEN) if case["tlp"] == 2: markings.append(TLP_AMBER) if case["tlp"] == 3: markings.append(TLP_RED) if len(markings) == 0: markings.append(TLP_WHITE) bundle_objects = [] incident = StixXOpenCTIIncident( id=OpenCTIStix2Utils.generate_random_stix_id("x-opencti-incident"), name=case["title"], description=case["description"], first_seen=datetime.utcfromtimestamp( int(case["createdAt"]) / 1000).strftime("%Y-%m-%dT%H:%M:%SZ"), last_seen=datetime.utcfromtimestamp( int(case["updatedAt"]) / 1000).strftime("%Y-%m-%dT%H:%M:%SZ"), object_marking_refs=markings, labels=case["tags"] if "tags" in case else [], created_by_ref=self.identity["standard_id"], ) bundle_objects.append(incident) # Get observables observables = self.thehive_api.get_case_observables( case_id=case["id"]).json() for observable in observables: if observable["dataType"] == "hash": if len(observable["data"]) == 32: data_type = "file_md5" elif len(observable["data"]) == 40: data_type = "file_sha1" elif len(observable["data"]) == 64: data_type = "file_sha256" else: data_type = "unknown" else: data_type = observable["dataType"] observable_key = OBSERVABLES_MAPPING[data_type] if observable_key is not None: stix_observable = SimpleObservable( id=OpenCTIStix2Utils.generate_random_stix_id( "x-opencti-simple-observable"), key=observable_key, value=observable["data"], description=observable["message"], x_opencti_score=80 if observable["ioc"] else 50, object_marking_refs=markings, labels=observable["tags"] if "tags" in observable else [], created_by_ref=self.identity["standard_id"], x_opencti_create_indicator=observable["ioc"], ) stix_observable_relation = Relationship( id=OpenCTIStix2Utils.generate_random_stix_id( "relationship"), relationship_type="related-to", created_by_ref=self.identity["standard_id"], source_ref=stix_observable.id, target_ref=incident.id, object_marking_refs=markings, ) bundle_objects.append(stix_observable) bundle_objects.append(stix_observable_relation) if observable["sighted"]: fake_indicator_id = ( "indicator--c1034564-a9fb-429b-a1c1-c80116cc8e1e") stix_sighting = Sighting( id=OpenCTIStix2Utils.generate_random_stix_id( "sighting"), first_seen=datetime.utcfromtimestamp( int(observable["startDate"] / 1000)).strftime("%Y-%m-%dT%H:%M:%SZ"), last_seen=datetime.utcfromtimestamp( int(observable["startDate"] / 1000 + 3600)).strftime("%Y-%m-%dT%H:%M:%SZ"), where_sighted_refs=[self.identity["standard_id"]], sighting_of_ref=fake_indicator_id, custom_properties={ "x_opencti_sighting_of_ref": stix_observable.id }, ) bundle_objects.append(stix_sighting) bundle = Bundle(objects=bundle_objects).serialize() return bundle
import zmq import time from stix2 import Indicator, Sighting def send(topic, msg, host="127.0.0.1", port=50000, bind=True): """Sends a single, user specified message""" socket = zmq.Context().socket(zmq.PUB) if bind is True: socket.bind(f"tcp://{host}:{port}") time.sleep(0.5) else: socket.connect(f"tcp://{host}:{port}") time.sleep(0.5) # print(f"send string: {topic} {msg}") socket.send_string(f"{topic} {msg}") time.sleep(0.5) if __name__ == "__main__": indicator = Indicator( pattern="[domain-name:value = 'evil.com']", pattern_type="stix" ) sighting = Sighting( sighting_of_ref="indicator--629a6400-8817-4bcb-aee7-8c74fc57482c", custom_properties={"x_threatbus_source": "VAST"}, ) send("stix2/indicator", indicator.serialize(), port=13372, bind=False) send("stix2/sighting", sighting.serialize(), port=13372, bind=False)
def setUp(self): self.raw_ts = 1579104545 self.ts = datetime.fromtimestamp(self.raw_ts) self.domain_ioc = "example.com" self.ip_ioc = "example.com" self.pattern = f"[domain-name:value = '{self.domain_ioc}'] AND [ipv4-addr:value = '{self.ip_ioc}']" self.attr_uuid = "5e1f2787-fcfc-4718-a58a-00b4c0a82f06" self.indicator_id = f"indicator--{self.attr_uuid}" self.indicator = Indicator( id=self.indicator_id, created=self.ts, pattern_type="stix", pattern=self.pattern, ) self.sighting = Sighting(created=self.ts, sighting_of_ref=self.indicator_id) self.valid_misp_attribute = { "id": self.id, "event_id": "1", "object_id": "0", "object_relation": None, "category": "Network activity", "type": "domain|ip", "value1": self.domain_ioc, "value2": self.ip_ioc, "to_ids": True, "uuid": self.attr_uuid, "timestamp": str(self.raw_ts), "distribution": "5", "sharing_group_id": "0", "comment": "", "deleted": False, "disable_correlation": False, "value": f"{self.domain_ioc}|{self.ip_ioc}", "Sighting": [], "Tag": [], } self.valid_misp_msg = json.loads( """{ "Attribute": { "id": "1", "event_id": "1", "object_id": "0", "object_relation": null, "category": "Network activity", "type": "ip-src", "value1": "6.6.6.6", "value2": "", "to_ids": true, "uuid": "5f7b2284-bdd8-4ef4-b457-032ac0a82f02", "timestamp": "1601906335", "distribution": "5", "sharing_group_id": "0", "comment": "", "deleted": false, "disable_correlation": false, "first_seen": null, "last_seen": null, "value": "6.6.6.6", "Tag": [ { "id": "3", "name": "baz", "colour": "#05ff00", "exportable": true }, { "id": "1", "name": "foo", "colour": "#ff0000", "exportable": true } ], "Sighting": [] }, "Event": { "id": "1", "date": "2020-10-05", "info": "evil", "uuid": "5f7b2277-1ebc-4101-9152-032ac0a82f02", "published": false, "analysis": "0", "threat_level_id": "1", "org_id": "1", "orgc_id": "1", "distribution": "1", "sharing_group_id": "0", "Orgc": { "id": "1", "uuid": "5f7b21ea-52a4-49f0-87df-032ac0a82f02", "name": "ORGNAME" } }, "action": "edit" } """ )