Skip to content

Commit 42c6c04

Browse files
author
Thomas Andrejak
committed
Add new Alerter: IDMEF with Prelude SIEM
IDMEF (RFC 4765) is intended to be a standard data format that automated intrusion detection systems can use to report alerts about events that they deem suspicious. Prelude SIEM is an OpenSource SIEM: https://www.prelude-siem.org
1 parent f4bad06 commit 42c6c04

File tree

9 files changed

+338
-2
lines changed

9 files changed

+338
-2
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ env:
55
- TOXENV=docs
66
- TOXENV=py36
77
install:
8+
- apt-get install python3.6-prelude
89
- pip install tox
910
- >
1011
if [[ -n "${ES_VERSION}" ]] ; then

Dockerfile-test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
FROM ubuntu:latest
22

33
RUN apt-get update && apt-get upgrade -y
4-
RUN apt-get -y install build-essential python3.6 python3.6-dev python3-pip libssl-dev git
4+
RUN apt-get -y install build-essential python3.6 python3.6-dev python3-pip libssl-dev git python3.6-prelude
55

66
WORKDIR /home/elastalert
77

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ Currently, we have built-in support for the following alert types:
5757
- Gitter
5858
- Line Notify
5959
- Zabbix
60+
- IDMEF
6061

6162
Additional rule types and alerts can be easily imported or written.
6263

docs/source/ruletypes.rst

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2243,3 +2243,36 @@ Required:
22432243
``zbx_sender_port``: The port where zabbix server is listenning.
22442244
``zbx_host``: This field setup the host in zabbix that receives the value sent by Elastalert.
22452245
``zbx_item``: This field setup the item in the host that receives the value sent by Elastalert.
2246+
2247+
2248+
IDMEF
2249+
~~~~~~~~~~~
2250+
2251+
IDMEF will send notification to a Prelude SIEM server (https://www.prelude-siem.org). With this alert, you will send all the notables or suspicious events to IDMEF standard format (RFC 4765: https://tools.ietf.org/html/rfc4765). Events are enriched to facilitate automation and correlation processes but also to provide as much information to the operator (contextualization alerts) to allow it to respond quickly and effectively.
2252+
2253+
Required:
2254+
2255+
``alert_fields``: Define how to fill an IDMEF message. This is a "key: value" list and all keys refer to an IDMEF class. Possible keys:
2256+
* classification: alert.classification.text,
2257+
* description: alert.assessment.impact.description,
2258+
* severity: alert.assessment.impact.severity,
2259+
* impact_type: alert.assessment.impact.type,
2260+
* target_address: alert.target.node.address.address,
2261+
* target_port: alert.target.service.port,
2262+
* target_process: alert.target.process.name,
2263+
* target_pid: alert.target.process.pid,
2264+
* src_address: alert.source.node.address.address,
2265+
* src_port: alert.source.service.port,
2266+
* user_category: alert.target(0).user.category,
2267+
* user_type: alert.target(0).user.user_id(0).type,
2268+
* user: alert.target(0).user.user_id(0).name
2269+
2270+
Example usage::
2271+
2272+
alert: IDMEFAlerter
2273+
2274+
alert_fields:
2275+
- classification: "Abnormally high quantity of logs"
2276+
- description: "The host {hostname} is generating an abnormally high quantity of logs ({spike_count} while {reference_count} were generated in the last time frame)"
2277+
- severity: "medium"
2278+
- impact_type: "other"

elastalert/alerts.py

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import json
55
import logging
66
import os
7+
import pkg_resources
78
import re
89
import subprocess
910
import sys
@@ -18,6 +19,7 @@
1819
from smtplib import SMTPAuthenticationError
1920
from smtplib import SMTPException
2021
from socket import error
22+
from prelude import ClientEasy, IDMEF
2123

2224
import boto3
2325
import requests
@@ -2182,3 +2184,144 @@ def get_info(self):
21822184
'type': 'hivealerter',
21832185
'hive_host': self.rule.get('hive_connection', {}).get('hive_host', '')
21842186
}
2187+
2188+
2189+
class IDMEFAlerter(Alerter):
2190+
"""Define the IDMEF alerter."""
2191+
2192+
REQUIRED = ["classification", "severity", "description"]
2193+
2194+
ALERT_CONFIG_OPTS = {
2195+
"classification": "alert.classification.text",
2196+
"description": "alert.assessment.impact.description",
2197+
"severity": "alert.assessment.impact.severity",
2198+
"impact_type": "alert.assessment.impact.type",
2199+
"target_address": "alert.target.node.address.address",
2200+
"target_port": "alert.target.service.port",
2201+
"target_process": "alert.target.process.name",
2202+
"target_pid": "alert.target.process.pid",
2203+
"src_address": "alert.source.node.address.address",
2204+
"src_port": "alert.source.service.port",
2205+
"user_category": "alert.target(0).user.category",
2206+
"user_type": "alert.target(0).user.user_id(0).type",
2207+
"user": "alert.target(0).user.user_id(0).name"
2208+
}
2209+
2210+
client = ClientEasy(
2211+
"prelude-ai",
2212+
ClientEasy.PERMISSION_IDMEF_WRITE,
2213+
"Prelude AI",
2214+
"Behavior Analyzer",
2215+
"CS GROUP",
2216+
pkg_resources.get_distribution('prelude-ai').version
2217+
)
2218+
2219+
try:
2220+
client.start()
2221+
except Exception as e:
2222+
logging.error("Error while trying to start Elastalert IDMEF Alerter: %s" % e)
2223+
sys.exit(1)
2224+
2225+
def __init__(self, rule):
2226+
# Check if all required options for alert creation are present
2227+
self.alerting = self._check_required(rule)
2228+
self.rule = rule
2229+
2230+
def _check_required(self, rule):
2231+
missing_fields = []
2232+
alert_fields = {}
2233+
2234+
try:
2235+
alert_fields = rule["alert_fields"]
2236+
except KeyError:
2237+
elastalert_logger.warning("Missing 'alert_fields' configuration for IDMEF alerter in the '%s' rule. "
2238+
"No alerts will be sent." % rule["name"])
2239+
return False
2240+
2241+
for field in self.REQUIRED:
2242+
if not alert_fields.get(field):
2243+
missing_fields.append(field)
2244+
2245+
if missing_fields:
2246+
elastalert_logger.warning("Required fields [%s] for IDMEF alerter are missing in the '%s' rule. No alerts"
2247+
"will be sent.", ', '.join(missing_fields), rule["name"])
2248+
2249+
return False
2250+
2251+
return True
2252+
2253+
def _add_additional_data(self, idmef, key, value_type, value):
2254+
idmef.set("alert.additional_data(>>).meaning", key)
2255+
idmef.set("alert.additional_data(-1).type", value_type)
2256+
idmef.set("alert.additional_data(-1).data", value)
2257+
2258+
def _add_idmef_path_value(self, idmef, match, opt_name, default=None):
2259+
if opt_name not in self.ALERT_CONFIG_OPTS:
2260+
return
2261+
2262+
alert_fields = self.rule["alert_fields"]
2263+
try:
2264+
m = {}
2265+
for k, v in match.items():
2266+
m[k.replace('.keyword', '')] = v
2267+
2268+
idmef.set(self.ALERT_CONFIG_OPTS[opt_name], alert_fields[opt_name].format(**m))
2269+
except (KeyError, RuntimeError):
2270+
if not default:
2271+
return
2272+
2273+
idmef.set(self.ALERT_CONFIG_OPTS[opt_name], default)
2274+
2275+
def _fill_additional_data(self, idmef, match):
2276+
if match.get("message"):
2277+
self._add_additional_data(idmef, "Original Log", "string", match["message"])
2278+
2279+
self._add_additional_data(idmef, "Rule ID", "string", self.rule["name"])
2280+
2281+
if self.rule.get("query_key"):
2282+
grouping_field = self.rule["query_key"]
2283+
if grouping_field in match:
2284+
self._add_additional_data(idmef, "Grouping key", "string", grouping_field)
2285+
self._add_additional_data(idmef, "Grouping value", "string", match[grouping_field])
2286+
2287+
def _fill_source(self, idmef, match):
2288+
self._add_idmef_path_value(idmef, match, "src_address")
2289+
self._add_idmef_path_value(idmef, match, "src_port")
2290+
2291+
def _fill_target(self, idmef, match):
2292+
for field in ["target_address", "target_process", "target_pid", "user", "user_category", "user_type"]:
2293+
self._add_idmef_path_value(idmef, match, field)
2294+
2295+
def _fill_impact_info(self, idmef, match):
2296+
self._add_idmef_path_value(idmef, match, "severity", default="low")
2297+
self._add_idmef_path_value(idmef, match, "impact_type")
2298+
self._add_idmef_path_value(idmef, match, "description", default=self.rule["alert_fields"]["description"])
2299+
2300+
def _fill_detect_time(self, idmef, match):
2301+
timestamp_field = self.rule["timestamp_field"]
2302+
2303+
detect_time = match[timestamp_field]
2304+
if detect_time:
2305+
idmef.set("alert.detect_time", detect_time)
2306+
2307+
def _fill_classification(self, idmef, match):
2308+
self._add_idmef_path_value(idmef, match, "classification", default=self.rule["alert_fields"]["classification"])
2309+
2310+
def alert(self, matches):
2311+
if not self.alerting:
2312+
return
2313+
2314+
for match in matches:
2315+
idmef = IDMEF()
2316+
2317+
self._fill_classification(idmef, match)
2318+
self._fill_detect_time(idmef, match)
2319+
self._fill_impact_info(idmef, match)
2320+
self._fill_source(idmef, match)
2321+
self._fill_target(idmef, match)
2322+
self._fill_additional_data(idmef, match)
2323+
2324+
self.client.sendIDMEF(idmef)
2325+
2326+
def get_info(self):
2327+
return {'type': 'IDMEF Alerter'}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Alert when a login event is detected for user "admin" never before seen IP
2+
# In this example, "login" logs contain which user has logged in from what IP
3+
4+
# (Optional)
5+
# Elasticsearch host
6+
# es_host: elasticsearch.example.com
7+
8+
# (Optional)
9+
# Elasticsearch port
10+
# es_port: 14900
11+
12+
# (OptionaL) Connect with SSL to Elasticsearch
13+
#use_ssl: True
14+
15+
# (Optional) basic-auth username and password for Elasticsearch
16+
#es_username: someusername
17+
#es_password: somepassword
18+
19+
# (Required)
20+
# Rule name, must be unique
21+
name: Example new term rule
22+
23+
# (Required)
24+
# Type of alert.
25+
# the frequency rule type alerts when num_events events occur with timeframe time
26+
type: new_term
27+
28+
# (Required)
29+
# Index to search, wildcard supported
30+
index: logstash-*
31+
32+
# (Required, new_term specific)
33+
# Monitor the field ip_address
34+
fields:
35+
- "ip_address"
36+
37+
# (Optional, new_term specific)
38+
# This means that we will query 90 days worth of data when ElastAlert starts to find which values of ip_address already exist
39+
# If they existed in the last 90 days, no alerts will be triggered for them when they appear
40+
terms_window_size:
41+
days: 90
42+
43+
# (Required)
44+
# A list of Elasticsearch filters used for find events
45+
# These filters are joined with AND and nested in a filtered query
46+
# For more info: http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl.html
47+
# We are filtering for only "login_event" type documents with username "admin"
48+
filter:
49+
- term:
50+
_type: "login_event"
51+
- term:
52+
username: admin
53+
54+
# (Required)
55+
# The alert is use when a match is found
56+
alert:
57+
- "IDMEFAlerter"
58+
59+
# (required, IDMEF specific)
60+
# a list of IDMEF paths to format the alert
61+
alert_fields:
62+
- src_address: "{client}"
63+
- src_port: "{port}"
64+
- target_address: "{remotehost}"
65+
- target_process: "{process}"
66+
- target_pid: "{pid}"
67+
- user: "{user}"
68+
- user_category: "os-device"
69+
- user_type: "target-user"
70+
- classification: "Unusual device behavior"
71+
- description: "Unusual behavior from {client} to {remotehost}."
72+
- severity: "medium"
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Alert when there is a sudden spike in the volume of events
2+
3+
# (Optional)
4+
# Elasticsearch host
5+
# es_host: elasticsearch.example.com
6+
7+
# (Optional)
8+
# Elasticsearch port
9+
# es_port: 14900
10+
11+
# (Optional) Connect with SSL to Elasticsearch
12+
#use_ssl: True
13+
14+
# (Optional) basic-auth username and password for Elasticsearch
15+
#es_username: someusername
16+
#es_password: somepassword
17+
18+
# (Required)
19+
# Rule name, must be unique
20+
name: Event spike
21+
22+
# (Required)
23+
# Type of alert.
24+
# the spike rule type compares the number of events within two sliding windows to each other
25+
type: spike
26+
27+
# (Required)
28+
# Index to search, wildcard supported
29+
index: logstash-*
30+
31+
# (Required one of _cur or _ref, spike specific)
32+
# The minimum number of events that will trigger an alert
33+
# For example, if there are only 2 events between 12:00 and 2:00, and 20 between 2:00 and 4:00
34+
# _ref is 2 and _cur is 20, and the alert WILL fire because 20 is greater than threshold_cur and (_ref * spike_height)
35+
threshold_cur: 5
36+
#threshold_ref: 5
37+
38+
# (Required, spike specific)
39+
# The size of the window used to determine average event frequency
40+
# We use two sliding windows each of size timeframe
41+
# To measure the 'reference' rate and the current rate
42+
timeframe:
43+
hours: 2
44+
45+
# (Required, spike specific)
46+
# The spike rule matches when the current window contains spike_height times more
47+
# events than the reference window
48+
spike_height: 3
49+
50+
# (Required, spike specific)
51+
# The direction of the spike
52+
# 'up' matches only spikes, 'down' matches only troughs
53+
# 'both' matches both spikes and troughs
54+
spike_type: "up"
55+
56+
# (Required)
57+
# A list of Elasticsearch filters used for find events
58+
# These filters are joined with AND and nested in a filtered query
59+
# For more info: http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl.html
60+
filter:
61+
- query:
62+
query_string:
63+
query: "field: value"
64+
- type:
65+
value: "some_doc_type"
66+
67+
# (Required)
68+
# The alert is use when a match is found
69+
alert:
70+
- "IDMEFAlerter"
71+
72+
# (required, IDMEF specific)
73+
# a list of IDMEF paths to format the alert
74+
alert_fields:
75+
- classification: "Abnormally high quantity of logs"
76+
- description: "The host {hostname} is generating an abnormally high quantity of logs ({spike_count} while {reference_count} were generated in the last time frame)"
77+
- severity: "medium"
78+
- impact_type: "other"
79+
80+
# This option only keep count in memory
81+
use_terms_query: true
82+
83+
# Force doc_type needed for use_terms_query option
84+
doc_type: "events"

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ requests>=2.0.0
2121
stomp.py>=4.1.17
2222
texttable>=0.8.8
2323
twilio==6.0.0
24+
prelude>=5.0

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
'texttable>=0.8.8',
4949
'twilio>=6.0.0,<6.1',
5050
'python-magic>=0.4.15',
51-
'cffi>=1.11.5'
51+
'cffi>=1.11.5',
52+
'prelude>=5.0'
5253
]
5354
)

0 commit comments

Comments
 (0)