Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added EclecticIQ Analyser #1256

Merged
merged 1 commit into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions analyzers/EclecticIQ/EclecticIQ_SearchObservable.json
Original file line number Diff line number Diff line change
@@ -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": []
}
14 changes: 14 additions & 0 deletions analyzers/EclecticIQ/README.md
Original file line number Diff line number Diff line change
@@ -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
Binary file added analyzers/EclecticIQ/assets/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
127 changes: 127 additions & 0 deletions analyzers/EclecticIQ/eclecticiq.py
Original file line number Diff line number Diff line change
@@ -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()
2 changes: 2 additions & 0 deletions analyzers/EclecticIQ/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
cortexutils
requests
40 changes: 40 additions & 0 deletions thehive-templates/EclecticIQ_SearchObservable_1_0/long.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<div class="panel panel-info" ng-if="!content.results">No Data</div>

<div class="panel panel-info" ng-repeat="res in content.results.entities">
<div class="panel-heading">{{res.type}} - {{res.title}}</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>ID:</dt>
<dd>
<a target="_blank" href="{{content.results.url}}/entity/{{res.id}}"
>{{res.type}}--{{res.id}}</a
>
</dd>
</dl>
<dl class="dl-horizontal">
<dt>Entity Type:</dt>
<dd>{{res.type}}</dd>
</dl>
<dl class="dl-horizontal">
<dt>Timestamp:</dt>
<dd>{{res.timestamp}}</dd>
</dl>
<dl class="dl-horizontal">
<dt>Source Name:</dt>
<dd>{{res.source_name}}</dd>
</dl>
<dl class="dl-horizontal" ng-if="res.tags.length > 0">
<dt>Tags:</dt>
<dd><ul><li ng-repeat="tag in ::res.tags | orderBy:'toString()'">{{tag}}</li></ul></dd>
</dl>
</div>
</div>


<!-- General error -->
<div class="panel panel-danger" ng-if="!success">
<div class="panel-heading">
<strong>{{(artifact.data || artifact.attachment.name) | fang}}</strong>
</div>
<div class="panel-body">{{content.errorMessage}}</div>
</div>
7 changes: 7 additions & 0 deletions thehive-templates/EclecticIQ_SearchObservable_1_0/short.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<span
class="label"
ng-repeat="t in content.taxonomies"
ng-class="{'info': 'label-info', 'safe': 'label-success', 'suspicious': 'label-warning', 'malicious':'label-danger'}[t.level]"
>
{{t.namespace}} {{t.predicate}} {{t.value}}
</span>