From cbc6b7afb7e8c8bb9701c86f36a1e5d628d93bb7 Mon Sep 17 00:00:00 2001 From: deepanshu-eiq Date: Wed, 6 Mar 2024 16:51:58 +0530 Subject: [PATCH] Added EclecticIQ Analyser --- .../EclecticIQ_SearchObservable.json | 65 +++++++++ analyzers/EclecticIQ/README.md | 14 ++ analyzers/EclecticIQ/assets/logo.png | Bin 0 -> 4173 bytes analyzers/EclecticIQ/eclecticiq.py | 127 ++++++++++++++++++ analyzers/EclecticIQ/requirements.txt | 2 + .../EclecticIQ_SearchObservable_1_0/long.html | 40 ++++++ .../short.html | 7 + 7 files changed, 255 insertions(+) create mode 100644 analyzers/EclecticIQ/EclecticIQ_SearchObservable.json create mode 100644 analyzers/EclecticIQ/README.md create mode 100644 analyzers/EclecticIQ/assets/logo.png create mode 100755 analyzers/EclecticIQ/eclecticiq.py create mode 100644 analyzers/EclecticIQ/requirements.txt create mode 100644 thehive-templates/EclecticIQ_SearchObservable_1_0/long.html create mode 100644 thehive-templates/EclecticIQ_SearchObservable_1_0/short.html 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 0000000000000000000000000000000000000000..d9d4b5ebb9b25efb9a14c79138e18d5ff5c916e4 GIT binary patch literal 4173 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 M*D=(^b literal 0 HcmV?d00001 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 @@ +
No Data
+ +
+
{{res.type}} - {{res.title}}
+
+
+
ID:
+
+ {{res.type}}--{{res.id}} +
+
+
+
Entity Type:
+
{{res.type}}
+
+
+
Timestamp:
+
{{res.timestamp}}
+
+
+
Source Name:
+
{{res.source_name}}
+
+
+
Tags:
+
  • {{tag}}
+
+
+
+ + + +
+
+ {{(artifact.data || artifact.attachment.name) | fang}} +
+
{{content.errorMessage}}
+
diff --git a/thehive-templates/EclecticIQ_SearchObservable_1_0/short.html b/thehive-templates/EclecticIQ_SearchObservable_1_0/short.html new file mode 100644 index 000000000..599291f16 --- /dev/null +++ b/thehive-templates/EclecticIQ_SearchObservable_1_0/short.html @@ -0,0 +1,7 @@ + + {{t.namespace}} {{t.predicate}} {{t.value}} +