|
4 | 4 | import json |
5 | 5 | import logging |
6 | 6 | import os |
| 7 | +import pkg_resources |
7 | 8 | import re |
8 | 9 | import subprocess |
9 | 10 | import sys |
|
18 | 19 | from smtplib import SMTPAuthenticationError |
19 | 20 | from smtplib import SMTPException |
20 | 21 | from socket import error |
| 22 | +from prelude import ClientEasy, IDMEF |
21 | 23 |
|
22 | 24 | import boto3 |
23 | 25 | import requests |
@@ -2182,3 +2184,144 @@ def get_info(self): |
2182 | 2184 | 'type': 'hivealerter', |
2183 | 2185 | 'hive_host': self.rule.get('hive_connection', {}).get('hive_host', '') |
2184 | 2186 | } |
| 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'} |
0 commit comments