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 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)
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)