diff --git a/analyzers/EclecticIQ/EclecticIQ_SearchObservable.json b/analyzers/EclecticIQ/EclecticIQ_SearchObservable.json new file mode 100644 index 000000000..e8b260312 --- /dev/null +++ b/analyzers/EclecticIQ/EclecticIQ_SearchObservable.json @@ -0,0 +1,65 @@ +{ + "name": "EclecticIQ_SearchObservable", + "author": "BW", + "license": "AGPL-V3", + "url": "https://github.com/TheHive-Project/Cortex-Analyzers/", + "version": "2.0", + "description": "Query EclecticIQ Intelligence Center for a specific observable.", + "dataTypeList": [ + "domain", + "ip", + "url", + "fqdn", + "uri_path", + "user-agent", + "hash", + "mail", + "mail_subject", + "registry", + "regexp", + "other", + "filename" + ], + "config": { + "service": "search_observable" + }, + "baseConfig": "EclecticIQ", + "command": "EclecticIQ/eclecticiq.py", + "configurationItems": [ + { + "name": "name", + "description": "Name of EclecticIQ instance", + "multi": false, + "required": false, + "type": "string" + }, + { + "name": "url", + "description": "URL of EclecticIQ instance", + "type": "string", + "multi": false, + "required": true + }, + { + "name": "key", + "description": "API key for EclecticIQ instance", + "type": "string", + "multi": false, + "required": true + }, + { + "name": "cert_check", + "description": "Verify server certificate", + "type": "boolean", + "multi": false, + "required": true, + "defaultValue": true + } + ], + "registration_required": true, + "subscription_required": true, + "free_subscription": false, + "service_homepage": "https://www.eclecticiq.com", + "service_logo": { "path": "assets/logo.png", "caption": "logo" }, + "screenshots": [] +} diff --git a/analyzers/EclecticIQ/README.md b/analyzers/EclecticIQ/README.md new file mode 100644 index 000000000..407eade0a --- /dev/null +++ b/analyzers/EclecticIQ/README.md @@ -0,0 +1,14 @@ +[EclecticIQ](https://www.eclecticiq.com/) is a cyber threat intelligence platform which provides aggregation and analysis capabilities for threat intelligence data and integration with organization assets. + +The analyzer comes in one flavor to look for an observable in the platform and return any parent entities and their context. + +- EclecticIQ\_**SearchObservable**: returns entity data for a specific observable + +#### Requirements + +The EclecticIQ analyzer requires you to have access to an [EclecticIQ Intelligence Center](https://www.eclecticiq.com/) instance. + +Three parameters are required for each instance to make the analyzer work: + +- `url` : URL of the instance, e.g. "https://intel-platform.local" +- `key` : API Key for a user of the EclecticIQ Intelligence Center instance diff --git a/analyzers/EclecticIQ/assets/logo.png b/analyzers/EclecticIQ/assets/logo.png new file mode 100644 index 000000000..d9d4b5ebb Binary files /dev/null and b/analyzers/EclecticIQ/assets/logo.png differ diff --git a/analyzers/EclecticIQ/eclecticiq.py b/analyzers/EclecticIQ/eclecticiq.py new file mode 100755 index 000000000..85b993663 --- /dev/null +++ b/analyzers/EclecticIQ/eclecticiq.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +import typing as tp + +import requests + +from cortexutils.analyzer import Analyzer + + +class EclecticIQAnalyzer(Analyzer): + """Searches for given Observables in configured EclecticIQ instance. + All standard Cortex data types are supported.""" + + def __init__(self): + Analyzer.__init__(self) + + self.service = self.get_param("config.service", default="search_observable") + + self.name = self.get_param( + "config.name", message="No EclecticIQ instance name given." + ) + self.url = self.get_param("config.url", message="No EclecticIQ url given.") + self.key = self.get_param("config.key", message="No EclecticIQ api key given.") + self.data = self.get_param("data", message="Data is missing") + + if self.get_param("config.cert_check", True): + self.ssl = self.get_param("config.cert_path", True) + else: + self.ssl = False + + self.session = requests.Session() + self.session.verify = self.ssl + self.session.proxies = self.get_param("config.proxy") + self.session.headers.update( + {"Accept": "application/json", "Authorization": f"Bearer {self.key}"} + ) + + def summary(self, raw): + level = "info" + namespace = "EIQ" + predicate = "API" + found = len(raw["results"].get("entities", [])) + value = f"Found {found} entities" if found > 0 else "Not found" + taxonomy = self.build_taxonomy(level, namespace, predicate, value) + return {"taxonomies": [taxonomy]} + + def get_source(self, url): + response = self.session.get(url) + return response.json()["data"]["name"] + + @staticmethod + def get_confidence(data): + confidence = data.get("confidence", None) + if isinstance(confidence, dict): + confidence = confidence.get("value") + return confidence + + def run(self): + """ + Query EclecticIQ instance for data by querying observable for + observable id and then querying entities endpoint for parent entities + + Return dict response to cortex + """ + + results = { + "name": self.name, + "url": self.url, + "obs_value": self.data, + } + obs_id = self.add_observable_info(results) + if not obs_id: + # exit early for no data + return self.report({}) + + entities_info = self.get_entities_info(obs_id) + if not entities_info: + # exit early for no data + return self.report({}) + + results["count"] = entities_info["count"] + results["entities"] = [] + for entity in entities_info["data"]: + source_name = self.get_source(entity["sources"][0]) + entity_data = entity.get("data", {}) + results["entities"].append( + { + "id": entity["id"], + "title": entity_data.get("title"), + "type": entity_data.get("type"), + "confidence": self.get_confidence(entity_data), + "tags": entity.get("meta", {}).get("tags"), + "timestamp": entity.get("meta", {}).get( + "estimated_threat_start_time" + ), + "source_name": source_name, + } + ) + + self.report({"results": results}) + + def add_observable_info(self, results: dict) -> tp.Optional[str]: + url = self.url + "/api/v2/observables" # set observable url + params = {"filter[value]": self.data} # use data in filter param + response = self.session.get(url, params=params) + if not response.json().get("count"): + return None + + data = response.json()["data"] + results["obs_type"] = data[0]["type"] + results["obs_score"] = data[0].get("meta", {}).get("maliciousness") + return data[0]["id"] + + def get_entities_info(self, obs_id: str) -> tp.Optional[dict]: + url = self.url + "/api/v2/entities" # set entity url + params = {"filter[observables]": obs_id} # use observable id in filter param + + response = self.session.get(url, params=params) + response_json = response.json() + + if not response_json.get("count"): + return None + + return response_json + + +if __name__ == "__main__": + EclecticIQAnalyzer().run() diff --git a/analyzers/EclecticIQ/requirements.txt b/analyzers/EclecticIQ/requirements.txt new file mode 100644 index 000000000..4a21dbf63 --- /dev/null +++ b/analyzers/EclecticIQ/requirements.txt @@ -0,0 +1,2 @@ +cortexutils +requests \ No newline at end of file diff --git a/thehive-templates/EclecticIQ_SearchObservable_1_0/long.html b/thehive-templates/EclecticIQ_SearchObservable_1_0/long.html new file mode 100644 index 000000000..2fd9cb7d3 --- /dev/null +++ b/thehive-templates/EclecticIQ_SearchObservable_1_0/long.html @@ -0,0 +1,40 @@ +