From 21c6bcbb2ca6a96c7daf63d9cd531cb08d828986 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20=20Esteban=20L=C3=B3pez?= Date: Fri, 15 Dec 2023 10:45:16 +0100 Subject: [PATCH 1/3] Added vysion.py --- misp_modules/modules/expansion/vysion.json | 16 -- misp_modules/modules/expansion/vysion.py | 212 +++++++++++++++++++++ 2 files changed, 212 insertions(+), 16 deletions(-) delete mode 100644 misp_modules/modules/expansion/vysion.json create mode 100644 misp_modules/modules/expansion/vysion.py diff --git a/misp_modules/modules/expansion/vysion.json b/misp_modules/modules/expansion/vysion.json deleted file mode 100644 index 9f51ddf..0000000 --- a/misp_modules/modules/expansion/vysion.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "description": "Module to enrich the information by making use of the Vysion API.", - "logo": "vysion.png", - "requirements": [ - "Vysion python library", - "Vysion API Key" - ], - "input": "MISP Attribute which include: company(target-org), country, info.", - "output": "MISP objects containing title, link to our webapp and TOR, i2p or clearnet URLs.", - "references": [ - "https://vysion.ai/", - "https://developers.vysion.ai/", - "https://github.com/ByronLabs/vysion-cti/tree/main" - ], - "features": "This module gets correlated information from our dark web intelligence database. With this you will get several objects containing information related to, for example, an organization victim of a ransomware attack." -} \ No newline at end of file diff --git a/misp_modules/modules/expansion/vysion.py b/misp_modules/modules/expansion/vysion.py new file mode 100644 index 0000000..d584dc9 --- /dev/null +++ b/misp_modules/modules/expansion/vysion.py @@ -0,0 +1,212 @@ +import json +from pymisp import MISPAttribute, MISPEvent +from urllib.parse import urlparse + +import logging + +import vysion.client as vysion + +import vysion.dto as dto +from vysion.dto.util import MISPProcessor + +misperrors = {"error": "Error"} +mispattributes = { + "input": [ + "email", + "domain", + "hostname", + "url", + "text", + "btc", + "phone-number", + "target-org", + ], + "format": "misp_standard", +} + +# possible module-types: 'expansion', 'hover' or both +moduleinfo = { + "version": "1", + "author": "Byron Labs", + "description": "Enrich observables with the Vysion API", + "module-type": ["expansion"], +} + +# config fields that your code expects from the site admin +moduleconfig = [ + "apikey", + "event_limit", + "proxy_host", + "proxy_port", + "proxy_username", + "proxy_password", +] + +LOGGER = logging.getLogger("vysion") +LOGGER.setLevel(logging.INFO) +LOGGER.info("Starting Vysion") + +DEFAULT_RESULTS_LIMIT = 10 + + +def get_proxy_settings(config: dict) -> dict: + """Returns proxy settings in the requests format. + If no proxy settings are set, return None.""" + proxies = None + host = config.get("proxy_host") + port = config.get("proxy_port") + username = config.get("proxy_username") + password = config.get("proxy_password") + + if host: + if not port: + misperrors["error"] = ( + "The vysion_proxy_host config is set, " + "please also set the vysion_proxy_port." + ) + raise KeyError + parsed = urlparse(host) + if "http" in parsed.scheme: + scheme = "http" + else: + scheme = parsed.scheme + netloc = parsed.netloc + host = f"{netloc}:{port}" + + if username: + if not password: + misperrors["error"] = ( + "The vysion_proxy_username config is set, " + "please also set the vysion_proxy_password." + ) + raise KeyError + auth = f"{username}:{password}" + host = auth + "@" + host + + proxies = {"http": f"{scheme}://{host}", "https": f"{scheme}://{host}"} + return proxies + + +def parse_error(status_code: int) -> str: + + status_mapping = { + 500: "Vysion is blind.", + 400: "Incorrect request, please check the arguments.", + 403: "You don't have enough privileges to make the request.", + } + + if status_code in status_mapping: + return status_mapping[status_code] + + return "Vysion may not be accessible." + + +def handler(q=False): + + if q is False: + return False + + request = json.loads(q) + + if not request.get("config") or not request["config"].get("apikey"): + misperrors["error"] = "A Vysion api key is required for this module." + return misperrors + + if not request.get("attribute"): + return { + "error": f"{standard_error_message}, which should contain at least a type, a value and an uuid." + } + + if request["attribute"]["type"] not in mispattributes["input"]: + return {"error": "Unsupported attribute type."} + + # event_limit = request["config"].get("event_limit") + attribute = request["attribute"] + proxy_settings = get_proxy_settings(request.get("config")) + + try: + + client = vysion.Client( + api_key=request["config"]["apikey"], + headers={ + "x-tool": "MISPModuleVysionExpansion", + }, + proxy=proxy_settings["http"] if proxy_settings else None, + ) + + LOGGER.debug(attribute) + + misp_attribute = MISPAttribute() + misp_attribute.from_dict(**attribute) + + attribute_type = misp_attribute.type + attribute_value = misp_attribute.value + + # https://www.misp-project.org/datamodels/#types + + LOGGER.debug(attribute_type) + + result = None + + if attribute_type == "email": + result = client.find_email(attribute_value) + elif attribute_type == "domain": + result = client.search(attribute_value) + elif attribute_type == "url": + result = client.search( + attribute_value + ) # TODO result = client.find_url(attribute_value) + elif attribute_type == "text": + result = client.search(attribute_value) + elif attribute_type == "target-org": + result = client.search(attribute_value, exact=True) + elif attribute_type == "btc": + result = client.search(attribute_value) # TODO + elif attribute_type == "phone-number": + result = client.search(attribute_value) # TODO + + if result is None: + return {"results": {}} + elif isinstance(result, dto.VysionError): + LOGGER.error(str(result)) + return {"results": {}} + + p = MISPProcessor() + misp_event: MISPEvent = p.process(result, ref_attribute=misp_attribute) + + LOGGER.info("Vysion client initialized") + + LOGGER.info("Vysion result obtained") + + return { + "results": { + "Object": [ + json.loads(object.to_json()) for object in misp_event.objects + ], + "Attribute": [ + json.loads(attribute.to_json()) + for attribute in misp_event.attributes + ], + "Tag": [ + json.loads(tag.to_json()) + for tag in misp_event.tags + ] + } + } + + except vysion.APIError as ex: + + LOGGER.error("Error in Vysion") + LOGGER.error(ex) + + misperrors["error"] = ex.message + return misperrors + + +def introspection(): + return mispattributes + + +def version(): + moduleinfo["config"] = moduleconfig + return moduleinfo \ No newline at end of file From cad40047a70646a04352fd0ac918e08d9c903019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20=20Esteban=20L=C3=B3pez?= Date: Fri, 15 Dec 2023 10:54:16 +0100 Subject: [PATCH 2/3] Added vysion.py --- REQUIREMENTS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/REQUIREMENTS b/REQUIREMENTS index b3c1763..c9ce719 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -177,7 +177,7 @@ validators==0.14.0 vt-graph-api==2.2.0 vt-py==0.17.5 vulners==2.0.10 -vysion=1.0.8 ; python_version >= '3.7' +vysion==1.0.8 ; python_version >= '3.7' wand==0.6.11 websocket-client==1.5.1 ; python_version >= '3.7' websockets==11.0.3 ; python_version >= '3.7' From 4a265feef5e4fa2f430fe6b81ee0d41ee3844df7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20=20Esteban=20L=C3=B3pez?= Date: Wed, 20 Dec 2023 09:50:18 +0100 Subject: [PATCH 3/3] added 1.0.9 --- REQUIREMENTS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/REQUIREMENTS b/REQUIREMENTS index c9ce719..84f0430 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -177,7 +177,7 @@ validators==0.14.0 vt-graph-api==2.2.0 vt-py==0.17.5 vulners==2.0.10 -vysion==1.0.8 ; python_version >= '3.7' +vysion==1.0.9 wand==0.6.11 websocket-client==1.5.1 ; python_version >= '3.7' websockets==11.0.3 ; python_version >= '3.7'