elkserver/docker/redelk-base/redelkinstalldata/scripts/modules/alarm_manual/module.py (89 lines of code) (raw):
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
Part of RedELK
This check queries for C2 messages that contain "REDELK_ALARM" and will send an alarm with the content of that line.
Only alarms when c2.log.type is: events or implant_input
Authors:
- Outflank B.V. / Marc Smeets (@MarcOverIp)
- Lorenzo Bernardi (@fastlorenzo)
"""
import logging
from modules.helpers import get_initial_alarm_result, get_value, raw_search
info = {
"version": 0.1,
"name": "Alarm manual module",
"description": 'This check queries c2.message items (output and event log) that contain "REDELK_ALARM" and alarms the content of that line',
"type": "redelk_alarm",
"submodule": "alarm_manual",
}
class Module:
"""Alarm manual module"""
def __init__(self):
self.logger = logging.getLogger(info["submodule"])
def run(self):
"""Run the alarm module"""
ret = get_initial_alarm_result()
ret["info"] = info
ret["fields"] = [
"@timestamp",
"c2.message",
"agent.name",
"c2.log.type",
"host.name",
"user.name",
"host.ip",
]
ret["groupby"] = ["@timestamp"]
alarmed_messages = self.get_alarmed_messages()
report = self.alarm_check(alarmed_messages)
ret["hits"]["hits"] = report
ret["hits"]["total"] = len(report)
self.logger.info(
"finished running module. result: %s hits", ret["hits"]["total"]
)
return ret
def get_alarmed_messages(self):
"""Returns all previous messages that have been alarmed already"""
es_query = {
"sort": [{"@timestamp": {"order": "desc"}}],
"query": {
"bool": {
"filter": [
{"range": {"@timestamp": {"gte": "now-1y"}}},
{"match": {"tags": info["submodule"]}},
]
}
},
}
res = raw_search(es_query, index="rtops-*")
if res is None:
alarmed_hits = []
else:
alarmed_hits = res["hits"]["hits"]
# Created a dict grouped by c2 message (from c2.message)
messages = {}
for alarmed_hit in alarmed_hits:
# pylint: disable=invalid-name
message = get_value("_source.c2.message", alarmed_hit)
if message in messages:
messages[message].append(alarmed_hit)
else:
messages[message] = [alarmed_hit]
return messages
def alarm_check(self, alarmed_messages):
"""This check queries for C2 messages (input of eventlog) that contain 'REDELK_ALARM'"""
es_query = {
"sort": [{"@timestamp": {"order": "asc"}}],
"query": {
"bool": {
"must": {
"query_string": {
"query": "(c2.message:*REDELK_ALARM*) AND (((c2.log.type:implant_input) AND (tags:enrich_*)) OR (c2.log.type:events))"
}
},
"must_not": [{"match": {"tags": info["submodule"]}}],
}
},
}
res = raw_search(es_query, index="rtops-*")
if res is None:
not_enriched_hits = []
else:
not_enriched_hits = res["hits"]["hits"]
# Created a dict grouped by c2 messages (from c2.message)
messages = {}
for not_enriched in not_enriched_hits:
# pylint: disable=invalid-name
message = get_value("_source.c2.message", not_enriched)
if message in messages:
messages[message].append(not_enriched)
else:
messages[message] = [not_enriched]
hits = []
# Now we check if the C2 messages have already been alarmed in the past timeframe defined in the config
# pylint: disable=invalid-name
for message, message_val in messages.items():
# Not alarmed yet, process it
if message not in alarmed_messages:
hits += message_val
# Return the array of new documents to be alarmed
return hits