From 6a4ae2dfe964b28c134ec6ea5d851a6e0b2fa474 Mon Sep 17 00:00:00 2001 From: Quentin Long Date: Wed, 15 Apr 2020 14:17:19 -0700 Subject: [PATCH] Re-add TheHive alerter without any libraries --- docs/source/elastalert.rst | 1 + docs/source/ruletypes.rst | 45 ++++++++++++++++++++++++ elastalert/alerts.py | 70 ++++++++++++++++++++++++++++++++++++++ elastalert/loaders.py | 1 + 4 files changed, 117 insertions(+) diff --git a/docs/source/elastalert.rst b/docs/source/elastalert.rst index c29f80f23..b1008c3c4 100755 --- a/docs/source/elastalert.rst +++ b/docs/source/elastalert.rst @@ -42,6 +42,7 @@ Currently, we have support built in for these alert types: - GoogleChat - Debug - Stomp +- TheHive Additional rule types and alerts can be easily imported or written. (See :ref:`Writing rule types ` and :ref:`Writing alerts `) diff --git a/docs/source/ruletypes.rst b/docs/source/ruletypes.rst index 790c85919..ea415f0f2 100644 --- a/docs/source/ruletypes.rst +++ b/docs/source/ruletypes.rst @@ -2186,6 +2186,51 @@ Required: ``linenotify_access_token``: The access token that you got from https://notify-bot.line.me/my/ +theHive +~~~~~~ + +theHive alert type will send JSON request to theHive (Security Incident Response Platform) with TheHive4py API. Sent request will be stored like Hive Alert with description and observables. + +Required: + +``hive_connection``: The connection details as key:values. Required keys are ``hive_host``, ``hive_port`` and ``hive_apikey``. + +``hive_alert_config``: Configuration options for the alert. + +Optional: + +``hive_proxies``: Proxy configuration. + +``hive_observable_data_mapping``: If needed, matched data fields can be mapped to TheHive observable types using python string formatting. + +Example usage:: + + alert: hivealerter + + hive_connection: + hive_host: http://localhost + hive_port: + hive_apikey: + hive_proxies: + http: '' + https: '' + + hive_alert_config: + title: 'Title' ## This will default to {rule[index]_rule[name]} if not provided + type: 'external' + source: 'elastalert' + description: '{match[field1]} {rule[name]} Sample description' + severity: 2 + tags: ['tag1', 'tag2 {rule[name]}'] + tlp: 3 + status: 'New' + follow: True + + hive_observable_data_mapping: + - domain: "{match[field1]}_{rule[name]}" + - domain: "{match[field]}" + - ip: "{match[ip_field]}" + Zabbix ~~~~~~~~~~~ diff --git a/elastalert/alerts.py b/elastalert/alerts.py index f8ea9af34..8fa59542e 100644 --- a/elastalert/alerts.py +++ b/elastalert/alerts.py @@ -4,6 +4,7 @@ import json import logging import os +import re import subprocess import sys import time @@ -2104,3 +2105,72 @@ def alert(self, matches): def get_info(self): return {"type": "linenotify", "linenotify_access_token": self.linenotify_access_token} + + +class HiveAlerter(Alerter): + """ + Use matched data to create alerts containing observables in an instance of TheHive + """ + + required_options = set(['hive_connection', 'hive_alert_config']) + + def alert(self, matches): + + connection_details = self.rule['hive_connection'] + + for match in matches: + context = {'rule': self.rule, 'match': match} + + artifacts = [] + for mapping in self.rule.get('hive_observable_data_mapping', []): + for observable_type, match_data_key in mapping.items(): + try: + match_data_keys = re.findall(r'\{match\[([^\]]*)\]', match_data_key) + rule_data_keys = re.findall(r'\{rule\[([^\]]*)\]', match_data_key) + data_keys = match_data_keys + rule_data_keys + context_keys = list(context['match'].keys()) + list(context['rule'].keys()) + if all([True if k in context_keys else False for k in data_keys]): + artifact = {'tlp': 2, 'tags': [], 'message': None, 'dataType': observable_type, + 'data': match_data_key.format(**context)} + artifacts.append(artifact) + except KeyError: + raise KeyError('\nformat string\n{}\nmatch data\n{}'.format(match_data_key, context)) + + alert_config = { + 'artifacts': artifacts, + 'sourceRef': str(uuid.uuid4())[0:6], + 'customFields': {}, + 'caseTemplate': None, + 'title': '{rule[index]}_{rule[name]}'.format(**context), + 'date': int(time.time()) * 1000 + } + alert_config.update(self.rule.get('hive_alert_config', {})) + + for alert_config_field, alert_config_value in alert_config.items(): + if isinstance(alert_config_value, str): + alert_config[alert_config_field] = alert_config_value.format(**context) + elif isinstance(alert_config_value, (list, tuple)): + formatted_list = [] + for element in alert_config_value: + try: + formatted_list.append(element.format(**context)) + except (AttributeError, KeyError, IndexError): + formatted_list.append(element) + alert_config[alert_config_field] = formatted_list + + alert_body = json.dumps(alert_config, indent=4, sort_keys=True) + req = '{}:{}/api/alert'.format(connection_details['hive_host'], connection_details['hive_port']) + headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer {}'.format(connection_details.get('hive_apikey', ''))} + proxies = connection_details.get('hive_proxies', {'http': '', 'https': ''}) + verify = connection_details.get('hive_verify', False) + response = requests.post(req, headers=headers, data=alert_body, proxies=proxies, verify=verify) + + if response.status_code != 201: + raise Exception('alert not successfully created in TheHive\n{}'.format(response.text)) + + def get_info(self): + + return { + 'type': 'hivealerter', + 'hive_host': self.rule.get('hive_connection', {}).get('hive_host', '') + } diff --git a/elastalert/loaders.py b/elastalert/loaders.py index f15e5f2a2..771194768 100644 --- a/elastalert/loaders.py +++ b/elastalert/loaders.py @@ -77,6 +77,7 @@ class RulesLoader(object): 'servicenow': alerts.ServiceNowAlerter, 'alerta': alerts.AlertaAlerter, 'post': alerts.HTTPPostAlerter, + 'hivealerter': alerts.HiveAlerter } # A partial ordering of alert types. Relative order will be preserved in the resulting alerts list