From fd9c583b84bc06429d52e0eecd458f2c588bd7d7 Mon Sep 17 00:00:00 2001 From: Rudi Klein Date: Tue, 7 May 2024 17:08:03 +0200 Subject: [PATCH 01/33] docs --- README.md | 126 -------------- Writerside/topics/Wazuh-notifier.md | 3 - wazuh-active-response.py | 5 +- wazuh-discord-notifier.py | 104 ++++-------- wazuh-notifier-config.yaml | 16 +- wazuh-ntfy-notifier.py | 94 ++++------ wazuh_notifier_lib.py | 69 -------- wazuh_notifier_module.py | 254 ++++++++++++++++++++++++++++ 8 files changed, 330 insertions(+), 341 deletions(-) delete mode 100644 README.md delete mode 100755 wazuh_notifier_lib.py create mode 100755 wazuh_notifier_module.py diff --git a/README.md b/README.md deleted file mode 100644 index 3287897..0000000 --- a/README.md +++ /dev/null @@ -1,126 +0,0 @@ -# Wazuh notifier - -Wazuh notifier enables the Wazuh manager to be notified when selected events occur. - -## Contents - -The main script is a custom active response Python script: wazuh-active-response.py.
-The actual sending of the messages is done by 2 notifier Python scripts:
-**Discord notifier**: wazuh-discord-notifier.py, and **NTFY.sh notifier**: wazuh-ntfy-notifier.py
-A YAML configuration: wazuh-notifier-config.yaml, and a Python module: wazuh_notifier_lib.py - -Wazuh notifier is a stateless implementation and only notifies, using the Discord and/or NTFY.sh messaging services. - -The Wazuh notifier is triggered by configuring the **ossec.conf** and adding an **active response configuration.** - -## Installation ## - -### Step 1 ### - -Download the files from https://github.com/RudiKlein/wazuh-notifier to your server. - -### Step 2 ### - -Copy the 4 Python files to the /var/ossec/active-response/bin/ folder - -``` -$ cp /wazuh-*.py /var/ossec/active-response/bin/ -``` - -Set the correct ownership - -``` -$ chown root:wazuh /var/ossec/active-response/bin/wazuh-*.py -``` - -Set the correct permissions - -``` -$ chmod uog+rx /var/ossec/active-response/bin/wazuh-*.py -``` - -### Step 3 ### - -Copy the YAML file to /var/ossec/etc/ - -``` -$ cp /wazuh-notifier-config.yaml /var/ossec/etc/ -``` - -Set the correct ownership - -``` -$ chown root:wazuh /var/ossec/etc/wazuh-notifier-config.yaml -``` - -Set the correct permissions - -``` -$ chmod uog+r /var/ossec/etc/wazuh-notifier-config.yaml -``` - -### Step 4 ### - -Modify the ossec.conf configuration file and add the following
- -``` - - wazuh-active-response - wazuh-active-response.py - yes - -``` - -``` - - wazuh-active-response - server - - - -``` - -Add the rules you want to be informed about between the , with the rules id's seperated by comma's. -Example: 5402, 3461, 8777
-(Please refer to the Wazuh online documentation for more information [^Wazuh docs]) - -[^Wazuh docs]: https://documentation.wazuh.com/current/user-manual/capabilities/active-response/index.html - -## The Active Response module ## - -The wazuh-active-response.py acts as the interface between Wazuh and the messaging notifiers for Discord and ntfy. -It is based on the example active response Python script in the [^Wazuh docs]. - -## The Discord notifier ## - -## The ntfy.sh notifier ## - -## The YAML configuration ## - -**Enable/disable the notifiers**
- -``` -discord_enabled: 1 (0 if not set in the yaml configuration) -ntfy_enabled: 1 (0 if not set in the yaml configuration) -``` - -**Exclude rules that are enabled in the ossec.conf active response definition.**
-This prevents the need to alter the ossec.conf for temporary rule disabling and stopping/starting wazuh-manager. -Additionally, agents can also be excluded from notifications. - -``` -excluded_rules: "5401, 5402, 5403" -excluded_agents: "999" -``` - -Default settings for the ntfy notifier. This overrules the hardcoded defaults. - -``` -ntfy_server: "https://ntfy.sh/" -ntfy_sender: "Wazuh (IDS)" -ntfy_destination: "__KleinTest" -ntfy_priority: "5" -ntfy_message: "Test message" -ntfy_tags: "information, testing, yaml" -ntfy_click: "https://google.com" -``` \ No newline at end of file diff --git a/Writerside/topics/Wazuh-notifier.md b/Writerside/topics/Wazuh-notifier.md index c8789cc..59a439b 100644 --- a/Writerside/topics/Wazuh-notifier.md +++ b/Writerside/topics/Wazuh-notifier.md @@ -140,6 +140,3 @@ discord_click: "https://google.com" discord_full_message: "0" ``` -test - -![wazuh discord basic message](wazuh-discord-basic-message.png) \ No newline at end of file diff --git a/wazuh-active-response.py b/wazuh-active-response.py index cf9db3c..0a1a615 100755 --- a/wazuh-active-response.py +++ b/wazuh-active-response.py @@ -24,8 +24,8 @@ import os import sys from pathlib import PureWindowsPath, PurePosixPath -from wazuh_notifier_lib import import_config as ic -from wazuh_notifier_lib import set_env as se +from wazuh_notifier_module import import_config as ic +from wazuh_notifier_module import set_environment as se # Some variable assignments @@ -211,7 +211,6 @@ def main(argv): discord_message = construct_basic_message(argv, accent, agent_id, agent_name, event_id, event_description, event_level, event_fired_times) - if ic("discord_full_message") == "1": discord_message = discord_message + "\n" + accent + "__Full event__" + accent + event_full_message + '"' else: diff --git a/wazuh-discord-notifier.py b/wazuh-discord-notifier.py index b96b798..57758c8 100755 --- a/wazuh-discord-notifier.py +++ b/wazuh-discord-notifier.py @@ -16,22 +16,23 @@ # with their friends and communities. It allows for receiving message using webhooks. # For more information: https://discord.com. -import getopt import os -import sys from os.path import join, dirname import requests from dotenv import load_dotenv -from wazuh_notifier_lib import import_config as ic -from wazuh_notifier_lib import set_env as se -from wazuh_notifier_lib import set_time as st -from wazuh_notifier_lib import view_config as vc +from wazuh_notifier_module import get_arguments as ga +from wazuh_notifier_module import get_yaml_config as yc +from wazuh_notifier_module import set_basic_defaults as bd +from wazuh_notifier_module import set_environment as se +from wazuh_notifier_module import set_time as st +from wazuh_notifier_module import threat_priority_mapping as tpm # Get path values wazuh_path, ar_path, config_path = se() + # Get time value now_message, now_logging = st() @@ -62,11 +63,11 @@ def discord_command(n_server, n_sender, n_destination, n_priority, n_message, n_ ) n_data = {"username": n_sender, "embeds": [{"description": x_message, "title": n_destination}]} - result = requests.post(n_server, json=n_data) + requests.post(n_server, json=n_data) # Remove 1st argument from the list of command line arguments -argument_list: list = sys.argv[1:] +# argument_list: list = sys.argv[1:] # Short options options: str = "u:s:p:m:t:c:hv" @@ -74,78 +75,39 @@ options: str = "u:s:p:m:t:c:hv" # Long options long_options: list = ["server=", "sender=", "destination=", "priority=", "message=", "tags=", "click=", "help", "view"] -# Setting some basic defaults. -d_sender: str = "Security message" -d_destination: str = "WAZUH (IDS)" -d_priority: str = "5" -d_message: str = "Test message" -d_tags: str = "informational, testing, hard-code" -d_click: str = "https://google.com" +# Defining who I am +notifier = "discord" -# Use the values from the config yaml if available. Overrides the basic defaults. -server = discord_webhook -sender = d_sender if (ic("discord_sender") is None) else ic("discord_sender") -destination = d_destination if (ic("discord_destination") is None) else ic("discord_destination") -priority = d_priority if (ic("discord_priority") is None) else ic("discord_priority") -message = d_message if (ic("discord_message") is None) else ic("discord_message") -tags = d_tags if (ic("discord_tags") is None) else ic("discord_tags") -click = d_click if (ic("discord_click") is None) else ic("discord_click") +# Retrieve the hard-coded basic defaults. -help_text: str = """ - -u, --server is the webhook URL of the Discord server. It is stored in .env. - -s, --sender is the sender of the message, either an app name or a person. - The default is "Security message". - -d, --destination is the destination (actually the originator) of the message, either an app name or a person. - Default is "Wazuh (IDS)" - -p, --priority is the priority of the message, ranging from 1 (highest), to 5 (lowest). - Default is 5. - -m, --message is the text of the message to be sent. - Default is "Test message", but may include --tags and/or --click. - -t, --tags is an arbitrary strings of tags (keywords), seperated by a "," (comma). - Default is "informational, testing, hard-coded". - -c, --click is a link (URL) that can be followed by tapping/clicking inside the message. - Default is https://google.com. - -h, --help Shows this help message. - -v, --view Show yaml configuration. -""" +(d_server, d_sender, d_destination, d_priority, d_message, d_tags, d_click, d_notifier_priority_1, + d_notifier_priority_2, d_notifier_priority_3, d_notifier_priority_4, d_notifier_priority_5) = bd(notifier) + +# Use the values from the config yaml if available. Overrides the basic defaults (get_yaml_config). + +yc_args = [notifier, d_server, d_sender, d_destination, d_priority, d_message, d_tags, d_click, d_notifier_priority_1, + d_notifier_priority_2, d_notifier_priority_3, d_notifier_priority_4, d_notifier_priority_5] + +(server, sender, destination, priority, message, tags, click, notifier_priority_1, notifier_priority_2, + notifier_priority_3, notifier_priority_4, notifier_priority_5) = yc(*yc_args) # Get params during execution. Params found here, override minimal defaults and/or config settings. -try: - # Parsing argument - arguments, values = getopt.getopt(argument_list, options, long_options) - # checking each argument - for current_argument, current_value in arguments: +if ga(notifier, options, long_options) is None: + pass + # sender, destination, priority, message, tags, click = "", "", "", "", "", "" +else: + sender, destination, priority, message, tags, click = ga(notifier, options, long_options) - if current_argument in ("-h", "--help"): - print(help_text) - exit() +# Get the threat level from the message and map it to priority - elif current_argument in ("-v", "--view"): - vc() - exit() +threat_level = message[message.find('Threat level:') + 13:message.find('Threat level:') + 15].replace(" ", "") - elif current_argument in ("-s", "--sender"): - sender = current_value +# Get the mapping between threat level (event) and priority (Discord/ntfy) - elif current_argument in ("-d", "--destination"): - destination = current_value - - elif current_argument in ("-p", "--priority"): - priority = current_value - - elif current_argument in ("-m", "--message"): - message = current_value - - elif current_argument in ("-t", "--tags"): - tags = current_value - - elif current_argument in ("-c", "--click"): - click = current_value - -except getopt.error as err: - # output error, and return with an error code - print(str(err)) +# noinspection PyRedeclaration +priority = tpm(threat_level, notifier_priority_1, notifier_priority_2, notifier_priority_3, + notifier_priority_4, notifier_priority_5) # Finally, execute the POST request discord_command(discord_webhook, sender, destination, priority, message, tags, click) diff --git a/wazuh-notifier-config.yaml b/wazuh-notifier-config.yaml index 89f2f74..e9648b9 100755 --- a/wazuh-notifier-config.yaml +++ b/wazuh-notifier-config.yaml @@ -12,9 +12,17 @@ ntfy_enabled: 1 # Exclude rules that are listed in the ossec.conf active response definition. -excluded_rules: "5401, 5402, 5403" +excluded_rules: "5401, 5403" excluded_agents: "999" +# Priority mapping from 1-12 (Wazuh events) to 1-5 (Discord and ntfy notification) + +notifier_priority_1: 12, 11, 10 +notifier_priority_2: 9, 8 +notifier_priority_3: 7, 6 +notifier_priority_4: 5, 4 +notifier_priority_5: 3 ,2, 1 + # COMMON configuration settings end here. @@ -34,7 +42,7 @@ excluded_agents: "999" ntfy_server: "https://ntfy.sh/" ntfy_sender: "Wazuh (IDS)" ntfy_destination: "__KleinTest" -ntfy_priority: "5" +ntfy_priority: "3" ntfy_message: "Test message" ntfy_tags: "information, testing, yaml" ntfy_click: "https://google.com" @@ -57,10 +65,10 @@ ntfy_full_message: "0" # -h, --help shows this help message. Must have no value argument. # -v, --view show config. -discord_server: "not used. The webhook (server) is a secret stored in .env" +discord_server: "not used! The webhook (server) is a secret stored in .env" discord_sender: "Security message" discord_destination: "WAZUH (IDS)" -discord_priority: "5" +discord_priority: "3" discord_message: "Test message" discord_tags: "informational, testing, yaml" discord_click: "https://google.com" diff --git a/wazuh-ntfy-notifier.py b/wazuh-ntfy-notifier.py index f037fe8..334a825 100755 --- a/wazuh-ntfy-notifier.py +++ b/wazuh-ntfy-notifier.py @@ -16,16 +16,17 @@ # It allows you to send notifications to your phone or desktop via scripts from any computer, and/or using a REST API. # It's infinitely flexible, and 100% free software. For more information: https://ntfy.sh. -import getopt import json import sys import requests -from wazuh_notifier_lib import import_config as ic -from wazuh_notifier_lib import set_env as se -from wazuh_notifier_lib import set_time as st -from wazuh_notifier_lib import view_config as vc +from wazuh_notifier_module import get_arguments as ga +from wazuh_notifier_module import get_yaml_config as yc +from wazuh_notifier_module import set_basic_defaults as bd +from wazuh_notifier_module import set_environment as se +from wazuh_notifier_module import set_time as st +from wazuh_notifier_module import threat_priority_mapping as tpm # Get path values wazuh_path, ar_path, config_path = se() @@ -48,6 +49,7 @@ def ntfy_command(n_server, n_sender, n_destination, n_priority, n_message, n_tag # todo POST the request **** NEEDS future TRY **** requests.post(n_server + n_destination, data=x_message, headers=n_header) + # Remove 1st argument from the list of command line arguments argument_list = sys.argv[1:] @@ -57,76 +59,38 @@ options: str = "u:s:d:p:m:t:c:hv" # Long options long_options: list = ["server=", "sender=", "destination=", "priority=", "message=", "tags=", "click", "help", "view"] -# Setting some minimal defaults in case the yaml config isn't available -d_server: str = "https://ntfy.sh/" -d_sender: str = "Security message" -d_destination: str = "phil_alerts" -d_priority: str = "5" -d_message: str = "Test message" -d_tags: str = "informational, testing, hard-coded" -d_click: str = "https://google.com" +# Defining who I am +notifier = "ntfy" -# Use the values from the config yaml if available. Overrides the minimal defaults. -server = d_server if (ic("ntfy_server") is None) else ic("ntfy_server") -sender = d_sender if (ic("ntfy_sender") is None) else ic("ntfy_sender") -destination = d_destination if (ic("ntfy_destination") is None) else ic("ntfy_destination") -priority = d_priority if (ic("ntfy_priority") is None) else ic("ntfy_priority") -message = d_message if (ic("ntfy_message") is None) else ic("ntfy_message") -tags = d_tags if (ic("ntfy_tags") is None) else ic("ntfy_tags") -click = d_click if (ic("ntfy_click") is None) else ic("ntfy_click") +# Retrieve the hard-coded basic defaults. +(d_server, d_sender, d_destination, d_priority, d_message, d_tags, d_click, d_notifier_priority_1, + d_notifier_priority_2, d_notifier_priority_3, d_notifier_priority_4, d_notifier_priority_5) = bd(notifier) -help_text: str = """ - -u, --server is the URL of the NTFY server, ending with a "/". Default is https://ntfy.sh/. - -s, --sender is the sender of the message, either an app name or a person. Default is "Wazuh (IDS)". - -d, --destination is the NTFY subscription, to send the message to. Default is none. - -p, --priority is the priority of the message, ranging from 1 (highest), to 5 (lowest). Default is 5. - -m, --message is the text of the message to be sent. Default is "Test message". - -t, --tags is an arbitrary strings of tags (keywords), seperated by a "," (comma). Default is "informational, testing, hard-coded". - -c, --click is a link (URL) that can be followed by tapping/clicking inside the message. Default is https://google.com. - -h, --help shows this help message. Must have no value argument. - -v, --view show config. -""" +# Use the values from the config yaml if available. Overrides the basic defaults. +yc_args = [notifier, d_server, d_sender, d_destination, d_priority, d_message, d_tags, d_click, d_notifier_priority_1, + d_notifier_priority_2, d_notifier_priority_3, d_notifier_priority_4, d_notifier_priority_5] + +(server, sender, destination, priority, message, tags, click, notifier_priority_1, notifier_priority_2, + notifier_priority_3, notifier_priority_4, notifier_priority_5) = yc(*yc_args) # Get params during execution. Params found here, override minimal defaults and/or config settings. -try: - # Parsing argument - arguments, values = getopt.getopt(argument_list, options, long_options) - # Checking each argument - for current_argument, current_value in arguments: +if ga(notifier, options, long_options) is None: + pass + # sender, destination, priority, message, tags, click = "", "", "", "", "", "" +else: + sender, destination, priority, message, tags, click = ga(notifier, options, long_options) - if current_argument in ("-h", "--help"): - print(help_text) - exit() +# Get the threat level from the message and map it to priority - elif current_argument in ("-v", "--view"): - vc() - exit() +threat_level = message[message.find('Threat level:') + 13:message.find('Threat level:') + 15].replace(" ", "") - elif current_argument in ("-u", "--server"): - server = current_value +# Get the mapping between threat level (event) and priority (Discord/ntfy) - elif current_argument in ("-s", "--sender"): - sender = current_value +# noinspection PyRedeclaration +priority = tpm(threat_level, notifier_priority_1, notifier_priority_2, notifier_priority_3, + notifier_priority_4, notifier_priority_5) - elif current_argument in ("-d", "--destination"): - destination = current_value - - elif current_argument in ("-p", "--priority"): - priority = current_value - - elif current_argument in ("-m", "--message"): - message = current_value - - elif current_argument in ("-t", "--tags"): - tags = current_value - - elif current_argument in ("-c", "--click"): - click = current_value - -except getopt.error as err: - # output error, and return with an error code - print(str(err)) # Finally, execute the POST request ntfy_command(server, sender, destination, priority, message, tags, click) diff --git a/wazuh_notifier_lib.py b/wazuh_notifier_lib.py deleted file mode 100755 index 127dd88..0000000 --- a/wazuh_notifier_lib.py +++ /dev/null @@ -1,69 +0,0 @@ -import os -import time - -import yaml - - -# Set structured timestamp for logging and discord/ntfy message. - - -def set_time(): - now_message = time.strftime('%a, %d %b %Y %H:%M:%S') - now_logging = time.strftime('%Y/%m/%d %H:%M:%S') - return now_message, now_logging - - -# Define paths: wazuh_path = wazuh root directory -# ar_path = active-responses.log path, -# config_path = wazuh-notifier-config.yaml - -def set_env(): - - wazuh_path = os.path.abspath(os.path.join(__file__, "../../..")) - ar_path = '{0}/logs/active-responses.log'.format(wazuh_path) - config_path = '{0}/etc/wazuh-notifier-config.yaml'.format(wazuh_path) - - return wazuh_path, ar_path, config_path - - -# Import configuration settings from wazuh-notifier-config.yaml - - -def import_config(key): - try: - _, _, config_path = set_env() - - with open(config_path, 'r') as ntfier_config: - config: dict = yaml.safe_load(ntfier_config) - value: str = config.get(key) - return value - except (FileNotFoundError, PermissionError, OSError): - return None - - -# Show configuration settings from wazuh-notifier-config.yaml - - -def view_config(): - - _, _, config_path = set_env() - - try: - with open(config_path, 'r') as ntfier_config: - print(ntfier_config.read()) - except (FileNotFoundError, PermissionError, OSError): - print(config_path + " does not exist or is not accessible") - return - - -# Logging the Wazuh active Response request - - -def ar_log(): - now = set_time() - _, ar_path, _ = set_env() - msg = '{0} {1} {2}'.format(now, os.path.realpath(__file__), 'Post JSON Alert') - f = open(ar_path, 'a') - f.write(msg + '\n') - f.close() - diff --git a/wazuh_notifier_module.py b/wazuh_notifier_module.py new file mode 100755 index 0000000..bb00b12 --- /dev/null +++ b/wazuh_notifier_module.py @@ -0,0 +1,254 @@ +import getopt +import os +import sys +import time + +import yaml + + +# Set structured timestamp for logging and discord/ntfy message. + + +def set_time(): + now_message = time.strftime('%a, %d %b %Y %H:%M:%S') + now_logging = time.strftime('%Y/%m/%d %H:%M:%S') + return now_message, now_logging + + +# Define paths: wazuh_path = wazuh root directory +# ar_path = active-responses.log path, +# config_path = wazuh-notifier-config.yaml + +def set_environment(): + # todo fix reference when running manually/in process + + wazuh_path = "/var/ossec" + # wazuh_path = os.path.abspath(os.path.join(__file__, "../../..")) + ar_path = '{0}/logs/active-responses.log'.format(wazuh_path) + config_path = '{0}/etc/wazuh-notifier-config.yaml'.format(wazuh_path) + + return wazuh_path, ar_path, config_path + + +# Import configuration settings from wazuh-notifier-config.yaml + + +def import_config(key): + try: + _, _, config_path = set_environment() + + with open(config_path, 'r') as ntfier_config: + config: dict = yaml.safe_load(ntfier_config) + value: str = config.get(key) + return value + except (FileNotFoundError, PermissionError, OSError): + return None + + +# Show configuration settings from wazuh-notifier-config.yaml + + +def view_config(): + _, _, config_path = set_environment() + + try: + with open(config_path, 'r') as ntfier_config: + print(ntfier_config.read()) + except (FileNotFoundError, PermissionError, OSError): + print(config_path + " does not exist or is not accessible") + return + + +# Logging the Wazuh active Response request + + +def ar_log(): + now = set_time() + _, ar_path, _ = set_environment() + msg = '{0} {1} {2}'.format(now, os.path.realpath(__file__), 'Post JSON Alert') + f = open(ar_path, 'a') + f.write(msg + '\n') + f.close() + + +def threat_priority_mapping(threat_level, np_1, np_2, np_3, np_4, np_5): + # Map threat level v/s priority + + if threat_level in np_1: + priority_mapping = "1" + elif threat_level in np_2: + priority_mapping = "2" + elif threat_level in np_3: + priority_mapping = "3" + elif threat_level in np_4: + priority_mapping = "4" + elif threat_level in np_5: + priority_mapping = "5" + else: + priority_mapping = "3" + + return priority_mapping + + +def set_basic_defaults(notifier): + # Setting some minimal defaults in case the yaml config isn't available + notifier: str = notifier.lower() + + sender: str = "Security message" + destination: str = "Test" + priority: str = "1" + message: str = "Test message" + tags: str = "informational, testing, hard-coded" + click: str = "https://google.com" + + if notifier == "ntfy": + # NTFY defaults. + server: str = "https://ntfy.sh/" + + elif notifier == "discord": + + # Discord defaults. + server: str = "" + + else: + server: str = "Unknown notifier specified. Must be ntfy or discord." + + # Mapping event threat level to 5 value priority level. + + np_5 = "12, 11, 10" + np_4 = "9, 8" + np_3 = "7, 6" + np_2 = "5, 4" + np_1 = "3, 2, 1" + + return (server, sender, destination, priority, message, tags, click, + np_1, np_2, np_3, np_4, np_5) + + +def get_yaml_config(notifier: str, y_server: str, y_sender: str, y_destination: str, y_priority: str, y_message: str, + y_tags: str, y_click: str, y_np_1: str, y_np_2: str, y_np_3: str, y_np_4: str, y_np_5: str): + notifier: str = notifier.lower() + server = y_server if (import_config(notifier + "_server") is None) else import_config(notifier + "_server") + sender = y_sender if (import_config(notifier + "_sender") is None) else import_config(notifier + "_sender") + destination = y_destination if (import_config(notifier + "_destination") is None) else \ + import_config(notifier + "_destination") + priority = y_priority if (import_config(notifier + "_priority") is None) else import_config(notifier + "_priority") + message = y_message if (import_config(notifier + "_message") is None) else import_config(notifier + "_message") + tags = y_tags if (import_config(notifier + "_tags") is None) else import_config(notifier + "_tags") + click = y_click if (import_config(notifier + "_click") is None) else import_config(notifier + "_click") + + np_1 = y_np_1 if (import_config("np1") is None) else import_config("np1") + np_2 = y_np_2 if (import_config("np2") is None) else import_config("np2") + np_3 = y_np_3 if (import_config("np3") is None) else import_config("np3") + np_4 = y_np_4 if (import_config("np4") is None) else import_config("np4") + np_5 = y_np_5 if (import_config("np5") is None) else import_config("np5") + + return (server, sender, destination, priority, message, tags, click, + np_1, np_2, np_3, np_4, np_5) + + +def call_for_help(notifier): + notifier: str = notifier.lower() + + if notifier == "ntfy": + # NTFY help. + + help_text: str = """ + -u, --server is the URL of the NTFY server, ending with a "/". + Default is https://ntfy.sh/. + -s, --sender is the sender of the message, either an app name or a person. + Default is "Wazuh (IDS)". + -d, --destination is the NTFY subscription, to send the message to. + Default is none. + -p, --priority is the priority of the message, ranging from 1 (lowest), to 5 (highest). + Default is 5. + -m, --message is the text of the message to be sent. + Default is "Test message". + -t, --tags is an arbitrary strings of tags (keywords), seperated by a "," (comma). + Default is "informational, testing, hard-coded". + -c, --click is a link (URL) that can be followed by tapping/clicking inside the message. + Default is https://google.com. + -h, --help shows this help message. Must have no value argument. + -v, --view show config. + """ + + elif notifier == "discord": + + # Discord help. + + help_text: str = """ + -u, --server is the webhook URL of the Discord server. It is stored in .env. + -s, --sender is the sender of the message, either an app name or a person. + The default is "Security message". + -d, --destination is the destination (actually the originator) of the message, either an app name or a person. + Default is "Wazuh (IDS)" + -p, --priority is the priority of the message, ranging from 1 (highest), to 5 (lowest). + Default is 5. + -m, --message is the text of the message to be sent. + Default is "Test message", but may include --tags and/or --click. + -t, --tags is an arbitrary strings of tags (keywords), seperated by a "," (comma). + Default is "informational, testing, hard-coded". + -c, --click is a link (URL) that can be followed by tapping/clicking inside the message. + Default is https://google.com. + -h, --help Shows this help message. + -v, --view Show yaml configuration. + """ + else: + help_text: str = """ + No help available. Assuming the wrong notifier asked for help. + """ + + return help_text + + +def get_arguments(notifier, options, long_options): + # Get params during execution. Params found here, override minimal defaults and/or config settings. + + help_text = call_for_help(notifier) + + sender, destination, message, priority, tags, click = "", "", "", "", "", "" + notifier: str = notifier.lower() + + if notifier == "discord": + + pass + else: + argument_list: list = sys.argv[1:] + try: + # Parsing argument + arguments, values = getopt.getopt(argument_list, options, long_options) + + # checking each argument + for current_argument, current_value in arguments: + + if current_argument in ("-h", "--help"): + print(help_text) + exit() + + elif current_argument in ("-v", "--view"): + view_config() + exit() + + elif current_argument in ("-s", "--sender"): + sender = current_value + + elif current_argument in ("-d", "--destination"): + destination = current_value + + elif current_argument in ("-p", "--priority"): + priority = current_value + + elif current_argument in ("-m", "--message"): + message = current_value + + elif current_argument in ("-t", "--tags"): + tags = current_value + + elif current_argument in ("-c", "--click"): + click = current_value + + except getopt.error as err: + # output error, and return with an error code + print(str(err)) + + return sender, destination, priority, tags, click From 4e8254fbe92bf49f29f33e285f3bbec262af250a Mon Sep 17 00:00:00 2001 From: Rudi Klein Date: Tue, 7 May 2024 20:14:36 +0200 Subject: [PATCH 02/33] uhhhm --- wazuh-ntfy-notifier.py | 13 ++++++++----- wazuh_notifier_module.py | 11 +++++++---- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/wazuh-ntfy-notifier.py b/wazuh-ntfy-notifier.py index 334a825..19cd77a 100755 --- a/wazuh-ntfy-notifier.py +++ b/wazuh-ntfy-notifier.py @@ -75,11 +75,14 @@ yc_args = [notifier, d_server, d_sender, d_destination, d_priority, d_message, d # Get params during execution. Params found here, override minimal defaults and/or config settings. -if ga(notifier, options, long_options) is None: - pass - # sender, destination, priority, message, tags, click = "", "", "", "", "", "" -else: - sender, destination, priority, message, tags, click = ga(notifier, options, long_options) +# noinspection PyRedeclaration +a_sender, a_destination, a_message, a_priority, a_tags, a_click = ga(notifier, options, long_options) + +if a_sender != '': sender = a_sender +if a_destination != '': destination = a_destination +if a_priority != "": priority = a_priority +if a_tags != "": tags = a_tags +if a_click != "": click = a_click # Get the threat level from the message and map it to priority diff --git a/wazuh_notifier_module.py b/wazuh_notifier_module.py index bb00b12..a4b0a7a 100755 --- a/wazuh_notifier_module.py +++ b/wazuh_notifier_module.py @@ -204,16 +204,19 @@ def call_for_help(notifier): def get_arguments(notifier, options, long_options): # Get params during execution. Params found here, override minimal defaults and/or config settings. + notifier: str = notifier.lower() + help_text = call_for_help(notifier) sender, destination, message, priority, tags, click = "", "", "", "", "", "" - notifier: str = notifier.lower() - if notifier == "discord": + argument_list: list = sys.argv[1:] + if not argument_list: pass + else: - argument_list: list = sys.argv[1:] + try: # Parsing argument arguments, values = getopt.getopt(argument_list, options, long_options) @@ -251,4 +254,4 @@ def get_arguments(notifier, options, long_options): # output error, and return with an error code print(str(err)) - return sender, destination, priority, tags, click + return sender, destination, message, priority, tags, click From 5ee0b78c075de8f66db80e06eef8138c425afd3c Mon Sep 17 00:00:00 2001 From: darius Date: Wed, 8 May 2024 01:56:48 +0200 Subject: [PATCH 03/33] go functional mirror added --- requirements.txt | 3 + wazuh-notify-go/.env | 2 + wazuh-notify-go/.idea/.gitignore | 8 +++ wazuh-notify-go/.idea/.name | 1 + wazuh-notify-go/.idea/modules.xml | 8 +++ wazuh-notify-go/config.yaml | 86 +++++++++++++++++++++++++ wazuh-notify-go/go.mod | 8 +++ wazuh-notify-go/go.sum | 6 ++ wazuh-notify-go/init.go | 59 +++++++++++++++++ wazuh-notify-go/main.go | 21 ++++++ wazuh-notify-go/notification/discord.go | 42 ++++++++++++ wazuh-notify-go/notification/ntfy.go | 33 ++++++++++ wazuh-notify-go/types/types.go | 25 +++++++ 13 files changed, 302 insertions(+) create mode 100644 requirements.txt create mode 100644 wazuh-notify-go/.env create mode 100644 wazuh-notify-go/.idea/.gitignore create mode 100644 wazuh-notify-go/.idea/.name create mode 100644 wazuh-notify-go/.idea/modules.xml create mode 100644 wazuh-notify-go/config.yaml create mode 100644 wazuh-notify-go/go.mod create mode 100644 wazuh-notify-go/go.sum create mode 100644 wazuh-notify-go/init.go create mode 100644 wazuh-notify-go/main.go create mode 100644 wazuh-notify-go/notification/discord.go create mode 100644 wazuh-notify-go/notification/ntfy.go create mode 100644 wazuh-notify-go/types/types.go diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6d956f2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +requests~=2.31.0 +PyYAML~=6.0.1 +python-dotenv~=1.0.1 \ No newline at end of file diff --git a/wazuh-notify-go/.env b/wazuh-notify-go/.env new file mode 100644 index 0000000..c7bbc35 --- /dev/null +++ b/wazuh-notify-go/.env @@ -0,0 +1,2 @@ +DISCORD_WEBHOOK=https://discord.com/api/webhooks/1237526475306176572/kHGnaQiM8qWOfdLIN1LWqgq3dsfqiHtsfs-Z5FralJNdX5hdw-MOPf4zzIDiFVjcIat4 +NTFY_URL=https://ntfy.sh/__KleinTest \ No newline at end of file diff --git a/wazuh-notify-go/.idea/.gitignore b/wazuh-notify-go/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/wazuh-notify-go/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/wazuh-notify-go/.idea/.name b/wazuh-notify-go/.idea/.name new file mode 100644 index 0000000..78dd73c --- /dev/null +++ b/wazuh-notify-go/.idea/.name @@ -0,0 +1 @@ +wazuh-notify-go \ No newline at end of file diff --git a/wazuh-notify-go/.idea/modules.xml b/wazuh-notify-go/.idea/modules.xml new file mode 100644 index 0000000..c6cb081 --- /dev/null +++ b/wazuh-notify-go/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/wazuh-notify-go/config.yaml b/wazuh-notify-go/config.yaml new file mode 100644 index 0000000..3aab265 --- /dev/null +++ b/wazuh-notify-go/config.yaml @@ -0,0 +1,86 @@ +--- +#start of yaml + +# This is the yaml config file for both the wazuh-ntfy-notifier.py and wazuh-discord-notifier.py. +# The yaml needs to be in the same folder as the wazuh-ntfy-notifier.py and wazuh-discord-notifier.py + +# COMMON (custom-wazuh-notifiers.py) configuration settings start here. +# 1 = messages will be sent through this message server. 0 = messages will NOT be sent through this message server. + +discord_enabled: 1 +ntfy_enabled: 1 +targets: "discord,ntfy" + +# Exclude rules that are listed in the ossec.conf active response definition. + +excluded_rules: "5401, 5403" +excluded_agents: "999" + +# Priority mapping from 1-12 (Wazuh events) to 1-5 (Discord and ntfy notification) + +notifier_priority_1: 12, 11, 10 +notifier_priority_2: 9, 8 +notifier_priority_3: 7, 6 +notifier_priority_4: 5, 4 +notifier_priority_5: 3 ,2, 1 + +# COMMON configuration settings end here. + + +# NTFY configuration settings start here. +# The default values refer to the hard-coded defaults, if no yaml configuration is found. +# +# -u, --server is the URL of the NTFY server, ending with a "/". Default is https://ntfy.sh/. +# -s, --sender is the sender of the message, either an app name or a person. Default is "Wazuh (IDS)". +# -d, --destination is the NTFY subscription, to send the message to. Default is none. +# -p, --priority is the priority of the message, ranging from 1 (highest), to 5 (lowest). Default is 5. +# -m, --message is the text of the message to be sent. Default is "Test message". +# -t, --tags is an arbitrary strings of tags (keywords), seperated by a "," (comma). Default is "informational, testing, hard-coded". +# -c, --click is a link (URL) that can be followed by tapping/clicking inside the message. Default is https://google.com. +# -h, --help shows this help message. Must have no value argument. +# -v, --view show config. + +ntfy_server: "https://ntfy.sh/" +ntfy_sender: "Wazuh (IDS)" +ntfy_destination: "__KleinTest" +ntfy_priority: "3" +ntfy_message: "Test message" +ntfy_tags: "information, testing, yaml" +ntfy_click: "https://google.com" + +# 1 to send the full event data with the message. 0 only sends the message with basic details +ntfy_full_message: "0" + +# NTFY configuration settings end here. + +# DISCORD configuration settings start here. +# The default values refer to the hard-coded defaults, if no yaml configuration is found. + +# -u, --server is the webhook URL of the Discord server. It is stored in .env. +# -s, --sender is the sender of the message, either an app name or a person. The default is "Security message". +# -d, --destination is the destination (actually the originator) of the message, either an app name or a person. Default is "Wazuh (IDS)" +# -p, --priority is the priority of the message, ranging from 1 (highest), to 5 (lowest). Default is 5. +# -m, --message is the text of the message to be sent. Default is "Test message", but may include --tags and/or --click. +# -t, --tags is an arbitrary strings of tags (keywords), seperated by a "," (comma). Default is "informational, testing, hard-coded". +# -c, --click is a link (URL) that can be followed by tapping/clicking inside the message. Default is https://google.com. +# -h, --help shows this help message. Must have no value argument. +# -v, --view show config. + +discord_server: "not used! The webhook (server) is a secret stored in .env" +discord_sender: "Security message" +discord_destination: "WAZUH (IDS)" +discord_priority: 3 +discord_message: "Test message" +discord_tags: "informational, testing, yaml" +discord_click: "https://google.com" + +# 1 to send the full event data with the message. 0 only sends the message with basic details +discord_full_message: "0" + +# DISCORD configuration settings ends here. + +#end of yaml +... + + + diff --git a/wazuh-notify-go/go.mod b/wazuh-notify-go/go.mod new file mode 100644 index 0000000..5897e35 --- /dev/null +++ b/wazuh-notify-go/go.mod @@ -0,0 +1,8 @@ +module wazuh-notify + +go 1.22 + +require ( + github.com/joho/godotenv v1.5.1 + gopkg.in/yaml.v2 v2.4.0 +) diff --git a/wazuh-notify-go/go.sum b/wazuh-notify-go/go.sum new file mode 100644 index 0000000..f7e7502 --- /dev/null +++ b/wazuh-notify-go/go.sum @@ -0,0 +1,6 @@ +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/wazuh-notify-go/init.go b/wazuh-notify-go/init.go new file mode 100644 index 0000000..aa94353 --- /dev/null +++ b/wazuh-notify-go/init.go @@ -0,0 +1,59 @@ +package main + +import ( + "flag" + "github.com/joho/godotenv" + "gopkg.in/yaml.v2" + "log" + "os" + "wazuh-notify/types" +) + +var configParams types.Params + +func initNotify() { + err := godotenv.Load() + if err != nil { + log.Fatalf(".env not found: %v", err) + return + } + + flag.StringVar(&inputParams.Server, "server", "", "is the webhook URL of the Discord server. It is stored in .env.") + flag.StringVar(&inputParams.Click, "click", "", "is a link (URL) that can be followed by tapping/clicking inside the message. Default is https://google.com.") + flag.StringVar(&inputParams.Destination, "destination", "", "is the destination (actually the originator) of the message, either an app name or a person. Default is \"Wazuh (IDS)\"") + flag.StringVar(&inputParams.Message, "message", "", "is the text of the message to be sent. Default is \"Test message\", but may include --tags and/or --click.") + flag.IntVar(&inputParams.Priority, "priority", 0, "is the priority of the message, ranging from 1 (highest), to 5 (lowest). Default is 5.") + flag.StringVar(&inputParams.Sender, "sender", "", "is the sender of the message, either an app name or a person. The default is \"Security message\".") + flag.StringVar(&inputParams.Tags, "tags", "", "is an arbitrary strings of tags (keywords), seperated by a \",\" (comma). Default is \"informational,testing,hard-coded\".") + flag.StringVar(&inputParams.Targets, "targets", "", "is a list of targets to send notifications to. Default is \"discord\".") + + flag.Parse() + + yamlFile, err := os.ReadFile("./config.yaml") + yaml.Unmarshal(yamlFile, &configParams) + + if inputParams.Server == "" { + inputParams.Server = configParams.Server + } + if inputParams.Click == "" { + inputParams.Click = configParams.Click + } + if inputParams.Destination == "" { + inputParams.Destination = configParams.Destination + } + if inputParams.Message == "" { + inputParams.Message = configParams.Message + } + if inputParams.Priority == 0 { + inputParams.Priority = configParams.Priority + } + if inputParams.Sender == "" { + inputParams.Sender = configParams.Sender + } + if inputParams.Tags == "" { + inputParams.Tags = configParams.Tags + } + if inputParams.Targets == "" { + inputParams.Targets = configParams.Targets + } +} diff --git a/wazuh-notify-go/main.go b/wazuh-notify-go/main.go new file mode 100644 index 0000000..42685c9 --- /dev/null +++ b/wazuh-notify-go/main.go @@ -0,0 +1,21 @@ +package main + +import ( + "strings" + "wazuh-notify/notification" + "wazuh-notify/types" +) + +var inputParams types.Params + +func main() { + initNotify() + for _, target := range strings.Split(inputParams.Targets, ",") { + switch target { + case "discord": + notification.SendDiscord(inputParams) + case "ntfy": + notification.SendNtfy(inputParams) + } + } +} diff --git a/wazuh-notify-go/notification/discord.go b/wazuh-notify-go/notification/discord.go new file mode 100644 index 0000000..6c625c5 --- /dev/null +++ b/wazuh-notify-go/notification/discord.go @@ -0,0 +1,42 @@ +package notification + +import ( + "bytes" + "encoding/json" + "fmt" + "log" + "net/http" + "os" + "wazuh-notify/types" +) + +func SendDiscord(params types.Params) { + embedDescription := fmt.Sprintf("\n\n %s \n\nPriority: %x\nTags: %s\n\n%s", + params.Message, + params.Priority, + params.Tags, + params.Click, + ) + + message := types.Message{ + Username: params.Sender, + Embeds: []types.Embed{ + { + Title: params.Destination, + Description: embedDescription, + }, + }, + } + + payload := new(bytes.Buffer) + + err := json.NewEncoder(payload).Encode(message) + if err != nil { + return + } + + _, err = http.Post(os.Getenv("DISCORD_WEBHOOK"), "application/json", payload) + if err != nil { + log.Fatalf("An Error Occured %v", err) + } +} diff --git a/wazuh-notify-go/notification/ntfy.go b/wazuh-notify-go/notification/ntfy.go new file mode 100644 index 0000000..3749d41 --- /dev/null +++ b/wazuh-notify-go/notification/ntfy.go @@ -0,0 +1,33 @@ +package notification + +import ( + "net/http" + "os" + "strconv" + "strings" + "time" + "wazuh-notify/types" +) + +func SendNtfy(params types.Params) { + + payload := time.Now().Format(time.RFC3339) + "\n\n" + params.Message + + req, _ := http.NewRequest("POST", os.Getenv("NTFY_URL"), strings.NewReader(payload)) + req.Header.Set("Content-Type", "text/plain") + + if params.Sender != "" { + req.Header.Add("Title", params.Sender) + } + if params.Tags != "" { + req.Header.Add("Tags", params.Tags) + } + if params.Click != "" { + req.Header.Add("Click", params.Click) + } + if params.Priority != 0 { + req.Header.Add("Priority", strconv.Itoa(params.Priority)) + } + + http.DefaultClient.Do(req) +} diff --git a/wazuh-notify-go/types/types.go b/wazuh-notify-go/types/types.go new file mode 100644 index 0000000..8b76d08 --- /dev/null +++ b/wazuh-notify-go/types/types.go @@ -0,0 +1,25 @@ +package types + +type Params struct { + Server string `yaml:"discord_server"` + Sender string `yaml:"discord_sender"` + Destination string `yaml:"discord_destination"` + Priority int `yaml:"discord_priority"` + Message string `yaml:"discord_message"` + Tags string `yaml:"discord_tags"` + Click string `yaml:"discord_click"` + Targets string `yaml:"targets"` +} + +type Message struct { + Username string `json:"username,omitempty"` + AvatarUrl string `json:"avatar_url,omitempty"` + Content string `json:"content,omitempty"` + Embeds []Embed `json:"embeds,omitempty"` +} + +type Embed struct { + Title string `json:"title,omitempty"` + Description string `json:"description,omitempty"` + Color string `json:"color,omitempty"` +} From 9e0d5b22d2de53385a8e1db460eda71208336194 Mon Sep 17 00:00:00 2001 From: darius Date: Wed, 8 May 2024 01:57:54 +0200 Subject: [PATCH 04/33] . --- wazuh-notify-go/.idea/.gitignore | 8 -------- wazuh-notify-go/.idea/.name | 1 - wazuh-notify-go/.idea/modules.xml | 8 -------- 3 files changed, 17 deletions(-) delete mode 100644 wazuh-notify-go/.idea/.gitignore delete mode 100644 wazuh-notify-go/.idea/.name delete mode 100644 wazuh-notify-go/.idea/modules.xml diff --git a/wazuh-notify-go/.idea/.gitignore b/wazuh-notify-go/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/wazuh-notify-go/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/wazuh-notify-go/.idea/.name b/wazuh-notify-go/.idea/.name deleted file mode 100644 index 78dd73c..0000000 --- a/wazuh-notify-go/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -wazuh-notify-go \ No newline at end of file diff --git a/wazuh-notify-go/.idea/modules.xml b/wazuh-notify-go/.idea/modules.xml deleted file mode 100644 index c6cb081..0000000 --- a/wazuh-notify-go/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file From 3a7ad4941a9ed045227f4c593c48af7dffc2e409 Mon Sep 17 00:00:00 2001 From: Rudi Klein Date: Wed, 8 May 2024 15:09:35 +0200 Subject: [PATCH 05/33] yaml update --- wazuh-notify-go/config.yaml | 57 ++-------------------------------- wazuh-notify-go/init.go | 35 +++------------------ wazuh-notify-go/types/types.go | 16 +++++----- 3 files changed, 15 insertions(+), 93 deletions(-) diff --git a/wazuh-notify-go/config.yaml b/wazuh-notify-go/config.yaml index 3aab265..1f9c60c 100644 --- a/wazuh-notify-go/config.yaml +++ b/wazuh-notify-go/config.yaml @@ -7,8 +7,6 @@ # COMMON (custom-wazuh-notifiers.py) configuration settings start here. # 1 = messages will be sent through this message server. 0 = messages will NOT be sent through this message server. -discord_enabled: 1 -ntfy_enabled: 1 targets: "discord,ntfy" # Exclude rules that are listed in the ossec.conf active response definition. @@ -24,61 +22,10 @@ notifier_priority_3: 7, 6 notifier_priority_4: 5, 4 notifier_priority_5: 3 ,2, 1 -# COMMON configuration settings end here. +sender: "Wazuh (IDS)" +click: "https://google.com" -# NTFY configuration settings start here. -# The default values refer to the hard-coded defaults, if no yaml configuration is found. -# -# -u, --server is the URL of the NTFY server, ending with a "/". Default is https://ntfy.sh/. -# -s, --sender is the sender of the message, either an app name or a person. Default is "Wazuh (IDS)". -# -d, --destination is the NTFY subscription, to send the message to. Default is none. -# -p, --priority is the priority of the message, ranging from 1 (highest), to 5 (lowest). Default is 5. -# -m, --message is the text of the message to be sent. Default is "Test message". -# -t, --tags is an arbitrary strings of tags (keywords), seperated by a "," (comma). Default is "informational, testing, hard-coded". -# -c, --click is a link (URL) that can be followed by tapping/clicking inside the message. Default is https://google.com. -# -h, --help shows this help message. Must have no value argument. -# -v, --view show config. - -ntfy_server: "https://ntfy.sh/" -ntfy_sender: "Wazuh (IDS)" -ntfy_destination: "__KleinTest" -ntfy_priority: "3" -ntfy_message: "Test message" -ntfy_tags: "information, testing, yaml" -ntfy_click: "https://google.com" - -# 1 to send the full event data with the message. 0 only sends the message with basic details -ntfy_full_message: "0" - -# NTFY configuration settings end here. - -# DISCORD configuration settings start here. -# The default values refer to the hard-coded defaults, if no yaml configuration is found. - -# -u, --server is the webhook URL of the Discord server. It is stored in .env. -# -s, --sender is the sender of the message, either an app name or a person. The default is "Security message". -# -d, --destination is the destination (actually the originator) of the message, either an app name or a person. Default is "Wazuh (IDS)" -# -p, --priority is the priority of the message, ranging from 1 (highest), to 5 (lowest). Default is 5. -# -m, --message is the text of the message to be sent. Default is "Test message", but may include --tags and/or --click. -# -t, --tags is an arbitrary strings of tags (keywords), seperated by a "," (comma). Default is "informational, testing, hard-coded". -# -c, --click is a link (URL) that can be followed by tapping/clicking inside the message. Default is https://google.com. -# -h, --help shows this help message. Must have no value argument. -# -v, --view show config. - -discord_server: "not used! The webhook (server) is a secret stored in .env" -discord_sender: "Security message" -discord_destination: "WAZUH (IDS)" -discord_priority: 3 -discord_message: "Test message" -discord_tags: "informational, testing, yaml" -discord_click: "https://google.com" - -# 1 to send the full event data with the message. 0 only sends the message with basic details -discord_full_message: "0" - -# DISCORD configuration settings ends here. - #end of yaml ... diff --git a/wazuh-notify-go/init.go b/wazuh-notify-go/init.go index aa94353..3d1a4f6 100644 --- a/wazuh-notify-go/init.go +++ b/wazuh-notify-go/init.go @@ -18,42 +18,17 @@ func initNotify() { return } + yamlFile, err := os.ReadFile("./config.yaml") + yaml.Unmarshal(yamlFile, &configParams) + flag.StringVar(&inputParams.Server, "server", "", "is the webhook URL of the Discord server. It is stored in .env.") - flag.StringVar(&inputParams.Click, "click", "", "is a link (URL) that can be followed by tapping/clicking inside the message. Default is https://google.com.") + flag.StringVar(&inputParams.Click, "click", configParams.Click, "is a link (URL) that can be followed by tapping/clicking inside the message. Default is https://google.com.") flag.StringVar(&inputParams.Destination, "destination", "", "is the destination (actually the originator) of the message, either an app name or a person. Default is \"Wazuh (IDS)\"") flag.StringVar(&inputParams.Message, "message", "", "is the text of the message to be sent. Default is \"Test message\", but may include --tags and/or --click.") flag.IntVar(&inputParams.Priority, "priority", 0, "is the priority of the message, ranging from 1 (highest), to 5 (lowest). Default is 5.") - flag.StringVar(&inputParams.Sender, "sender", "", "is the sender of the message, either an app name or a person. The default is \"Security message\".") + flag.StringVar(&inputParams.Sender, "sender", configParams.Sender, "is the sender of the message, either an app name or a person. The default is \"Security message\".") flag.StringVar(&inputParams.Tags, "tags", "", "is an arbitrary strings of tags (keywords), seperated by a \",\" (comma). Default is \"informational,testing,hard-coded\".") flag.StringVar(&inputParams.Targets, "targets", "", "is a list of targets to send notifications to. Default is \"discord\".") flag.Parse() - - yamlFile, err := os.ReadFile("./config.yaml") - yaml.Unmarshal(yamlFile, &configParams) - - if inputParams.Server == "" { - inputParams.Server = configParams.Server - } - if inputParams.Click == "" { - inputParams.Click = configParams.Click - } - if inputParams.Destination == "" { - inputParams.Destination = configParams.Destination - } - if inputParams.Message == "" { - inputParams.Message = configParams.Message - } - if inputParams.Priority == 0 { - inputParams.Priority = configParams.Priority - } - if inputParams.Sender == "" { - inputParams.Sender = configParams.Sender - } - if inputParams.Tags == "" { - inputParams.Tags = configParams.Tags - } - if inputParams.Targets == "" { - inputParams.Targets = configParams.Targets - } } diff --git a/wazuh-notify-go/types/types.go b/wazuh-notify-go/types/types.go index 8b76d08..7c2ac96 100644 --- a/wazuh-notify-go/types/types.go +++ b/wazuh-notify-go/types/types.go @@ -1,14 +1,14 @@ package types type Params struct { - Server string `yaml:"discord_server"` - Sender string `yaml:"discord_sender"` - Destination string `yaml:"discord_destination"` - Priority int `yaml:"discord_priority"` - Message string `yaml:"discord_message"` - Tags string `yaml:"discord_tags"` - Click string `yaml:"discord_click"` - Targets string `yaml:"targets"` + Server string + Sender string `yaml:"sender,omitempty"` + Destination string + Priority int + Message string + Tags string + Click string `yaml:"click,omitempty"` + Targets string `yaml:"targets,omitempty"` } type Message struct { From 9668eec414a35c5ccfe1c6478d1b37b2543fd817 Mon Sep 17 00:00:00 2001 From: Rudi Klein Date: Wed, 8 May 2024 22:13:12 +0200 Subject: [PATCH 06/33] chopped of the old brush --- wazuh-notifier-config.yaml | 60 ++------------------------------------ 1 file changed, 3 insertions(+), 57 deletions(-) diff --git a/wazuh-notifier-config.yaml b/wazuh-notifier-config.yaml index e9648b9..2fbe808 100755 --- a/wazuh-notifier-config.yaml +++ b/wazuh-notifier-config.yaml @@ -7,8 +7,7 @@ # COMMON (custom-wazuh-notifiers.py) configuration settings start here. # 1 = messages will be sent through this message server. 0 = messages will NOT be sent through this message server. -discord_enabled: 1 -ntfy_enabled: 1 +targets: "discord,ntfy" # Exclude rules that are listed in the ossec.conf active response definition. @@ -23,63 +22,10 @@ notifier_priority_3: 7, 6 notifier_priority_4: 5, 4 notifier_priority_5: 3 ,2, 1 -# COMMON configuration settings end here. +sender: "Wazuh (IDS)" +click: "https://google.com" -# NTFY configuration settings start here. -# The default values refer to the hard-coded defaults, if no yaml configuration is found. -# -# -u, --server is the URL of the NTFY server, ending with a "/". Default is https://ntfy.sh/. -# -s, --sender is the sender of the message, either an app name or a person. Default is "Wazuh (IDS)". -# -d, --destination is the NTFY subscription, to send the message to. Default is none. -# -p, --priority is the priority of the message, ranging from 1 (highest), to 5 (lowest). Default is 5. -# -m, --message is the text of the message to be sent. Default is "Test message". -# -t, --tags is an arbitrary strings of tags (keywords), seperated by a "," (comma). Default is "informational, testing, hard-coded". -# -c, --click is a link (URL) that can be followed by tapping/clicking inside the message. Default is https://google.com. -# -h, --help shows this help message. Must have no value argument. -# -v, --view show config. - -ntfy_server: "https://ntfy.sh/" -ntfy_sender: "Wazuh (IDS)" -ntfy_destination: "__KleinTest" -ntfy_priority: "3" -ntfy_message: "Test message" -ntfy_tags: "information, testing, yaml" -ntfy_click: "https://google.com" - -# 1 to send the full event data with the message. 0 only sends the message with basic details -ntfy_full_message: "0" - -# NTFY configuration settings end here. - -# DISCORD configuration settings start here. -# The default values refer to the hard-coded defaults, if no yaml configuration is found. - -# -u, --server is the webhook URL of the Discord server. It is stored in .env. -# -s, --sender is the sender of the message, either an app name or a person. The default is "Security message". -# -d, --destination is the destination (actually the originator) of the message, either an app name or a person. Default is "Wazuh (IDS)" -# -p, --priority is the priority of the message, ranging from 1 (highest), to 5 (lowest). Default is 5. -# -m, --message is the text of the message to be sent. Default is "Test message", but may include --tags and/or --click. -# -t, --tags is an arbitrary strings of tags (keywords), seperated by a "," (comma). Default is "informational, testing, hard-coded". -# -c, --click is a link (URL) that can be followed by tapping/clicking inside the message. Default is https://google.com. -# -h, --help shows this help message. Must have no value argument. -# -v, --view show config. - -discord_server: "not used! The webhook (server) is a secret stored in .env" -discord_sender: "Security message" -discord_destination: "WAZUH (IDS)" -discord_priority: "3" -discord_message: "Test message" -discord_tags: "informational, testing, yaml" -discord_click: "https://google.com" - -# 1 to send the full event data with the message. 0 only sends the message with basic details -discord_full_message: "0" - -# DISCORD configuration settings ends here. - #end of yaml ... - - From 45c767a9cefde4f5e7e1989ec6e8152718a4d43c Mon Sep 17 00:00:00 2001 From: Rudi Klein Date: Wed, 8 May 2024 22:13:45 +0200 Subject: [PATCH 07/33] chopped of the old brush --- requirements.txt | 4 +- wazuh-notifier.py | 86 +++++++++++++++++++ wazuh_notifier_module.py | 176 +++++++++++++-------------------------- 3 files changed, 148 insertions(+), 118 deletions(-) create mode 100755 wazuh-notifier.py diff --git a/requirements.txt b/requirements.txt index 6d956f2..6a36804 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -requests~=2.31.0 -PyYAML~=6.0.1 +requests~=2.25.1 +PyYAML~=5.4.1 python-dotenv~=1.0.1 \ No newline at end of file diff --git a/wazuh-notifier.py b/wazuh-notifier.py new file mode 100755 index 0000000..c13bbc8 --- /dev/null +++ b/wazuh-notifier.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 + +# This script is free software. +# +# Copyright (C) 2024, Rudi Klein. +# All rights reserved. +# +# This program is free software; you can redistribute it +# and/or modify it under the terms of the GNU General Public +# License (version 2) as published by the FSF - Free Software +# Foundation. +# +# This script is executed by the active response script (wazuh-active-response.py), which is triggered by rules firing. +# +# Discord is a voice, video and text communication service used by over a hundred million people to hang out and talk +# with their friends and communities. It allows for receiving message using webhooks. +# For more information: https://discord.com. + + +import json + +import requests + +from wazuh_notifier_module import get_arguments +from wazuh_notifier_module import get_env +from wazuh_notifier_module import get_yaml_config +from wazuh_notifier_module import set_environment +from wazuh_notifier_module import set_time +from wazuh_notifier_module import threat_priority_mapping + +# Setup the environment + +# Get time value +now_message, now_logging = set_time() + +# Get .env values +discord_webhook, ntfy_webhook = get_env() + +# Get path values +wazuh_path, ar_path, config_path = set_environment() + + +# the POST builders for the targets. Prepares https and sends the request. + +def discord_command(url, sender, destination, priority, message, tags, click): + x_message = (now_message + + "\n\n" + message + "\n\n" + + "Priority: " + priority + "\n" + + "Tags: " + tags + "\n\n" + click + ) + data = {"username": sender, "embeds": [{"description": x_message, "title": destination}]} + + requests.post(url, json=data) + + +def ntfy_command(url, sender, destination, priority, message, tags, click): + header = "" + if sender != "": header = header + '"Title"' + ": " + '"' + sender + '"' + ", " + if tags != "": header = header + '"Tags"' + ": " + '"' + tags + '"' + ", " + if click != "": header = header + '"Click"' + ": " + '"' + click + '"' + ", " + if priority != "": header = header + '"Priority"' + ": " + '"' + priority + '"' + header = json.loads("{" + header + "}") + x_message = now_message + "\n\n" + message + + # todo POST the request **** NEEDS future TRY **** + requests.post(url + destination, data=x_message, headers=header) + + +# Get the YAML config if any +config: dict = get_yaml_config() + +# Get the command line arguments +if get_arguments() is None: + url, sender, destination, message, priority, tags, click = "", "", "", "", "", "", "" +else: + url, sender, destination, priority, message, tags, click = get_arguments() + +# Get the threat level from the event (message) +threat_level = message[message.find('Threat level:') + 13:message.find('Threat level:') + 15].replace(" ", "") + +# Get the mapping between threat level (event) and priority (Discord/ntfy) +threat_priority = threat_priority_mapping(threat_level, config.get('np_1'), config.get('np_2'), + config.get('np_3'), config.get('np_4'), config.get('np_5')) + +# Finally, execute the POST request +# discord_command(discord_webhook, sender, destination, priority, message, tags, click) diff --git a/wazuh_notifier_module.py b/wazuh_notifier_module.py index a4b0a7a..f8f2c0f 100755 --- a/wazuh_notifier_module.py +++ b/wazuh_notifier_module.py @@ -2,10 +2,30 @@ import getopt import os import sys import time +from os.path import join, dirname import yaml +from dotenv import load_dotenv +def get_env(): + try: + dotenv_path = join(dirname(__file__), '.env') + load_dotenv(dotenv_path) + if not os.path.isfile(dotenv_path): + raise Exception(dotenv_path, "file not found") + + # Retrieve url from .env + discord_url = os.getenv("DISCORD_url") + ntfy_url = os.getenv("NTFY_url") + + except Exception as err: + # output error, and return with an error code + print(str(Exception(err.args))) + exit(err) + + return discord_url, ntfy_url + # Set structured timestamp for logging and discord/ntfy message. @@ -25,7 +45,7 @@ def set_environment(): wazuh_path = "/var/ossec" # wazuh_path = os.path.abspath(os.path.join(__file__, "../../..")) ar_path = '{0}/logs/active-responses.log'.format(wazuh_path) - config_path = '{0}/etc/wazuh-notifier-config.yaml'.format(wazuh_path) + config_path = 'wazuh-notifier-config.yaml'.format(wazuh_path) return wazuh_path, ar_path, config_path @@ -33,14 +53,13 @@ def set_environment(): # Import configuration settings from wazuh-notifier-config.yaml -def import_config(key): +def import_config(): try: _, _, config_path = set_environment() with open(config_path, 'r') as ntfier_config: config: dict = yaml.safe_load(ntfier_config) - value: str = config.get(key) - return value + return config except (FileNotFoundError, PermissionError, OSError): return None @@ -90,125 +109,47 @@ def threat_priority_mapping(threat_level, np_1, np_2, np_3, np_4, np_5): return priority_mapping -def set_basic_defaults(notifier): - # Setting some minimal defaults in case the yaml config isn't available - notifier: str = notifier.lower() +def get_yaml_config(): + config = import_config() - sender: str = "Security message" - destination: str = "Test" - priority: str = "1" - message: str = "Test message" - tags: str = "informational, testing, hard-coded" - click: str = "https://google.com" + config['np_1'] = "3, 3, 3" if (config.get("notifier_priority_1") is None) else config.get("notifier_priority_1") + config['np_2'] = "4, 5" if (config.get("notifier_priority_2") is None) else config.get("notifier_priority_2") + config['np_3'] = "6, 7" if (config.get("notifier_priority_3") is None) else config.get("notifier_priority_3") + config['np_4'] = "8, 9" if (config.get("notifier_priority_4") is None) else config.get("notifier_priority_4") + config['np_5'] = "10, 11, 12" if (config.get("notifier_priority_5") is None) else config.get("notifier_priority_5") + config['targets'] = "ntfy, discord" if (config.get("targets") is None) else config.get("targets") + config['excluded_rules'] = "" if (config.get("excluded_rules") is None) else config.get("excluded_rules") + config['excluded_agents'] = "" if (config.get("excluded_agents") is None) else config.get("excluded_agents") + config['sender'] = "Wazuh (IDS)" if (config.get("sender") is None) else config.get("sender") + config['click'] = "https://wazuh.org" if (config.get("click") is None) else config.get("click") - if notifier == "ntfy": - # NTFY defaults. - server: str = "https://ntfy.sh/" - - elif notifier == "discord": - - # Discord defaults. - server: str = "" - - else: - server: str = "Unknown notifier specified. Must be ntfy or discord." - - # Mapping event threat level to 5 value priority level. - - np_5 = "12, 11, 10" - np_4 = "9, 8" - np_3 = "7, 6" - np_2 = "5, 4" - np_1 = "3, 2, 1" - - return (server, sender, destination, priority, message, tags, click, - np_1, np_2, np_3, np_4, np_5) + return config -def get_yaml_config(notifier: str, y_server: str, y_sender: str, y_destination: str, y_priority: str, y_message: str, - y_tags: str, y_click: str, y_np_1: str, y_np_2: str, y_np_3: str, y_np_4: str, y_np_5: str): - notifier: str = notifier.lower() - server = y_server if (import_config(notifier + "_server") is None) else import_config(notifier + "_server") - sender = y_sender if (import_config(notifier + "_sender") is None) else import_config(notifier + "_sender") - destination = y_destination if (import_config(notifier + "_destination") is None) else \ - import_config(notifier + "_destination") - priority = y_priority if (import_config(notifier + "_priority") is None) else import_config(notifier + "_priority") - message = y_message if (import_config(notifier + "_message") is None) else import_config(notifier + "_message") - tags = y_tags if (import_config(notifier + "_tags") is None) else import_config(notifier + "_tags") - click = y_click if (import_config(notifier + "_click") is None) else import_config(notifier + "_click") - - np_1 = y_np_1 if (import_config("np1") is None) else import_config("np1") - np_2 = y_np_2 if (import_config("np2") is None) else import_config("np2") - np_3 = y_np_3 if (import_config("np3") is None) else import_config("np3") - np_4 = y_np_4 if (import_config("np4") is None) else import_config("np4") - np_5 = y_np_5 if (import_config("np5") is None) else import_config("np5") - - return (server, sender, destination, priority, message, tags, click, - np_1, np_2, np_3, np_4, np_5) - - -def call_for_help(notifier): - notifier: str = notifier.lower() - - if notifier == "ntfy": - # NTFY help. - - help_text: str = """ - -u, --server is the URL of the NTFY server, ending with a "/". - Default is https://ntfy.sh/. - -s, --sender is the sender of the message, either an app name or a person. - Default is "Wazuh (IDS)". - -d, --destination is the NTFY subscription, to send the message to. - Default is none. - -p, --priority is the priority of the message, ranging from 1 (lowest), to 5 (highest). - Default is 5. - -m, --message is the text of the message to be sent. - Default is "Test message". - -t, --tags is an arbitrary strings of tags (keywords), seperated by a "," (comma). - Default is "informational, testing, hard-coded". - -c, --click is a link (URL) that can be followed by tapping/clicking inside the message. - Default is https://google.com. - -h, --help shows this help message. Must have no value argument. - -v, --view show config. - """ - - elif notifier == "discord": - - # Discord help. - - help_text: str = """ - -u, --server is the webhook URL of the Discord server. It is stored in .env. - -s, --sender is the sender of the message, either an app name or a person. - The default is "Security message". - -d, --destination is the destination (actually the originator) of the message, either an app name or a person. - Default is "Wazuh (IDS)" - -p, --priority is the priority of the message, ranging from 1 (highest), to 5 (lowest). - Default is 5. - -m, --message is the text of the message to be sent. - Default is "Test message", but may include --tags and/or --click. - -t, --tags is an arbitrary strings of tags (keywords), seperated by a "," (comma). - Default is "informational, testing, hard-coded". - -c, --click is a link (URL) that can be followed by tapping/clicking inside the message. - Default is https://google.com. - -h, --help Shows this help message. - -v, --view Show yaml configuration. - """ - else: - help_text: str = """ - No help available. Assuming the wrong notifier asked for help. - """ - - return help_text - - -def get_arguments(notifier, options, long_options): +def get_arguments(): # Get params during execution. Params found here, override minimal defaults and/or config settings. - notifier: str = notifier.lower() + # Short options + options: str = "u:s:p:m:t:c:hv" - help_text = call_for_help(notifier) + # Long options + long_options: list = ["url=", "sender=", "destination=", "priority=", "message=", "tags=", "click=", "help", + "view"] - sender, destination, message, priority, tags, click = "", "", "", "", "", "" + help_text: str = """ + -u, --url is the url for the server, ending with a "/". + -s, --sender is the sender of the message, either an app name or a person. + -d, --destination is the NTFY subscription or Discord title, to send the message to. + -p, --priority is the priority of the message, ranging from 1 (lowest), to 5 (highest). + -m, --message is the text of the message to be sent. + -t, --tags is an arbitrary strings of tags (keywords), seperated by a "," (comma). + -c, --click is a link (URL) that can be followed by tapping/clicking inside the message. + -h, --help shows this help message. Must have no value argument. + -v, --view show config. + + """ + + url, sender, destination, message, priority, tags, click = "", "", "", "", "", "", "" argument_list: list = sys.argv[1:] @@ -232,6 +173,9 @@ def get_arguments(notifier, options, long_options): view_config() exit() + elif current_argument in ("-u", "--url"): + url = current_value + elif current_argument in ("-s", "--sender"): sender = current_value @@ -254,4 +198,4 @@ def get_arguments(notifier, options, long_options): # output error, and return with an error code print(str(err)) - return sender, destination, message, priority, tags, click + return url, sender, destination, message, priority, tags, click From d3bf7caff3c46148af36417b6d7b60042c10e630 Mon Sep 17 00:00:00 2001 From: darius Date: Thu, 9 May 2024 12:24:44 +0200 Subject: [PATCH 08/33] wazuh reader --- wazuh-notify-go/init.go | 1 + wazuh-notify-go/main.go | 12 ++++++ wazuh-notify-go/types/wazuh.go | 72 ++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 wazuh-notify-go/types/wazuh.go diff --git a/wazuh-notify-go/init.go b/wazuh-notify-go/init.go index 3d1a4f6..22a4bde 100644 --- a/wazuh-notify-go/init.go +++ b/wazuh-notify-go/init.go @@ -31,4 +31,5 @@ func initNotify() { flag.StringVar(&inputParams.Targets, "targets", "", "is a list of targets to send notifications to. Default is \"discord\".") flag.Parse() + inputParams.Targets = configParams.Targets } diff --git a/wazuh-notify-go/main.go b/wazuh-notify-go/main.go index 42685c9..0cbeda4 100644 --- a/wazuh-notify-go/main.go +++ b/wazuh-notify-go/main.go @@ -1,15 +1,27 @@ package main import ( + "bufio" + "encoding/json" + "os" "strings" "wazuh-notify/notification" "wazuh-notify/types" ) var inputParams types.Params +var wazuhData types.WazuhMessage func main() { initNotify() + + reader := bufio.NewReader(os.Stdin) + + json.NewDecoder(reader).Decode(&wazuhData) //todo for later + + text, _ := reader.ReadString('\n') //todo for testing + inputParams.Message = text + for _, target := range strings.Split(inputParams.Targets, ",") { switch target { case "discord": diff --git a/wazuh-notify-go/types/wazuh.go b/wazuh-notify-go/types/wazuh.go new file mode 100644 index 0000000..4a4da76 --- /dev/null +++ b/wazuh-notify-go/types/wazuh.go @@ -0,0 +1,72 @@ +package types + +type WazuhMessage struct { + Version int `json:"version"` + Origin Origin `json:"origin"` + Command string `json:"command"` + Parameters Parameters `json:"parameters"` +} + +type Origin struct { + Name string `json:"name"` + Module string `json:"module"` +} + +type Parameters struct { + ExtraArgs []interface{} `json:"extra_args"` + Alert Alert `json:"alert"` + Program string `json:"program"` +} + +type Alert struct { + Timestamp string `json:"timestamp"` + Rule Rule `json:"rule"` + Agent Agent `json:"agent"` + Manager Manager `json:"manager"` + ID string `json:"id"` + FullLog string `json:"full_log"` + Decoder Decoder `json:"decoder"` + Data Data `json:"data"` + Location string `json:"location"` +} + +type Rule struct { + Level int `json:"level"` + Description string `json:"description"` + ID string `json:"id"` + Mitre Mitre `json:"mitre"` + Info string `json:"info"` + Firedtimes int `json:"firedtimes"` + Mail bool `json:"mail"` + Groups []string `json:"groups"` + PciDss []string `json:"pci_dss"` + Gdpr []string `json:"gdpr"` + Nist80053 []string `json:"nist_800_53"` + Tsc []string `json:"tsc"` +} + +type Mitre struct { + ID []string `json:"id"` + Tactic []string `json:"tactic"` + Technique []string `json:"technique"` +} + +type Agent struct { + ID string `json:"id"` + Name string `json:"name"` +} + +type Manager struct { + Name string `json:"name"` +} + +type Decoder struct { + Name string `json:"name"` +} + +type Data struct { + Protocol string `json:"protocol"` + Srcip string `json:"srcip"` + ID string `json:"id"` + URL string `json:"url"` +} From 7807c550122dae03015701344f9ae5563d9a3cad Mon Sep 17 00:00:00 2001 From: Rudi Klein Date: Thu, 9 May 2024 15:27:45 +0200 Subject: [PATCH 09/33] semi-final python go final --- test.go | 11 ++++++++ wazuh-notifier-conf.yaml | 31 +++++++++++++++++++++ wazuh-notify-go/config.yaml | 10 +++---- wazuh-notify-go/log/log.go | 14 ++++++++++ wazuh-notify-go/main.go | 20 ++++--------- wazuh-notify-go/notification/discord.go | 20 +++++++------ wazuh-notify-go/notification/ntfy.go | 7 ++++- wazuh-notify-go/{ => services}/init.go | 37 +++++++++++++++++++------ wazuh-notify-go/services/mapping.go | 21 ++++++++++++++ wazuh-notify-go/types/types.go | 20 +++++++------ wazuh_notifier_module.py | 2 +- 11 files changed, 147 insertions(+), 46 deletions(-) create mode 100644 test.go create mode 100755 wazuh-notifier-conf.yaml create mode 100644 wazuh-notify-go/log/log.go rename wazuh-notify-go/{ => services}/init.go (65%) create mode 100644 wazuh-notify-go/services/mapping.go diff --git a/test.go b/test.go new file mode 100644 index 0000000..7bfc947 --- /dev/null +++ b/test.go @@ -0,0 +1,11 @@ +package main + +import ( + "fmt" + "os" +) + +func main() { + fmt.Println("hier") + os.Stderr.Write([]byte("hier2")) +} diff --git a/wazuh-notifier-conf.yaml b/wazuh-notifier-conf.yaml new file mode 100755 index 0000000..2fbe808 --- /dev/null +++ b/wazuh-notifier-conf.yaml @@ -0,0 +1,31 @@ +--- +#start of yaml + +# This is the yaml config file for both the wazuh-ntfy-notifier.py and wazuh-discord-notifier.py. +# The yaml needs to be in the same folder as the wazuh-ntfy-notifier.py and wazuh-discord-notifier.py + +# COMMON (custom-wazuh-notifiers.py) configuration settings start here. +# 1 = messages will be sent through this message server. 0 = messages will NOT be sent through this message server. + +targets: "discord,ntfy" + +# Exclude rules that are listed in the ossec.conf active response definition. + +excluded_rules: "5401, 5403" +excluded_agents: "999" + +# Priority mapping from 1-12 (Wazuh events) to 1-5 (Discord and ntfy notification) + +notifier_priority_1: 12, 11, 10 +notifier_priority_2: 9, 8 +notifier_priority_3: 7, 6 +notifier_priority_4: 5, 4 +notifier_priority_5: 3 ,2, 1 + +sender: "Wazuh (IDS)" +click: "https://google.com" + + +#end of yaml +... + diff --git a/wazuh-notify-go/config.yaml b/wazuh-notify-go/config.yaml index 1f9c60c..50a3302 100644 --- a/wazuh-notify-go/config.yaml +++ b/wazuh-notify-go/config.yaml @@ -16,11 +16,11 @@ excluded_agents: "999" # Priority mapping from 1-12 (Wazuh events) to 1-5 (Discord and ntfy notification) -notifier_priority_1: 12, 11, 10 -notifier_priority_2: 9, 8 -notifier_priority_3: 7, 6 -notifier_priority_4: 5, 4 -notifier_priority_5: 3 ,2, 1 +priority_1: 12, 11, 10 +priority_2: 9, 8 +priority_3: 7, 6 +priority_4: 5, 4 +priority_5: 3 ,2, 1 sender: "Wazuh (IDS)" click: "https://google.com" diff --git a/wazuh-notify-go/log/log.go b/wazuh-notify-go/log/log.go new file mode 100644 index 0000000..4864f5a --- /dev/null +++ b/wazuh-notify-go/log/log.go @@ -0,0 +1,14 @@ +package log + +import ( + "os" + "time" +) + +var f, _ = os.OpenFile("active-responses.log", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) + +func Log(message string) { + if _, err := f.WriteString("\n" + time.Now().Format(time.DateTime) + message); err != nil { + panic(err) + } +} diff --git a/wazuh-notify-go/main.go b/wazuh-notify-go/main.go index 0cbeda4..807af23 100644 --- a/wazuh-notify-go/main.go +++ b/wazuh-notify-go/main.go @@ -1,32 +1,22 @@ package main import ( - "bufio" - "encoding/json" - "os" "strings" + "wazuh-notify/log" "wazuh-notify/notification" - "wazuh-notify/types" + "wazuh-notify/services" ) -var inputParams types.Params -var wazuhData types.WazuhMessage - func main() { - initNotify() - - reader := bufio.NewReader(os.Stdin) - - json.NewDecoder(reader).Decode(&wazuhData) //todo for later - - text, _ := reader.ReadString('\n') //todo for testing - inputParams.Message = text + inputParams := services.InitNotify() for _, target := range strings.Split(inputParams.Targets, ",") { switch target { case "discord": + log.Log(target) notification.SendDiscord(inputParams) case "ntfy": + log.Log(target) notification.SendNtfy(inputParams) } } diff --git a/wazuh-notify-go/notification/discord.go b/wazuh-notify-go/notification/discord.go index 6c625c5..1c89b41 100644 --- a/wazuh-notify-go/notification/discord.go +++ b/wazuh-notify-go/notification/discord.go @@ -3,26 +3,30 @@ package notification import ( "bytes" "encoding/json" - "fmt" "log" "net/http" "os" + "strconv" "wazuh-notify/types" ) func SendDiscord(params types.Params) { - embedDescription := fmt.Sprintf("\n\n %s \n\nPriority: %x\nTags: %s\n\n%s", - params.Message, - params.Priority, - params.Tags, - params.Click, - ) + embedDescription := "\n\n" + + "**Agent:** " + params.WazuhMessage.Parameters.Alert.Agent.Name + "\n" + + "**Event id:** " + params.WazuhMessage.Parameters.Alert.Rule.ID + "\n" + + "**Description:** " + params.WazuhMessage.Parameters.Alert.Rule.Description + "\n" + + "**Threat level:** " + strconv.Itoa(params.WazuhMessage.Parameters.Alert.Rule.Level) + "\n" + + "**Times fired:** " + strconv.Itoa(params.WazuhMessage.Parameters.Alert.Rule.Firedtimes) + + "\n\n" + + "Priority: " + strconv.Itoa(params.Priority) + "\n" + + "Tags: " + params.Tags + "\n\n" + + params.Click message := types.Message{ Username: params.Sender, Embeds: []types.Embed{ { - Title: params.Destination, + Title: params.Sender, Description: embedDescription, }, }, diff --git a/wazuh-notify-go/notification/ntfy.go b/wazuh-notify-go/notification/ntfy.go index 3749d41..995a102 100644 --- a/wazuh-notify-go/notification/ntfy.go +++ b/wazuh-notify-go/notification/ntfy.go @@ -11,7 +11,12 @@ import ( func SendNtfy(params types.Params) { - payload := time.Now().Format(time.RFC3339) + "\n\n" + params.Message + payload := time.Now().Format(time.RFC3339) + "\n\n" + + "Agent: " + params.WazuhMessage.Parameters.Alert.Agent.Name + "\n" + + "Event id: " + params.WazuhMessage.Parameters.Alert.Rule.ID + "\n" + + "Description: " + params.WazuhMessage.Parameters.Alert.Rule.Description + "\n" + + "Threat level: " + strconv.Itoa(params.WazuhMessage.Parameters.Alert.Rule.Level) + "\n" + + "Times fired: " + strconv.Itoa(params.WazuhMessage.Parameters.Alert.Rule.Firedtimes) + "\n" req, _ := http.NewRequest("POST", os.Getenv("NTFY_URL"), strings.NewReader(payload)) req.Header.Set("Content-Type", "text/plain") diff --git a/wazuh-notify-go/init.go b/wazuh-notify-go/services/init.go similarity index 65% rename from wazuh-notify-go/init.go rename to wazuh-notify-go/services/init.go index 22a4bde..2f8cdb8 100644 --- a/wazuh-notify-go/init.go +++ b/wazuh-notify-go/services/init.go @@ -1,35 +1,56 @@ -package main +package services import ( + "bufio" + "encoding/json" "flag" "github.com/joho/godotenv" "gopkg.in/yaml.v2" - "log" "os" + "wazuh-notify/log" "wazuh-notify/types" ) +var inputParams types.Params var configParams types.Params +var wazuhData types.WazuhMessage -func initNotify() { +func InitNotify() types.Params { err := godotenv.Load() if err != nil { - log.Fatalf(".env not found: %v", err) - return + log.Log("env failed to load") + } else { + log.Log("env loaded") } + wazuhInput() + yamlFile, err := os.ReadFile("./config.yaml") yaml.Unmarshal(yamlFile, &configParams) - flag.StringVar(&inputParams.Server, "server", "", "is the webhook URL of the Discord server. It is stored in .env.") + log.Log("yaml loaded") + + flag.StringVar(&inputParams.Url, "url", "", "is the webhook URL of the Discord server. It is stored in .env.") flag.StringVar(&inputParams.Click, "click", configParams.Click, "is a link (URL) that can be followed by tapping/clicking inside the message. Default is https://google.com.") - flag.StringVar(&inputParams.Destination, "destination", "", "is the destination (actually the originator) of the message, either an app name or a person. Default is \"Wazuh (IDS)\"") - flag.StringVar(&inputParams.Message, "message", "", "is the text of the message to be sent. Default is \"Test message\", but may include --tags and/or --click.") flag.IntVar(&inputParams.Priority, "priority", 0, "is the priority of the message, ranging from 1 (highest), to 5 (lowest). Default is 5.") flag.StringVar(&inputParams.Sender, "sender", configParams.Sender, "is the sender of the message, either an app name or a person. The default is \"Security message\".") flag.StringVar(&inputParams.Tags, "tags", "", "is an arbitrary strings of tags (keywords), seperated by a \",\" (comma). Default is \"informational,testing,hard-coded\".") flag.StringVar(&inputParams.Targets, "targets", "", "is a list of targets to send notifications to. Default is \"discord\".") flag.Parse() + + log.Log("yaml loaded") inputParams.Targets = configParams.Targets + + return inputParams +} + +func wazuhInput() { + reader := bufio.NewReader(os.Stdin) + + json.NewDecoder(reader).Decode(&wazuhData) + + mapPriority() + + inputParams.WazuhMessage = wazuhData } diff --git a/wazuh-notify-go/services/mapping.go b/wazuh-notify-go/services/mapping.go new file mode 100644 index 0000000..90a1219 --- /dev/null +++ b/wazuh-notify-go/services/mapping.go @@ -0,0 +1,21 @@ +package services + +import "slices" + +func mapPriority() { + if slices.Contains(configParams.Priority1, wazuhData.Parameters.Alert.Rule.Level) { + inputParams.Priority = wazuhData.Parameters.Alert.Rule.Level + } + if slices.Contains(configParams.Priority2, wazuhData.Parameters.Alert.Rule.Level) { + inputParams.Priority = wazuhData.Parameters.Alert.Rule.Level + } + if slices.Contains(configParams.Priority3, wazuhData.Parameters.Alert.Rule.Level) { + inputParams.Priority = wazuhData.Parameters.Alert.Rule.Level + } + if slices.Contains(configParams.Priority4, wazuhData.Parameters.Alert.Rule.Level) { + inputParams.Priority = wazuhData.Parameters.Alert.Rule.Level + } + if slices.Contains(configParams.Priority5, wazuhData.Parameters.Alert.Rule.Level) { + inputParams.Priority = wazuhData.Parameters.Alert.Rule.Level + } +} diff --git a/wazuh-notify-go/types/types.go b/wazuh-notify-go/types/types.go index 7c2ac96..cb2970c 100644 --- a/wazuh-notify-go/types/types.go +++ b/wazuh-notify-go/types/types.go @@ -1,14 +1,18 @@ package types type Params struct { - Server string - Sender string `yaml:"sender,omitempty"` - Destination string - Priority int - Message string - Tags string - Click string `yaml:"click,omitempty"` - Targets string `yaml:"targets,omitempty"` + Url string + Sender string `yaml:"sender,omitempty"` + Priority int + Tags string + Click string `yaml:"click,omitempty"` + Targets string `yaml:"targets,omitempty"` + WazuhMessage WazuhMessage + Priority1 []int `yaml:"priority_1"` + Priority2 []int `yaml:"priority_2"` + Priority3 []int `yaml:"priority_3"` + Priority4 []int `yaml:"priority_4"` + Priority5 []int `yaml:"priority_5"` } type Message struct { diff --git a/wazuh_notifier_module.py b/wazuh_notifier_module.py index f8f2c0f..e6de6ad 100755 --- a/wazuh_notifier_module.py +++ b/wazuh_notifier_module.py @@ -45,7 +45,7 @@ def set_environment(): wazuh_path = "/var/ossec" # wazuh_path = os.path.abspath(os.path.join(__file__, "../../..")) ar_path = '{0}/logs/active-responses.log'.format(wazuh_path) - config_path = 'wazuh-notifier-config.yaml'.format(wazuh_path) + config_path = 'wazuh-notifier-conf.yaml'.format(wazuh_path) return wazuh_path, ar_path, config_path From e1b005ce197209e089106330f2ca5f4766cb1c56 Mon Sep 17 00:00:00 2001 From: darius Date: Thu, 9 May 2024 17:52:16 +0200 Subject: [PATCH 10/33] file location fix --- wazuh-notify-go/log/log.go | 9 +++++++-- wazuh-notify-go/notification/discord.go | 16 ++++++++++++++++ wazuh-notify-go/services/init.go | 20 +++++++++++++++++--- wazuh-notify-go/types/types.go | 2 +- 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/wazuh-notify-go/log/log.go b/wazuh-notify-go/log/log.go index 4864f5a..ebd9eab 100644 --- a/wazuh-notify-go/log/log.go +++ b/wazuh-notify-go/log/log.go @@ -2,13 +2,18 @@ package log import ( "os" + "path" "time" ) -var f, _ = os.OpenFile("active-responses.log", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) +var logFile *os.File + +func OpenLogFile(BasePath string) { + logFile, _ = os.OpenFile(path.Join(BasePath, "../../log/active-responses.log"), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) +} func Log(message string) { - if _, err := f.WriteString("\n" + time.Now().Format(time.DateTime) + message); err != nil { + if _, err := logFile.WriteString("\n" + message + ": " + time.Now().String()); err != nil { panic(err) } } diff --git a/wazuh-notify-go/notification/discord.go b/wazuh-notify-go/notification/discord.go index 1c89b41..2620f7a 100644 --- a/wazuh-notify-go/notification/discord.go +++ b/wazuh-notify-go/notification/discord.go @@ -22,12 +22,28 @@ func SendDiscord(params types.Params) { "Tags: " + params.Tags + "\n\n" + params.Click + var color int + + switch params.Priority { + case 1: + color = 0x339900 + case 2: + color = 0x99cc33 + case 3: + color = 0xffcc00 + case 4: + color = 0xff9966 + case 5: + color = 0xcc3300 + } + message := types.Message{ Username: params.Sender, Embeds: []types.Embed{ { Title: params.Sender, Description: embedDescription, + Color: color, }, }, } diff --git a/wazuh-notify-go/services/init.go b/wazuh-notify-go/services/init.go index 2f8cdb8..4817740 100644 --- a/wazuh-notify-go/services/init.go +++ b/wazuh-notify-go/services/init.go @@ -7,6 +7,8 @@ import ( "github.com/joho/godotenv" "gopkg.in/yaml.v2" "os" + "path" + "runtime" "wazuh-notify/log" "wazuh-notify/types" ) @@ -14,18 +16,30 @@ import ( var inputParams types.Params var configParams types.Params var wazuhData types.WazuhMessage +var BasePath string func InitNotify() types.Params { - err := godotenv.Load() + _, currentFile, _, _ := runtime.Caller(1) + + BasePath = path.Dir(currentFile) + + log.OpenLogFile(BasePath) + + err := godotenv.Load(path.Join(BasePath, "../../etc/.env")) if err != nil { log.Log("env failed to load") + godotenv.Load(path.Join(BasePath, "/.env")) } else { log.Log("env loaded") } wazuhInput() - yamlFile, err := os.ReadFile("./config.yaml") + yamlFile, err := os.ReadFile(path.Join(BasePath, "../../etc/config.yaml")) + if err != nil { + log.Log("yaml failed to load") + yamlFile, err = os.ReadFile(path.Join(BasePath, "config.yaml")) + } yaml.Unmarshal(yamlFile, &configParams) log.Log("yaml loaded") @@ -39,7 +53,7 @@ func InitNotify() types.Params { flag.Parse() - log.Log("yaml loaded") + log.Log("params loaded") inputParams.Targets = configParams.Targets return inputParams diff --git a/wazuh-notify-go/types/types.go b/wazuh-notify-go/types/types.go index cb2970c..75a77e2 100644 --- a/wazuh-notify-go/types/types.go +++ b/wazuh-notify-go/types/types.go @@ -25,5 +25,5 @@ type Message struct { type Embed struct { Title string `json:"title,omitempty"` Description string `json:"description,omitempty"` - Color string `json:"color,omitempty"` + Color int `json:"color,omitempty"` } From ab40f40c00024bbc333d359128538614d68bc5c2 Mon Sep 17 00:00:00 2001 From: darius Date: Thu, 9 May 2024 17:59:21 +0200 Subject: [PATCH 11/33] log improve --- wazuh-notify-go/log/log.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/wazuh-notify-go/log/log.go b/wazuh-notify-go/log/log.go index ebd9eab..fac490a 100644 --- a/wazuh-notify-go/log/log.go +++ b/wazuh-notify-go/log/log.go @@ -10,6 +10,14 @@ var logFile *os.File func OpenLogFile(BasePath string) { logFile, _ = os.OpenFile(path.Join(BasePath, "../../log/active-responses.log"), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) + _, err := logFile.WriteString( + "\n#######################################\n## START ##" + + "\n" + time.Now().String() + + "\n#######################################\n", + ) + if err != nil { + panic(err) + } } func Log(message string) { From 5e5d3003929064f7f25f714c7ff447e54ee276f7 Mon Sep 17 00:00:00 2001 From: Rudi Klein Date: Thu, 9 May 2024 18:51:16 +0200 Subject: [PATCH 12/33] semi-final python go final --- wazuh-notify-go/.env | 2 +- wazuh-notify-go/log/log.go | 2 +- wazuh-notify-go/services/init.go | 4 +- .../{config.yaml => wazuh-notify-config.yaml} | 0 wazuh_notifier_module.py | 43 +++++++++++-------- 5 files changed, 29 insertions(+), 22 deletions(-) rename wazuh-notify-go/{config.yaml => wazuh-notify-config.yaml} (100%) diff --git a/wazuh-notify-go/.env b/wazuh-notify-go/.env index c7bbc35..cecf3fc 100644 --- a/wazuh-notify-go/.env +++ b/wazuh-notify-go/.env @@ -1,2 +1,2 @@ -DISCORD_WEBHOOK=https://discord.com/api/webhooks/1237526475306176572/kHGnaQiM8qWOfdLIN1LWqgq3dsfqiHtsfs-Z5FralJNdX5hdw-MOPf4zzIDiFVjcIat4 +DISCORD_URL=https://discord.com/api/webhooks/1237526475306176572/kHGnaQiM8qWOfdLIN1LWqgq3dsfqiHtsfs-Z5FralJNdX5hdw-MOPf4zzIDiFVjcIat4 NTFY_URL=https://ntfy.sh/__KleinTest \ No newline at end of file diff --git a/wazuh-notify-go/log/log.go b/wazuh-notify-go/log/log.go index fac490a..3ebf948 100644 --- a/wazuh-notify-go/log/log.go +++ b/wazuh-notify-go/log/log.go @@ -9,7 +9,7 @@ import ( var logFile *os.File func OpenLogFile(BasePath string) { - logFile, _ = os.OpenFile(path.Join(BasePath, "../../log/active-responses.log"), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) + logFile, _ = os.OpenFile(path.Join(BasePath, "../../logs/active-responses.log"), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) _, err := logFile.WriteString( "\n#######################################\n## START ##" + "\n" + time.Now().String() + diff --git a/wazuh-notify-go/services/init.go b/wazuh-notify-go/services/init.go index 4817740..163abdb 100644 --- a/wazuh-notify-go/services/init.go +++ b/wazuh-notify-go/services/init.go @@ -35,10 +35,10 @@ func InitNotify() types.Params { wazuhInput() - yamlFile, err := os.ReadFile(path.Join(BasePath, "../../etc/config.yaml")) + yamlFile, err := os.ReadFile(path.Join(BasePath, "../../etc/wazuh-notify-config.yaml")) if err != nil { log.Log("yaml failed to load") - yamlFile, err = os.ReadFile(path.Join(BasePath, "config.yaml")) + yamlFile, err = os.ReadFile(path.Join(BasePath, "wazuh-notify-config.yaml")) } yaml.Unmarshal(yamlFile, &configParams) diff --git a/wazuh-notify-go/config.yaml b/wazuh-notify-go/wazuh-notify-config.yaml similarity index 100% rename from wazuh-notify-go/config.yaml rename to wazuh-notify-go/wazuh-notify-config.yaml diff --git a/wazuh_notifier_module.py b/wazuh_notifier_module.py index e6de6ad..6e7a9b4 100755 --- a/wazuh_notifier_module.py +++ b/wazuh_notifier_module.py @@ -16,8 +16,8 @@ def get_env(): raise Exception(dotenv_path, "file not found") # Retrieve url from .env - discord_url = os.getenv("DISCORD_url") - ntfy_url = os.getenv("NTFY_url") + discord_url = os.getenv("DISCORD_URL") + ntfy_url = os.getenv("NTFY_URL") except Exception as err: # output error, and return with an error code @@ -26,6 +26,7 @@ def get_env(): return discord_url, ntfy_url + # Set structured timestamp for logging and discord/ntfy message. @@ -37,7 +38,7 @@ def set_time(): # Define paths: wazuh_path = wazuh root directory # ar_path = active-responses.log path, -# config_path = wazuh-notifier-config.yaml +# config_path = wazuh-notifier-wazuh-notify-config.yaml def set_environment(): # todo fix reference when running manually/in process @@ -45,12 +46,12 @@ def set_environment(): wazuh_path = "/var/ossec" # wazuh_path = os.path.abspath(os.path.join(__file__, "../../..")) ar_path = '{0}/logs/active-responses.log'.format(wazuh_path) - config_path = 'wazuh-notifier-conf.yaml'.format(wazuh_path) + config_path = 'wazuh-notifier-wazuh-notify-config.yaml'.format(wazuh_path) return wazuh_path, ar_path, config_path -# Import configuration settings from wazuh-notifier-config.yaml +# Import configuration settings from wazuh-notifier-wazuh-notify-config.yaml def import_config(): @@ -64,7 +65,7 @@ def import_config(): return None -# Show configuration settings from wazuh-notifier-config.yaml +# Show configuration settings from wazuh-notifier-wazuh-notify-config.yaml def view_config(): @@ -95,33 +96,39 @@ def threat_priority_mapping(threat_level, np_1, np_2, np_3, np_4, np_5): if threat_level in np_1: priority_mapping = "1" + priority_color = 0x339900 elif threat_level in np_2: priority_mapping = "2" + priority_color = 0x99cc33 elif threat_level in np_3: priority_mapping = "3" + priority_color = 0xffcc00 elif threat_level in np_4: priority_mapping = "4" + priority_color = 0xff9966 elif threat_level in np_5: priority_mapping = "5" + priority_color = 0xcc3300 else: priority_mapping = "3" + priority_color = 0xffcc00 - return priority_mapping + return priority_mapping, priority_color def get_yaml_config(): config = import_config() - config['np_1'] = "3, 3, 3" if (config.get("notifier_priority_1") is None) else config.get("notifier_priority_1") - config['np_2'] = "4, 5" if (config.get("notifier_priority_2") is None) else config.get("notifier_priority_2") - config['np_3'] = "6, 7" if (config.get("notifier_priority_3") is None) else config.get("notifier_priority_3") - config['np_4'] = "8, 9" if (config.get("notifier_priority_4") is None) else config.get("notifier_priority_4") - config['np_5'] = "10, 11, 12" if (config.get("notifier_priority_5") is None) else config.get("notifier_priority_5") - config['targets'] = "ntfy, discord" if (config.get("targets") is None) else config.get("targets") - config['excluded_rules'] = "" if (config.get("excluded_rules") is None) else config.get("excluded_rules") - config['excluded_agents'] = "" if (config.get("excluded_agents") is None) else config.get("excluded_agents") - config['sender'] = "Wazuh (IDS)" if (config.get("sender") is None) else config.get("sender") - config['click'] = "https://wazuh.org" if (config.get("click") is None) else config.get("click") + config['np_1'] = config.get('np_1', '1, 2, 3') + config['np_2'] = config.get('np_2', '4,5') + config['np_3'] = config.get('np_3', '6,7') + config['np_4'] = config.get('np_4', '8,9') + config['np_5'] = config.get('np_5', '10, 11, 12') + config['targets'] = config.get('targets', 'ntfy, discord') + config['excluded_rules'] = config.get('excluded_rules', '') + config['excluded_agents'] = config.get('excluded_agents', '') + config['sender'] = 'Wazuh (IDS)' + config['click'] = 'https://wazuh.org' return config @@ -154,7 +161,7 @@ def get_arguments(): argument_list: list = sys.argv[1:] if not argument_list: - pass + return url, sender, destination, message, priority, tags, click else: From 69ebc0fca30203a0ff7009df4538b23115532c92 Mon Sep 17 00:00:00 2001 From: darius Date: Thu, 9 May 2024 19:03:34 +0200 Subject: [PATCH 13/33] hotfix --- wazuh-notify-go/notification/discord.go | 2 +- wazuh-notify-go/services/init.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wazuh-notify-go/notification/discord.go b/wazuh-notify-go/notification/discord.go index 2620f7a..372d8b2 100644 --- a/wazuh-notify-go/notification/discord.go +++ b/wazuh-notify-go/notification/discord.go @@ -55,7 +55,7 @@ func SendDiscord(params types.Params) { return } - _, err = http.Post(os.Getenv("DISCORD_WEBHOOK"), "application/json", payload) + _, err = http.Post(os.Getenv("DISCORD_URL"), "application/json", payload) if err != nil { log.Fatalf("An Error Occured %v", err) } diff --git a/wazuh-notify-go/services/init.go b/wazuh-notify-go/services/init.go index 163abdb..cdb01d5 100644 --- a/wazuh-notify-go/services/init.go +++ b/wazuh-notify-go/services/init.go @@ -28,7 +28,7 @@ func InitNotify() types.Params { err := godotenv.Load(path.Join(BasePath, "../../etc/.env")) if err != nil { log.Log("env failed to load") - godotenv.Load(path.Join(BasePath, "/.env")) + godotenv.Load(path.Join(BasePath, ".env")) } else { log.Log("env loaded") } From 43999ff91437fe8a4f437e68b547c56d32f9bbbe Mon Sep 17 00:00:00 2001 From: darius Date: Thu, 9 May 2024 21:00:24 +0200 Subject: [PATCH 14/33] priority + tags fix --- wazuh-notify-go/services/init.go | 9 ++++++--- wazuh-notify-go/services/mapping.go | 13 +++++++------ wazuh-notify-go/wazuh-notify-config.yaml | 10 +++++----- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/wazuh-notify-go/services/init.go b/wazuh-notify-go/services/init.go index cdb01d5..ff99215 100644 --- a/wazuh-notify-go/services/init.go +++ b/wazuh-notify-go/services/init.go @@ -9,6 +9,7 @@ import ( "os" "path" "runtime" + "strings" "wazuh-notify/log" "wazuh-notify/types" ) @@ -33,8 +34,6 @@ func InitNotify() types.Params { log.Log("env loaded") } - wazuhInput() - yamlFile, err := os.ReadFile(path.Join(BasePath, "../../etc/wazuh-notify-config.yaml")) if err != nil { log.Log("yaml failed to load") @@ -56,6 +55,8 @@ func InitNotify() types.Params { log.Log("params loaded") inputParams.Targets = configParams.Targets + wazuhInput() + return inputParams } @@ -64,7 +65,9 @@ func wazuhInput() { json.NewDecoder(reader).Decode(&wazuhData) - mapPriority() + inputParams.Priority = mapPriority() + + inputParams.Tags += strings.Join(wazuhData.Parameters.Alert.Rule.Groups, ",") inputParams.WazuhMessage = wazuhData } diff --git a/wazuh-notify-go/services/mapping.go b/wazuh-notify-go/services/mapping.go index 90a1219..051ff0f 100644 --- a/wazuh-notify-go/services/mapping.go +++ b/wazuh-notify-go/services/mapping.go @@ -2,20 +2,21 @@ package services import "slices" -func mapPriority() { +func mapPriority() int { if slices.Contains(configParams.Priority1, wazuhData.Parameters.Alert.Rule.Level) { - inputParams.Priority = wazuhData.Parameters.Alert.Rule.Level + return 1 } if slices.Contains(configParams.Priority2, wazuhData.Parameters.Alert.Rule.Level) { - inputParams.Priority = wazuhData.Parameters.Alert.Rule.Level + return 2 } if slices.Contains(configParams.Priority3, wazuhData.Parameters.Alert.Rule.Level) { - inputParams.Priority = wazuhData.Parameters.Alert.Rule.Level + return 3 } if slices.Contains(configParams.Priority4, wazuhData.Parameters.Alert.Rule.Level) { - inputParams.Priority = wazuhData.Parameters.Alert.Rule.Level + return 4 } if slices.Contains(configParams.Priority5, wazuhData.Parameters.Alert.Rule.Level) { - inputParams.Priority = wazuhData.Parameters.Alert.Rule.Level + return 5 } + return 0 } diff --git a/wazuh-notify-go/wazuh-notify-config.yaml b/wazuh-notify-go/wazuh-notify-config.yaml index 50a3302..9f2a28e 100644 --- a/wazuh-notify-go/wazuh-notify-config.yaml +++ b/wazuh-notify-go/wazuh-notify-config.yaml @@ -16,11 +16,11 @@ excluded_agents: "999" # Priority mapping from 1-12 (Wazuh events) to 1-5 (Discord and ntfy notification) -priority_1: 12, 11, 10 -priority_2: 9, 8 -priority_3: 7, 6 -priority_4: 5, 4 -priority_5: 3 ,2, 1 +priority_5: [12,11,10] +priority_4: [9,8] +priority_3: [7,6] +priority_2: [5,4] +priority_1: [3,2,1] sender: "Wazuh (IDS)" click: "https://google.com" From 123dfecadce2ad9783859e6f7d0b9f41474ffb59 Mon Sep 17 00:00:00 2001 From: Darius Date: Thu, 9 May 2024 23:11:41 +0200 Subject: [PATCH 15/33] test --- wazuh-notify-go/log/log.go | 1 - wazuh-notify-go/services/init.go | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/wazuh-notify-go/log/log.go b/wazuh-notify-go/log/log.go index 3ebf948..2633ce4 100644 --- a/wazuh-notify-go/log/log.go +++ b/wazuh-notify-go/log/log.go @@ -2,7 +2,6 @@ package log import ( "os" - "path" "time" ) diff --git a/wazuh-notify-go/services/init.go b/wazuh-notify-go/services/init.go index ff99215..eee4aac 100644 --- a/wazuh-notify-go/services/init.go +++ b/wazuh-notify-go/services/init.go @@ -20,9 +20,7 @@ var wazuhData types.WazuhMessage var BasePath string func InitNotify() types.Params { - _, currentFile, _, _ := runtime.Caller(1) - - BasePath = path.Dir(currentFile) + BasePath, _ := os.Executable() log.OpenLogFile(BasePath) From a18ae36dfb239d66507ce42b70e61e92fd2d25b3 Mon Sep 17 00:00:00 2001 From: darius Date: Thu, 9 May 2024 23:13:00 +0200 Subject: [PATCH 16/33] fix --- wazuh-notify-go/log/log.go | 1 + wazuh-notify-go/services/init.go | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/wazuh-notify-go/log/log.go b/wazuh-notify-go/log/log.go index 2633ce4..3ebf948 100644 --- a/wazuh-notify-go/log/log.go +++ b/wazuh-notify-go/log/log.go @@ -2,6 +2,7 @@ package log import ( "os" + "path" "time" ) diff --git a/wazuh-notify-go/services/init.go b/wazuh-notify-go/services/init.go index eee4aac..1e7aea5 100644 --- a/wazuh-notify-go/services/init.go +++ b/wazuh-notify-go/services/init.go @@ -8,7 +8,6 @@ import ( "gopkg.in/yaml.v2" "os" "path" - "runtime" "strings" "wazuh-notify/log" "wazuh-notify/types" From a6504842f21ab6449a2b19cf4159e34cfd6d85a4 Mon Sep 17 00:00:00 2001 From: Darius Date: Thu, 9 May 2024 23:19:59 +0200 Subject: [PATCH 17/33] fix path --- wazuh-notify-go/services/init.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/wazuh-notify-go/services/init.go b/wazuh-notify-go/services/init.go index 1e7aea5..4111836 100644 --- a/wazuh-notify-go/services/init.go +++ b/wazuh-notify-go/services/init.go @@ -4,6 +4,7 @@ import ( "bufio" "encoding/json" "flag" + "fmt" "github.com/joho/godotenv" "gopkg.in/yaml.v2" "os" @@ -19,22 +20,23 @@ var wazuhData types.WazuhMessage var BasePath string func InitNotify() types.Params { - BasePath, _ := os.Executable() + BaseFilePath, _ := os.Executable() + BaseDirPath := path.Dir(BaseFilePath) + + log.OpenLogFile(BaseDirPath) - log.OpenLogFile(BasePath) - - err := godotenv.Load(path.Join(BasePath, "../../etc/.env")) + err := godotenv.Load(path.Join(BaseDirPath, "../../etc/.env")) if err != nil { log.Log("env failed to load") - godotenv.Load(path.Join(BasePath, ".env")) + godotenv.Load(path.Join(BaseDirPath, ".env")) } else { log.Log("env loaded") } - yamlFile, err := os.ReadFile(path.Join(BasePath, "../../etc/wazuh-notify-config.yaml")) + yamlFile, err := os.ReadFile(path.Join(BaseDirPath, "../../etc/wazuh-notify-config.yaml")) if err != nil { log.Log("yaml failed to load") - yamlFile, err = os.ReadFile(path.Join(BasePath, "wazuh-notify-config.yaml")) + yamlFile, err = os.ReadFile(path.Join(BaseDirPath, "wazuh-notify-config.yaml")) } yaml.Unmarshal(yamlFile, &configParams) From ee13db3b07c9f77f24940f8676a9a71b4fa3dc46 Mon Sep 17 00:00:00 2001 From: Darius Date: Thu, 9 May 2024 23:20:52 +0200 Subject: [PATCH 18/33] import fix --- wazuh-notify-go/services/init.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/wazuh-notify-go/services/init.go b/wazuh-notify-go/services/init.go index 4111836..82ac9b5 100644 --- a/wazuh-notify-go/services/init.go +++ b/wazuh-notify-go/services/init.go @@ -4,7 +4,6 @@ import ( "bufio" "encoding/json" "flag" - "fmt" "github.com/joho/godotenv" "gopkg.in/yaml.v2" "os" @@ -22,7 +21,7 @@ var BasePath string func InitNotify() types.Params { BaseFilePath, _ := os.Executable() BaseDirPath := path.Dir(BaseFilePath) - + log.OpenLogFile(BaseDirPath) err := godotenv.Load(path.Join(BaseDirPath, "../../etc/.env")) From 7ac4686344d5eef380edc55681540747b756d2f8 Mon Sep 17 00:00:00 2001 From: darius Date: Thu, 9 May 2024 23:27:21 +0200 Subject: [PATCH 19/33] log improve --- wazuh-notify-go/services/init.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/wazuh-notify-go/services/init.go b/wazuh-notify-go/services/init.go index 82ac9b5..f6f14a7 100644 --- a/wazuh-notify-go/services/init.go +++ b/wazuh-notify-go/services/init.go @@ -16,7 +16,6 @@ import ( var inputParams types.Params var configParams types.Params var wazuhData types.WazuhMessage -var BasePath string func InitNotify() types.Params { BaseFilePath, _ := os.Executable() @@ -40,6 +39,8 @@ func InitNotify() types.Params { yaml.Unmarshal(yamlFile, &configParams) log.Log("yaml loaded") + configParamString, _ := json.Marshal(configParams) + log.Log(string(configParamString)) flag.StringVar(&inputParams.Url, "url", "", "is the webhook URL of the Discord server. It is stored in .env.") flag.StringVar(&inputParams.Click, "click", configParams.Click, "is a link (URL) that can be followed by tapping/clicking inside the message. Default is https://google.com.") @@ -51,6 +52,9 @@ func InitNotify() types.Params { flag.Parse() log.Log("params loaded") + inputParamString, _ := json.Marshal(inputParams) + log.Log(string(inputParamString)) + inputParams.Targets = configParams.Targets wazuhInput() @@ -68,4 +72,8 @@ func wazuhInput() { inputParams.Tags += strings.Join(wazuhData.Parameters.Alert.Rule.Groups, ",") inputParams.WazuhMessage = wazuhData + + log.Log("Wazuh data loaded") + inputParamString, _ := json.Marshal(inputParams) + log.Log(string(inputParamString)) } From 184622988d92c0baad45cd82dc317875869aef1b Mon Sep 17 00:00:00 2001 From: Darius Date: Thu, 9 May 2024 23:34:39 +0200 Subject: [PATCH 20/33] add log file close back --- wazuh-notify-go/log/log.go | 12 ++++++++++++ wazuh-notify-go/main.go | 1 + 2 files changed, 13 insertions(+) diff --git a/wazuh-notify-go/log/log.go b/wazuh-notify-go/log/log.go index 3ebf948..6597be4 100644 --- a/wazuh-notify-go/log/log.go +++ b/wazuh-notify-go/log/log.go @@ -20,6 +20,18 @@ func OpenLogFile(BasePath string) { } } +func CloseLogFile() { + _, err := logFile.WriteString( + "\n\n#######################################\n## CLOSE ##" + + "\n" + time.Now().String() + + "\n#######################################\n", + ) + if err != nil { + panic(err) + } + logFile.Close() +} + func Log(message string) { if _, err := logFile.WriteString("\n" + message + ": " + time.Now().String()); err != nil { panic(err) diff --git a/wazuh-notify-go/main.go b/wazuh-notify-go/main.go index 807af23..d0a2980 100644 --- a/wazuh-notify-go/main.go +++ b/wazuh-notify-go/main.go @@ -20,4 +20,5 @@ func main() { notification.SendNtfy(inputParams) } } + log.CloseLogFile() } From 5b383ef32228f5a3223df19f7123e3f3cd57c5f8 Mon Sep 17 00:00:00 2001 From: Rudi Klein Date: Fri, 10 May 2024 13:21:54 +0200 Subject: [PATCH 21/33] logfile --- wazuh-notify-go/wazuh-notify-config.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wazuh-notify-go/wazuh-notify-config.yaml b/wazuh-notify-go/wazuh-notify-config.yaml index 50a3302..4f29bd8 100644 --- a/wazuh-notify-go/wazuh-notify-config.yaml +++ b/wazuh-notify-go/wazuh-notify-config.yaml @@ -16,11 +16,11 @@ excluded_agents: "999" # Priority mapping from 1-12 (Wazuh events) to 1-5 (Discord and ntfy notification) -priority_1: 12, 11, 10 -priority_2: 9, 8 -priority_3: 7, 6 -priority_4: 5, 4 -priority_5: 3 ,2, 1 +priority_5: [ 15,14,13,12 ] +priority_4: [ 11,10,9 ] +priority_3: [ 8,7,6 ] +priority_2: [ 5,4 ] +priority_1: [ 3,2,1,0 ] sender: "Wazuh (IDS)" click: "https://google.com" From 7e95376a2206f0f4b026034b1ca14c766d4521c2 Mon Sep 17 00:00:00 2001 From: Rudi Klein Date: Fri, 10 May 2024 13:23:28 +0200 Subject: [PATCH 22/33] logfile --- wazuh-notify-go/wazuh-notify-config.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/wazuh-notify-go/wazuh-notify-config.yaml b/wazuh-notify-go/wazuh-notify-config.yaml index 4f29bd8..0721c5f 100644 --- a/wazuh-notify-go/wazuh-notify-config.yaml +++ b/wazuh-notify-go/wazuh-notify-config.yaml @@ -4,10 +4,8 @@ # This is the yaml config file for both the wazuh-ntfy-notifier.py and wazuh-discord-notifier.py. # The yaml needs to be in the same folder as the wazuh-ntfy-notifier.py and wazuh-discord-notifier.py -# COMMON (custom-wazuh-notifiers.py) configuration settings start here. -# 1 = messages will be sent through this message server. 0 = messages will NOT be sent through this message server. - targets: "discord,ntfy" +full_message: "discord,ntfy" # Exclude rules that are listed in the ossec.conf active response definition. From cc7f93ba64fb963c2a8a0bc355a2ad638aede241 Mon Sep 17 00:00:00 2001 From: darius Date: Fri, 10 May 2024 14:23:54 +0200 Subject: [PATCH 23/33] exclude rules added full message added --- wazuh-notify-go/notification/discord.go | 42 ++++++++++++++++++------ wazuh-notify-go/services/filters.go | 24 ++++++++++++++ wazuh-notify-go/services/init.go | 5 +++ wazuh-notify-go/types/types.go | 27 ++++++++------- wazuh-notify-go/wazuh-notify-config.yaml | 12 +++---- 5 files changed, 82 insertions(+), 28 deletions(-) create mode 100644 wazuh-notify-go/services/filters.go diff --git a/wazuh-notify-go/notification/discord.go b/wazuh-notify-go/notification/discord.go index 372d8b2..aa46484 100644 --- a/wazuh-notify-go/notification/discord.go +++ b/wazuh-notify-go/notification/discord.go @@ -6,21 +6,43 @@ import ( "log" "net/http" "os" + "slices" "strconv" + "strings" "wazuh-notify/types" ) func SendDiscord(params types.Params) { - embedDescription := "\n\n" + - "**Agent:** " + params.WazuhMessage.Parameters.Alert.Agent.Name + "\n" + - "**Event id:** " + params.WazuhMessage.Parameters.Alert.Rule.ID + "\n" + - "**Description:** " + params.WazuhMessage.Parameters.Alert.Rule.Description + "\n" + - "**Threat level:** " + strconv.Itoa(params.WazuhMessage.Parameters.Alert.Rule.Level) + "\n" + - "**Times fired:** " + strconv.Itoa(params.WazuhMessage.Parameters.Alert.Rule.Firedtimes) + - "\n\n" + - "Priority: " + strconv.Itoa(params.Priority) + "\n" + - "Tags: " + params.Tags + "\n\n" + - params.Click + + var embedDescription string + + if slices.Contains(strings.Split(params.FullMessage, ","), "discord") { + fullMessage, _ := json.MarshalIndent(params.WazuhMessage, "", " ") + fullMessageString := strings.ReplaceAll(string(fullMessage), `"`, "") + fullMessageString = strings.ReplaceAll(fullMessageString, "{", "") + fullMessageString = strings.ReplaceAll(fullMessageString, "}", "") + fullMessageString = strings.ReplaceAll(fullMessageString, "[", "") + fullMessageString = strings.ReplaceAll(fullMessageString, "]", "") + fullMessageString = strings.ReplaceAll(fullMessageString, " ,", "") + + embedDescription = "\n\n ```" + + fullMessageString + + "```\n\n" + + "Priority: " + strconv.Itoa(params.Priority) + "\n" + + "Tags: " + params.Tags + "\n\n" + + params.Click + } else { + embedDescription = "\n\n" + + "**Agent:** " + params.WazuhMessage.Parameters.Alert.Agent.Name + "\n" + + "**Event id:** " + params.WazuhMessage.Parameters.Alert.Rule.ID + "\n" + + "**Description:** " + params.WazuhMessage.Parameters.Alert.Rule.Description + "\n" + + "**Threat level:** " + strconv.Itoa(params.WazuhMessage.Parameters.Alert.Rule.Level) + "\n" + + "**Times fired:** " + strconv.Itoa(params.WazuhMessage.Parameters.Alert.Rule.Firedtimes) + + "\n\n" + + "Priority: " + strconv.Itoa(params.Priority) + "\n" + + "Tags: " + params.Tags + "\n\n" + + params.Click + } var color int diff --git a/wazuh-notify-go/services/filters.go b/wazuh-notify-go/services/filters.go new file mode 100644 index 0000000..8f630b3 --- /dev/null +++ b/wazuh-notify-go/services/filters.go @@ -0,0 +1,24 @@ +package services + +import ( + "os" + "strings" + "wazuh-notify/log" +) + +func Filter() { + for _, rule := range strings.Split(inputParams.ExcludedRules, ",") { + if rule == inputParams.WazuhMessage.Parameters.Alert.Rule.ID { + log.Log("rule excluded") + log.CloseLogFile() + os.Exit(0) + } + } + for _, agent := range strings.Split(inputParams.ExcludedAgents, ",") { + if agent == inputParams.WazuhMessage.Parameters.Alert.Agent.ID { + log.Log("agent excluded") + log.CloseLogFile() + os.Exit(0) + } + } +} diff --git a/wazuh-notify-go/services/init.go b/wazuh-notify-go/services/init.go index f6f14a7..df7fd29 100644 --- a/wazuh-notify-go/services/init.go +++ b/wazuh-notify-go/services/init.go @@ -56,6 +56,9 @@ func InitNotify() types.Params { log.Log(string(inputParamString)) inputParams.Targets = configParams.Targets + inputParams.FullMessage = configParams.FullMessage + inputParams.ExcludedAgents = configParams.ExcludedAgents + inputParams.ExcludedRules = configParams.ExcludedRules wazuhInput() @@ -73,6 +76,8 @@ func wazuhInput() { inputParams.WazuhMessage = wazuhData + Filter() + log.Log("Wazuh data loaded") inputParamString, _ := json.Marshal(inputParams) log.Log(string(inputParamString)) diff --git a/wazuh-notify-go/types/types.go b/wazuh-notify-go/types/types.go index 75a77e2..6293259 100644 --- a/wazuh-notify-go/types/types.go +++ b/wazuh-notify-go/types/types.go @@ -1,18 +1,21 @@ package types type Params struct { - Url string - Sender string `yaml:"sender,omitempty"` - Priority int - Tags string - Click string `yaml:"click,omitempty"` - Targets string `yaml:"targets,omitempty"` - WazuhMessage WazuhMessage - Priority1 []int `yaml:"priority_1"` - Priority2 []int `yaml:"priority_2"` - Priority3 []int `yaml:"priority_3"` - Priority4 []int `yaml:"priority_4"` - Priority5 []int `yaml:"priority_5"` + Url string + Sender string `yaml:"sender,omitempty"` + Priority int + Tags string + Click string `yaml:"click,omitempty"` + Targets string `yaml:"targets,omitempty"` + FullMessage string `yaml:"full_message,omitempty"` + ExcludedRules string `yaml:"excluded_rules,omitempty"` + ExcludedAgents string `yaml:"excluded_agents,omitempty"` + WazuhMessage WazuhMessage + Priority1 []int `yaml:"priority_1"` + Priority2 []int `yaml:"priority_2"` + Priority3 []int `yaml:"priority_3"` + Priority4 []int `yaml:"priority_4"` + Priority5 []int `yaml:"priority_5"` } type Message struct { diff --git a/wazuh-notify-go/wazuh-notify-config.yaml b/wazuh-notify-go/wazuh-notify-config.yaml index 0721c5f..e3ea1f6 100644 --- a/wazuh-notify-go/wazuh-notify-config.yaml +++ b/wazuh-notify-go/wazuh-notify-config.yaml @@ -9,16 +9,16 @@ full_message: "discord,ntfy" # Exclude rules that are listed in the ossec.conf active response definition. -excluded_rules: "5401, 5403" +excluded_rules: "5401,5403" excluded_agents: "999" # Priority mapping from 1-12 (Wazuh events) to 1-5 (Discord and ntfy notification) -priority_5: [ 15,14,13,12 ] -priority_4: [ 11,10,9 ] -priority_3: [ 8,7,6 ] -priority_2: [ 5,4 ] -priority_1: [ 3,2,1,0 ] +priority_5: [15,14,13,12] +priority_4: [11,10,9] +priority_3: [8,7,6] +priority_2: [5,4] +priority_1: [3,2,1,0] sender: "Wazuh (IDS)" click: "https://google.com" From 1cf27eb64f3d3a788c6c36b54960d71e8143861c Mon Sep 17 00:00:00 2001 From: Rudi Klein Date: Sat, 11 May 2024 19:59:49 +0200 Subject: [PATCH 24/33] after active-response fixes --- wazuh-active-response.py | 305 +++++++++++--------------------------- wazuh-discord-notifier.py | 85 +++-------- wazuh-notifier-conf.yaml | 31 ---- wazuh-notify-config.yaml | 32 ++++ wazuh_notifier_module.py | 205 ++++++++++++++++++------- 5 files changed, 288 insertions(+), 370 deletions(-) delete mode 100755 wazuh-notifier-conf.yaml create mode 100755 wazuh-notify-config.yaml diff --git a/wazuh-active-response.py b/wazuh-active-response.py index 0a1a615..8e54941 100755 --- a/wazuh-active-response.py +++ b/wazuh-active-response.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 - # This script is adapted version of the Python active response script sample, provided by Wazuh, in the documentation: # https://documentation.wazuh.com/current/user-manual/capabilities/active-response/custom-active-response-scripts.html # It is provided under the below copyright statement: @@ -13,244 +12,116 @@ # License (version 2) as published by the FSF - Free Software # Foundation. # -# This version has changes in -# 1) the first lines of code with the assignments, and -# 2) the Start Custom Action Add section # This adapted version is free software. Rudi Klein, april 2024 -import datetime -import json import os import sys -from pathlib import PureWindowsPath, PurePosixPath -from wazuh_notifier_module import import_config as ic -from wazuh_notifier_module import set_environment as se +from wazuh_notifier_module import construct_basic_message +from wazuh_notifier_module import get_config +from wazuh_notifier_module import parameters_deconstruct +from wazuh_notifier_module import set_environment +from wazuh_notifier_module import threat_mapping -# Some variable assignments +# Path variable assignments -wazuh_path, ar_path, config_path = se() - -ADD_COMMAND = 0 -DELETE_COMMAND = 1 -CONTINUE_COMMAND = 2 -ABORT_COMMAND = 3 - -OS_SUCCESS = 0 -OS_INVALID = -1 - - -class Message: - - def __init__(self): - self.alert = "" - self.command = 0 - - -def write_debug_file(ar_name, msg): - with open(ar_path, mode="a") as log_file: - ar_name_posix = str(PurePosixPath(PureWindowsPath(ar_name[ar_name.find("active-response"):]))) - log_file.write( - str(datetime.datetime.now().strftime('%Y/%m/%d %H:%M:%S')) + " " + ar_name_posix + ": " + msg + "\n") - - -def setup_and_check_message(argv): - # get alert from stdin - input_str = "" - for line in sys.stdin: - input_str = line - break - - write_debug_file(argv[0], input_str) - - try: - data = json.loads(input_str) - except ValueError: - write_debug_file(argv[0], 'Decoding JSON has failed, invalid input format') - Message.command = OS_INVALID - return Message - - Message.alert = data - - command = data.get("command") - - if command == "add": - Message.command = ADD_COMMAND - elif command == "delete": - Message.command = DELETE_COMMAND - else: - Message.command = OS_INVALID - write_debug_file(argv[0], 'Not valid command: ' + command) - - return Message - - -def send_keys_and_check_message(argv, keys): - # build and send message with keys - keys_msg = json.dumps( - {"version": 1, "origin": {"name": argv[0], "module": "active-response"}, "command": "check_keys", - "parameters": {"keys": keys}}) - - write_debug_file(argv[0], keys_msg) - - print(keys_msg) - sys.stdout.flush() - - # read the response of previous message - input_str = "" - while True: - line = sys.stdin.readline() - if line: - input_str = line - break - - write_debug_file(argv[0], input_str) - - try: - data = json.loads(input_str) - except ValueError: - write_debug_file(argv[0], 'Decoding JSON has failed, invalid input format') - return Message - - action = data.get("command") - - if "continue" == action: - ret = CONTINUE_COMMAND - elif "abort" == action: - ret = ABORT_COMMAND - else: - ret = OS_INVALID - write_debug_file(argv[0], "Invalid value of 'command'") - - return ret - - -def parameters_deconstruct(argv, event_keys): - a_id: str = str(event_keys["agent"]["id"]) - a_name: str = str(event_keys["agent"]["name"]) - e_id: str = str(event_keys["rule"]["id"]) - e_description: str = str(event_keys["rule"]["description"]) - e_level: str = str(event_keys["rule"]["level"]) - e_fired_times: str = str(event_keys["rule"]["firedtimes"]) - e_full_event: str = str(json.dumps(event_keys, indent=0).replace('"', '') - .replace('{', '') - .replace('}', '') - .replace('[', '') - .replace(']', '') - .replace(',', '') - .replace(' ', '') - ) - - if e_id in ic("excluded_rules") or a_id in ic("excluded_agents"): - - write_debug_file(argv[0], "Excluded rule or agent: " + e_id + "/" + a_id) - - else: - - return a_id, a_name, e_id, e_description, e_level, e_fired_times, e_full_event - - -def construct_basic_message(argv, accent: str, a_id: str, a_name: str, e_id: str, e_description: str, e_level: str, - e_fired_times: str): - # Adding the BOLD text string to the Discord message. Ntfy has a different message format. - - basic_message: str = ("--message " + '"' + - accent + "Agent: " + accent + a_name + " (" + a_id + ")" + "\n" + - accent + "Event id: " + accent + e_id + "\n" + - accent + "Description: " + accent + e_description + "\n" + - accent + "Threat level: " + accent + e_level + "\n" + - # Watch this last addition to the string. It should include the closing quote for the - # basic_message string. It must be closed by -> '"'. This will be done outside this function - # in order to enable another specific addition (event_full_message) in the calling procedure. - accent + "Times fired: " + accent + e_fired_times + "\n") - - return basic_message +wazuh_path, ar_path, config_path, notifier_path = set_environment() def main(argv): - write_debug_file(argv[0], "Started") # validate json and get command - msg = setup_and_check_message(argv) - if msg.command < 0: - sys.exit(OS_INVALID) + # data = load_message(argv) + # This example event can be used for troubleshooting. Comment out the line above and uncomment the line below. + data: dict = {"version": 1, "origin": {"name": "worker01", "module": "wazuh-execd"}, "command": "add", + "parameters": {"extra_args": [], "alert": {"timestamp": "2021-02-01T20:58:44.830+0000", + "rule": {"level": 15, + "description": "Shellshock attack detected", + "id": "31168", "mitre": {"id": ["T1068", "T1190"], + "tactic": [ + "Privilege Escalation", + "Initial Access"], + "technique": [ + "Exploitation for Privilege Escalation", + "Exploit Public-Facing Application"]}, + "info": "CVE-2014-6271https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-6271", + "firedtimes": 2, "mail": "true", + "groups": ["web", "accesslog", "attack"], + "pci_dss": ["11.4"], "gdpr": ["IV_35.7.d"], + "nist_800_53": ["SI.4"], + "tsc": ["CC6.1", "CC6.8", "CC7.2", "CC7.3"]}, + "agent": {"id": "000", "name": "wazuh-server"}, + "manager": {"name": "wazuh-server"}, + "id": "1612213124.6448363", + "full_log": "192.168.0.223 - - [01/Feb/2021:20:58:43 +0000] \"GET / HTTP/1.1\" 200 612 \"-\" \"() { :; }; /bin/cat /etc/passwd\"", + "decoder": {"name": "web-accesslog"}, + "data": {"protocol": "GET", "srcip": "192.168.0.223", + "id": "200", "url": "/"}, + "location": "/var/log/nginx/access.log"}, + "program": "/var/ossec/active-response/bin/firewall-drop"}} - if msg.command == ADD_COMMAND: + alert = data["parameters"]["alert"] - """ Start Custom Key - At this point, it is necessary to select the keys from the alert and add them into the keys array. - """ + # Get the threat level from the event (message) + threat_level = data["parameters"]["alert"]["rule"]["level"] - alert = msg.alert["parameters"]["alert"] - keys = [alert["rule"]] + parameters: dict = parameters_deconstruct(argv, alert) - agent_id, agent_name, event_id, event_description, event_level, event_fired_times, event_full_message = \ - parameters_deconstruct(argv, alert) + # Get the YAML config if any + config: dict = get_config() - action = send_keys_and_check_message(argv, keys) - - # if necessary, abort execution - if action != CONTINUE_COMMAND: - - if action == ABORT_COMMAND: - write_debug_file(argv[0], "Aborted") - sys.exit(OS_SUCCESS) - else: - write_debug_file(argv[0], "Invalid command") - sys.exit(OS_INVALID) - - """ Start Custom Action Add """ - - if str(ic("discord_enabled")) == "1": - - accent = "**" - discord_notifier = '{0}/active-response/bin/wazuh-discord-notifier.py'.format(wazuh_path) - discord_exec = "python3 " + discord_notifier + " " - write_debug_file(argv[0], "Start Discord notifier") - discord_message = construct_basic_message(argv, accent, agent_id, agent_name, event_id, event_description, - event_level, event_fired_times) - - if ic("discord_full_message") == "1": - discord_message = discord_message + "\n" + accent + "__Full event__" + accent + event_full_message + '"' - else: - discord_message = discord_message + '"' - discord_command = discord_exec + discord_message - os.system(discord_command) - - if str(ic("ntfy_enabled")) == "1": - accent = "" - ntfy_notifier = '{0}/active-response/bin/wazuh-ntfy-notifier.py'.format(wazuh_path) - ntfy_exec = "python3 " + ntfy_notifier + " " - write_debug_file(argv[0], "Start NTFY notifier") - ntfy_message = construct_basic_message(argv, accent, agent_id, agent_name, event_level, event_description, - event_id, event_fired_times) - - # If the full message flag is set, the full message PLUS the closing parenthesis will be added - if ic("ntfy_full_message") == "1": - ntfy_message = ntfy_message + "\n" + "Full event" + event_full_message + '"' - else: - ntfy_message = ntfy_message + '"' - - ntfier_command = ntfy_exec + ntfy_message - os.system(ntfier_command) - - """ End Custom Action Add """ - - elif msg.command == DELETE_COMMAND: - - """ Start Custom Action Delete """ - - pass - - """ End Custom Action Delete """ + # Get the mapping between threat level (event) and priority (Discord/ntfy) + threat_priority = threat_mapping(threat_level, config.get('np_1'), config.get('np_2'), + config.get('np_3'), config.get('np_4'), config.get('np_5')) + if "discord" in config["targets"]: + accent: str = "**" + elif "ntfy" in config["targets"]: + accent: str = "" else: - write_debug_file(argv[0], "Invalid command") + accent: str = "" - write_debug_file(argv[0], "Ended") + notifier_message: str = construct_basic_message(argv, accent, + parameters.get('a_id', '000'), + parameters.get('a_name', 'agent not found'), + parameters.get('e_id', '9999'), + parameters.get('e_description', 'Event not found'), + parameters.get('e_level', '9999'), + parameters.get('e_fired_times', '3') + ) - sys.exit(OS_SUCCESS) + if "discord" in config["targets"]: + + discord_notifier: str = '{0}/active-response/bin/wazuh-discord-notifier.py'.format(wazuh_path) + discord_exec: str = "python3 " + discord_notifier + " " + + discord_message: str = notifier_message + + if "discord" in config["full_message"]: + discord_message: str = (discord_message + "\n" + accent + "__Full event__" + + accent + parameters['e_full_event'] + '"') + else: + discord_message: str = discord_message + '"' + + discord_command: str = discord_exec + discord_message + os.system(discord_command) + + if "ntfy" in config["targets"]: + + ntfy_notifier: str = '{0}/active-response/bin/wazuh-ntfy-notifier.py'.format(wazuh_path) + ntfy_exec: str = "python3 " + ntfy_notifier + " " + ntfy_message: str = notifier_message + + # If the full message flag is set, the full message PLUS the closing parenthesis will be added + if "ntfy" in config["full_message"]: + ntfy_message: str = ntfy_message + "\n" + "Full event" + parameters['e_full_event'] + '"' + + else: + ntfy_message: str = ntfy_message + '"' + + ntfy_command: str = ntfy_exec + ntfy_message + os.system(ntfy_command) if __name__ == "__main__": diff --git a/wazuh-discord-notifier.py b/wazuh-discord-notifier.py index 57758c8..4dd58e6 100755 --- a/wazuh-discord-notifier.py +++ b/wazuh-discord-notifier.py @@ -16,98 +16,51 @@ # with their friends and communities. It allows for receiving message using webhooks. # For more information: https://discord.com. -import os -from os.path import join, dirname import requests -from dotenv import load_dotenv -from wazuh_notifier_module import get_arguments as ga -from wazuh_notifier_module import get_yaml_config as yc -from wazuh_notifier_module import set_basic_defaults as bd -from wazuh_notifier_module import set_environment as se -from wazuh_notifier_module import set_time as st -from wazuh_notifier_module import threat_priority_mapping as tpm +from wazuh_notifier_module import color_mapping +from wazuh_notifier_module import get_arguments +from wazuh_notifier_module import get_config +from wazuh_notifier_module import get_env +from wazuh_notifier_module import set_environment +from wazuh_notifier_module import set_time # Get path values -wazuh_path, ar_path, config_path = se() - +wazuh_path, ar_path, config_path, notifier_path = set_environment() # Get time value -now_message, now_logging = st() +now_message, now_logging = set_time() -# Retrieve webhook from .env +# Get some paths. +discord_url, ntfy_url = get_env() -# Catching some path errors. -try: - dotenv_path = join(dirname(__file__), '.env') - load_dotenv(dotenv_path) - if not os.path.isfile(dotenv_path): - raise Exception(dotenv_path, "file not found") - - discord_webhook = os.getenv("DISCORD_WEBHOOK") - -except Exception as err: - # output error, and return with an error code - print(str(Exception(err.args))) - exit(err) +# Get the yaml config +config: dict = get_config() # the POST builder. Prepares https and sends the request. -def discord_command(n_server, n_sender, n_destination, n_priority, n_message, n_tags, n_click): +def discord_command(n_url, n_sender, n_destination, n_priority, n_message, n_tags, n_click): + color = color_mapping(n_priority) + x_message = (now_message + "\n\n" + n_message + "\n\n" + "Priority: " + n_priority + "\n" + "Tags: " + n_tags + "\n\n" + n_click ) - n_data = {"username": n_sender, "embeds": [{"description": x_message, "title": n_destination}]} + n_data = {"username": n_sender, "embeds": [{"color": color, "description": x_message, "title": n_destination}]} - requests.post(n_server, json=n_data) + requests.post(n_url, json=n_data) # Remove 1st argument from the list of command line arguments # argument_list: list = sys.argv[1:] -# Short options -options: str = "u:s:p:m:t:c:hv" - -# Long options -long_options: list = ["server=", "sender=", "destination=", "priority=", "message=", "tags=", "click=", "help", "view"] - -# Defining who I am notifier = "discord" -# Retrieve the hard-coded basic defaults. +url, sender, destination, priority, message, tags, click = get_arguments() -(d_server, d_sender, d_destination, d_priority, d_message, d_tags, d_click, d_notifier_priority_1, - d_notifier_priority_2, d_notifier_priority_3, d_notifier_priority_4, d_notifier_priority_5) = bd(notifier) - -# Use the values from the config yaml if available. Overrides the basic defaults (get_yaml_config). - -yc_args = [notifier, d_server, d_sender, d_destination, d_priority, d_message, d_tags, d_click, d_notifier_priority_1, - d_notifier_priority_2, d_notifier_priority_3, d_notifier_priority_4, d_notifier_priority_5] - -(server, sender, destination, priority, message, tags, click, notifier_priority_1, notifier_priority_2, - notifier_priority_3, notifier_priority_4, notifier_priority_5) = yc(*yc_args) - -# Get params during execution. Params found here, override minimal defaults and/or config settings. - -if ga(notifier, options, long_options) is None: - pass - # sender, destination, priority, message, tags, click = "", "", "", "", "", "" -else: - sender, destination, priority, message, tags, click = ga(notifier, options, long_options) - -# Get the threat level from the message and map it to priority - -threat_level = message[message.find('Threat level:') + 13:message.find('Threat level:') + 15].replace(" ", "") - -# Get the mapping between threat level (event) and priority (Discord/ntfy) - -# noinspection PyRedeclaration -priority = tpm(threat_level, notifier_priority_1, notifier_priority_2, notifier_priority_3, - notifier_priority_4, notifier_priority_5) # Finally, execute the POST request -discord_command(discord_webhook, sender, destination, priority, message, tags, click) +discord_command(discord_url, sender, destination, priority, message, tags, click) diff --git a/wazuh-notifier-conf.yaml b/wazuh-notifier-conf.yaml deleted file mode 100755 index 2fbe808..0000000 --- a/wazuh-notifier-conf.yaml +++ /dev/null @@ -1,31 +0,0 @@ ---- -#start of yaml - -# This is the yaml config file for both the wazuh-ntfy-notifier.py and wazuh-discord-notifier.py. -# The yaml needs to be in the same folder as the wazuh-ntfy-notifier.py and wazuh-discord-notifier.py - -# COMMON (custom-wazuh-notifiers.py) configuration settings start here. -# 1 = messages will be sent through this message server. 0 = messages will NOT be sent through this message server. - -targets: "discord,ntfy" - -# Exclude rules that are listed in the ossec.conf active response definition. - -excluded_rules: "5401, 5403" -excluded_agents: "999" - -# Priority mapping from 1-12 (Wazuh events) to 1-5 (Discord and ntfy notification) - -notifier_priority_1: 12, 11, 10 -notifier_priority_2: 9, 8 -notifier_priority_3: 7, 6 -notifier_priority_4: 5, 4 -notifier_priority_5: 3 ,2, 1 - -sender: "Wazuh (IDS)" -click: "https://google.com" - - -#end of yaml -... - diff --git a/wazuh-notify-config.yaml b/wazuh-notify-config.yaml new file mode 100755 index 0000000..5f6a6c3 --- /dev/null +++ b/wazuh-notify-config.yaml @@ -0,0 +1,32 @@ +--- +#start of yaml + +# This is the yaml config file for both the wazuh-ntfy-notifier.py and wazuh-discord-notifier.py. +# The yaml needs to be in the same folder as the wazuh-ntfy-notifier.py and wazuh-discord-notifier.py + +targets: "discord, ntfy" +full_message: "discord, ntfy" + +# Exclude rules that are listed in the ossec.conf active response definition. + +excluded_rules: "5401, 5403" +excluded_agents: "999" + +# Priority mapping from 0-15 (Wazuh events: threat levels) to 1-5 ( in notification) +# https://documentation.wazuh.com/current/user-manual/ruleset/rules-classification.html + +priority_5: [ 15,14,13,12 ] +priority_4: [ 11,10,9 ] +priority_3: [ 8,7,6 ] +priority_2: [ 5,4 ] +priority_1: [ 3,2,1,0 ] + +sender: "Wazuh (IDS)" +click: "https://google.com" + + +#end of yaml +... + + + diff --git a/wazuh_notifier_module.py b/wazuh_notifier_module.py index 6e7a9b4..515d8d3 100755 --- a/wazuh_notifier_module.py +++ b/wazuh_notifier_module.py @@ -1,13 +1,43 @@ +import datetime import getopt +import json import os import sys import time from os.path import join, dirname +from pathlib import PureWindowsPath, PurePosixPath import yaml from dotenv import load_dotenv +def set_environment() -> tuple: + # todo fix reference when running manually/in process + + set_wazuh_path = "/home/rudi/pycharm" + # set_wazuh_path = os.path.abspath(os.path.join(__file__, "../../..")) + set_ar_path = '{0}/logs/active-responses.log'.format(set_wazuh_path) + set_config_path = '{0}/etc/wazuh-notify-config.yaml'.format(set_wazuh_path) + set_notifier_path = '{0}/active-response/bin'.format(set_wazuh_path) + + return set_wazuh_path, set_ar_path, set_config_path, set_notifier_path + + +# Define paths: wazuh_path = wazuh root directory +# ar_path = active-responses.log path, +# config_path = wazuh-notifier-wazuh-notify-config.yaml + +wazuh_path, ar_path, config_path, notifier_path = set_environment() + + +# Debug writer +def write_debug_file(ar_name, msg): + with open(ar_path, mode="a") as log_file: + ar_name_posix = str(PurePosixPath(PureWindowsPath(ar_name[ar_name.find("active-response"):]))) + log_file.write( + str(datetime.datetime.now().strftime('%Y/%m/%d %H:%M:%S')) + " " + ar_name_posix + ": " + msg + "\n") + + def get_env(): try: dotenv_path = join(dirname(__file__), '.env') @@ -36,46 +66,51 @@ def set_time(): return now_message, now_logging -# Define paths: wazuh_path = wazuh root directory -# ar_path = active-responses.log path, -# config_path = wazuh-notifier-wazuh-notify-config.yaml - -def set_environment(): - # todo fix reference when running manually/in process - - wazuh_path = "/var/ossec" - # wazuh_path = os.path.abspath(os.path.join(__file__, "../../..")) - ar_path = '{0}/logs/active-responses.log'.format(wazuh_path) - config_path = 'wazuh-notifier-wazuh-notify-config.yaml'.format(wazuh_path) - - return wazuh_path, ar_path, config_path - - -# Import configuration settings from wazuh-notifier-wazuh-notify-config.yaml +# Import configuration settings from wazuh-notify-config.yaml def import_config(): try: - _, _, config_path = set_environment() + _, _, this_config_path, _ = set_environment() - with open(config_path, 'r') as ntfier_config: + with open(this_config_path, 'r') as ntfier_config: config: dict = yaml.safe_load(ntfier_config) return config except (FileNotFoundError, PermissionError, OSError): return None -# Show configuration settings from wazuh-notifier-wazuh-notify-config.yaml +# Process configuration settings from wazuh-notify-config.yaml + + +def get_config(): + config = import_config() + + config['np_5'] = config.get('np_1', [15, 14, 13, 12]) + config['np_4'] = config.get('np_2', [11, 10, 9]) + config['np_3'] = config.get('np_3', [8, 7, 6]) + config['np_2'] = config.get('np_4', [5, 4]) + config['np_1'] = config.get('np_5', [3, 2, 1, 0]) + config['targets'] = config.get('targets', 'ntfy, discord') + config['excluded_rules'] = config.get('excluded_rules', '') + config['excluded_agents'] = config.get('excluded_agents', '') + config['sender'] = 'Wazuh (IDS)' + config['click'] = 'https://wazuh.org' + + return config + + +# Show configuration settings from wazuh-notify-config.yaml def view_config(): - _, _, config_path = set_environment() + _, _, this_config_path, _ = set_environment() try: - with open(config_path, 'r') as ntfier_config: + with open(this_config_path, 'r') as ntfier_config: print(ntfier_config.read()) except (FileNotFoundError, PermissionError, OSError): - print(config_path + " does not exist or is not accessible") + print(this_config_path + " does not exist or is not accessible") return @@ -84,53 +119,49 @@ def view_config(): def ar_log(): now = set_time() - _, ar_path, _ = set_environment() + _, this_ar_path, _, _ = set_environment() msg = '{0} {1} {2}'.format(now, os.path.realpath(__file__), 'Post JSON Alert') - f = open(ar_path, 'a') + f = open(this_ar_path, 'a') f.write(msg + '\n') f.close() -def threat_priority_mapping(threat_level, np_1, np_2, np_3, np_4, np_5): +def threat_mapping(threat_level, np_1, np_2, np_3, np_4, np_5): # Map threat level v/s priority if threat_level in np_1: priority_mapping = "1" - priority_color = 0x339900 elif threat_level in np_2: priority_mapping = "2" - priority_color = 0x99cc33 elif threat_level in np_3: priority_mapping = "3" - priority_color = 0xffcc00 elif threat_level in np_4: priority_mapping = "4" - priority_color = 0xff9966 elif threat_level in np_5: priority_mapping = "5" - priority_color = 0xcc3300 else: priority_mapping = "3" + + return priority_mapping + + +def color_mapping(priority): + # Map priority to color + + if priority == 1: + priority_color = 0x339900 + elif priority == 2: + priority_color = 0x99cc33 + elif priority == 3: + priority_color = 0xffcc00 + elif priority == 4: + priority_color = 0xff9966 + elif priority == 5: + priority_color = 0xcc3300 + else: priority_color = 0xffcc00 - return priority_mapping, priority_color - - -def get_yaml_config(): - config = import_config() - - config['np_1'] = config.get('np_1', '1, 2, 3') - config['np_2'] = config.get('np_2', '4,5') - config['np_3'] = config.get('np_3', '6,7') - config['np_4'] = config.get('np_4', '8,9') - config['np_5'] = config.get('np_5', '10, 11, 12') - config['targets'] = config.get('targets', 'ntfy, discord') - config['excluded_rules'] = config.get('excluded_rules', '') - config['excluded_agents'] = config.get('excluded_agents', '') - config['sender'] = 'Wazuh (IDS)' - config['click'] = 'https://wazuh.org' - - return config + return priority_color def get_arguments(): @@ -155,8 +186,15 @@ def get_arguments(): -v, --view show config. """ + url: str + sender: str + destination: str + message: str + priority: int + tags: str + click: str - url, sender, destination, message, priority, tags, click = "", "", "", "", "", "", "" + url, sender, destination, message, priority, tags, click = "", "", "", "", 0, "", "" argument_list: list = sys.argv[1:] @@ -181,28 +219,83 @@ def get_arguments(): exit() elif current_argument in ("-u", "--url"): - url = current_value + url: str = current_value elif current_argument in ("-s", "--sender"): - sender = current_value + sender: str = current_value elif current_argument in ("-d", "--destination"): - destination = current_value + destination: str = current_value elif current_argument in ("-p", "--priority"): - priority = current_value + priority: int = current_value elif current_argument in ("-m", "--message"): - message = current_value + message: str = current_value elif current_argument in ("-t", "--tags"): - tags = current_value + tags: str = current_value elif current_argument in ("-c", "--click"): - click = current_value + click: str = current_value except getopt.error as err: # output error, and return with an error code print(str(err)) return url, sender, destination, message, priority, tags, click + + +def load_message(argv): + # get alert from stdin + input_str: str = "" + for line in sys.stdin: + input_str: str = line + break + + data: json = json.loads(input_str) + + if data.get("command") == "add": + return data + else: + # todo fix error message + sys.exit(1) + + +def parameters_deconstruct(argv, event_keys): + config: dict = get_config() + + a_id: str = str(event_keys["agent"]["id"]) + a_name: str = str(event_keys["agent"]["name"]) + e_id: str = str(event_keys["rule"]["id"]) + e_description: str = str(event_keys["rule"]["description"]) + e_level: str = str(event_keys["rule"]["level"]) + e_fired_times: str = str(event_keys["rule"]["firedtimes"]) + e_full_event: str = str(json.dumps(event_keys, indent=4).replace('"', '') + .replace('{', '') + .replace('}', '') + .replace('[', '') + .replace(']', '') + ) + + if e_id not in config["excluded_rules"] or a_id not in config["excluded_agents"]: + parameters: dict = dict(a_id=a_id, a_name=a_name, e_id=e_id, e_description=e_description, e_level=e_level, + e_fired_times=e_fired_times, e_full_event=e_full_event) + return parameters + + +def construct_basic_message(argv, accent: str, a_id: str, a_name: str, e_id: str, e_description: str, e_level: str, + e_fired_times: str) -> str: + # Adding the BOLD text string to the Discord message. Ntfy has a different message format. + + basic_message: str = ("--message " + '"' + + accent + "Agent: " + accent + a_name + " (" + a_id + ")" + "\n" + + accent + "Event id: " + accent + e_id + "\n" + + accent + "Description: " + accent + e_description + "\n" + + accent + "Threat level: " + accent + e_level + "\n" + + # Watch this last addition to the string. It should include the closing quote for the + # basic_message string. It must be closed by -> '"'. This will be done outside this function + # in order to enable another specific addition (event_full_message) in the calling procedure. + accent + "Times fired: " + accent + e_fired_times + "\n") + + return basic_message From e15c1c9c370df5d7cad374f0c9684399285c5c42 Mon Sep 17 00:00:00 2001 From: darius Date: Mon, 13 May 2024 14:44:32 +0200 Subject: [PATCH 25/33] mentions added priority map refactor --- wazuh-notify-go/notification/discord.go | 20 ++++++++++++++++++- wazuh-notify-go/services/init.go | 6 +++++- wazuh-notify-go/services/mapping.go | 10 +++++----- wazuh-notify-go/types/types.go | 11 ++++++----- wazuh-notify-go/wazuh-notify-config.yaml | 25 ++++++++++++++++++------ 5 files changed, 54 insertions(+), 18 deletions(-) diff --git a/wazuh-notify-go/notification/discord.go b/wazuh-notify-go/notification/discord.go index aa46484..1e20a85 100644 --- a/wazuh-notify-go/notification/discord.go +++ b/wazuh-notify-go/notification/discord.go @@ -35,7 +35,8 @@ func SendDiscord(params types.Params) { embedDescription = "\n\n" + "**Agent:** " + params.WazuhMessage.Parameters.Alert.Agent.Name + "\n" + "**Event id:** " + params.WazuhMessage.Parameters.Alert.Rule.ID + "\n" + - "**Description:** " + params.WazuhMessage.Parameters.Alert.Rule.Description + "\n" + + "**Rule:** " + params.WazuhMessage.Parameters.Alert.Rule.Description + "\n" + + "**Description: **" + params.WazuhMessage.Parameters.Alert.FullLog + "\n" + "**Threat level:** " + strconv.Itoa(params.WazuhMessage.Parameters.Alert.Rule.Level) + "\n" + "**Times fired:** " + strconv.Itoa(params.WazuhMessage.Parameters.Alert.Rule.Firedtimes) + "\n\n" + @@ -45,22 +46,39 @@ func SendDiscord(params types.Params) { } var color int + var mention string switch params.Priority { case 1: color = 0x339900 + if params.WazuhMessage.Parameters.Alert.Rule.Firedtimes >= params.PriorityMaps[4].MentionThreshold { + mention = "@here" + } case 2: color = 0x99cc33 + if params.WazuhMessage.Parameters.Alert.Rule.Firedtimes >= params.PriorityMaps[3].MentionThreshold { + mention = "@here" + } case 3: color = 0xffcc00 + if params.WazuhMessage.Parameters.Alert.Rule.Firedtimes >= params.PriorityMaps[2].MentionThreshold { + mention = "@here" + } case 4: color = 0xff9966 + if params.WazuhMessage.Parameters.Alert.Rule.Firedtimes >= params.PriorityMaps[1].MentionThreshold { + mention = "@here" + } case 5: color = 0xcc3300 + if params.WazuhMessage.Parameters.Alert.Rule.Firedtimes >= params.PriorityMaps[0].MentionThreshold { + mention = "@here" + } } message := types.Message{ Username: params.Sender, + Content: mention, Embeds: []types.Embed{ { Title: params.Sender, diff --git a/wazuh-notify-go/services/init.go b/wazuh-notify-go/services/init.go index df7fd29..88aabb6 100644 --- a/wazuh-notify-go/services/init.go +++ b/wazuh-notify-go/services/init.go @@ -36,7 +36,10 @@ func InitNotify() types.Params { log.Log("yaml failed to load") yamlFile, err = os.ReadFile(path.Join(BaseDirPath, "wazuh-notify-config.yaml")) } - yaml.Unmarshal(yamlFile, &configParams) + err = yaml.Unmarshal(yamlFile, &configParams) + if err != nil { + print(err) + } log.Log("yaml loaded") configParamString, _ := json.Marshal(configParams) @@ -59,6 +62,7 @@ func InitNotify() types.Params { inputParams.FullMessage = configParams.FullMessage inputParams.ExcludedAgents = configParams.ExcludedAgents inputParams.ExcludedRules = configParams.ExcludedRules + inputParams.PriorityMaps = configParams.PriorityMaps wazuhInput() diff --git a/wazuh-notify-go/services/mapping.go b/wazuh-notify-go/services/mapping.go index 051ff0f..c289491 100644 --- a/wazuh-notify-go/services/mapping.go +++ b/wazuh-notify-go/services/mapping.go @@ -3,19 +3,19 @@ package services import "slices" func mapPriority() int { - if slices.Contains(configParams.Priority1, wazuhData.Parameters.Alert.Rule.Level) { + if slices.Contains(configParams.PriorityMaps[4].ThreatMap, wazuhData.Parameters.Alert.Rule.Level) { return 1 } - if slices.Contains(configParams.Priority2, wazuhData.Parameters.Alert.Rule.Level) { + if slices.Contains(configParams.PriorityMaps[3].ThreatMap, wazuhData.Parameters.Alert.Rule.Level) { return 2 } - if slices.Contains(configParams.Priority3, wazuhData.Parameters.Alert.Rule.Level) { + if slices.Contains(configParams.PriorityMaps[2].ThreatMap, wazuhData.Parameters.Alert.Rule.Level) { return 3 } - if slices.Contains(configParams.Priority4, wazuhData.Parameters.Alert.Rule.Level) { + if slices.Contains(configParams.PriorityMaps[1].ThreatMap, wazuhData.Parameters.Alert.Rule.Level) { return 4 } - if slices.Contains(configParams.Priority5, wazuhData.Parameters.Alert.Rule.Level) { + if slices.Contains(configParams.PriorityMaps[0].ThreatMap, wazuhData.Parameters.Alert.Rule.Level) { return 5 } return 0 diff --git a/wazuh-notify-go/types/types.go b/wazuh-notify-go/types/types.go index 6293259..d4971be 100644 --- a/wazuh-notify-go/types/types.go +++ b/wazuh-notify-go/types/types.go @@ -11,11 +11,12 @@ type Params struct { ExcludedRules string `yaml:"excluded_rules,omitempty"` ExcludedAgents string `yaml:"excluded_agents,omitempty"` WazuhMessage WazuhMessage - Priority1 []int `yaml:"priority_1"` - Priority2 []int `yaml:"priority_2"` - Priority3 []int `yaml:"priority_3"` - Priority4 []int `yaml:"priority_4"` - Priority5 []int `yaml:"priority_5"` + PriorityMaps []PriorityMap `yaml:"priority_map"` +} + +type PriorityMap struct { + ThreatMap []int `yaml:"threat_map"` + MentionThreshold int `yaml:"mention_threshold"` } type Message struct { diff --git a/wazuh-notify-go/wazuh-notify-config.yaml b/wazuh-notify-go/wazuh-notify-config.yaml index e3ea1f6..b79d5b1 100644 --- a/wazuh-notify-go/wazuh-notify-config.yaml +++ b/wazuh-notify-go/wazuh-notify-config.yaml @@ -5,7 +5,7 @@ # The yaml needs to be in the same folder as the wazuh-ntfy-notifier.py and wazuh-discord-notifier.py targets: "discord,ntfy" -full_message: "discord,ntfy" +full_message: "ntfy" # Exclude rules that are listed in the ossec.conf active response definition. @@ -13,12 +13,25 @@ excluded_rules: "5401,5403" excluded_agents: "999" # Priority mapping from 1-12 (Wazuh events) to 1-5 (Discord and ntfy notification) +# Discord mention after x amount of event fired times + +priority_map: + - + threat_map: [15,14,13,12] + mention_threshold: 1 + - + threat_map: [11,10,9] + mention_threshold: 1 + - + threat_map: [8,7,6] + mention_threshold: 5 + - + threat_map: [5,4] + mention_threshold: 5 + - + threat_map: [3,2,1,0] + mention_threshold: 5 -priority_5: [15,14,13,12] -priority_4: [11,10,9] -priority_3: [8,7,6] -priority_2: [5,4] -priority_1: [3,2,1,0] sender: "Wazuh (IDS)" click: "https://google.com" From 933a125737be7b06d96e2a9cb08dcef0fdd5f88b Mon Sep 17 00:00:00 2001 From: darius Date: Mon, 13 May 2024 14:52:42 +0200 Subject: [PATCH 26/33] color added to prio map --- wazuh-notify-go/notification/discord.go | 10 +++++----- wazuh-notify-go/types/types.go | 1 + wazuh-notify-go/wazuh-notify-config.yaml | 5 +++++ 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/wazuh-notify-go/notification/discord.go b/wazuh-notify-go/notification/discord.go index 1e20a85..f6b977a 100644 --- a/wazuh-notify-go/notification/discord.go +++ b/wazuh-notify-go/notification/discord.go @@ -50,27 +50,27 @@ func SendDiscord(params types.Params) { switch params.Priority { case 1: - color = 0x339900 + color = params.PriorityMaps[4].Color if params.WazuhMessage.Parameters.Alert.Rule.Firedtimes >= params.PriorityMaps[4].MentionThreshold { mention = "@here" } case 2: - color = 0x99cc33 + color = params.PriorityMaps[3].Color if params.WazuhMessage.Parameters.Alert.Rule.Firedtimes >= params.PriorityMaps[3].MentionThreshold { mention = "@here" } case 3: - color = 0xffcc00 + color = params.PriorityMaps[2].Color if params.WazuhMessage.Parameters.Alert.Rule.Firedtimes >= params.PriorityMaps[2].MentionThreshold { mention = "@here" } case 4: - color = 0xff9966 + color = params.PriorityMaps[1].Color if params.WazuhMessage.Parameters.Alert.Rule.Firedtimes >= params.PriorityMaps[1].MentionThreshold { mention = "@here" } case 5: - color = 0xcc3300 + color = params.PriorityMaps[0].Color if params.WazuhMessage.Parameters.Alert.Rule.Firedtimes >= params.PriorityMaps[0].MentionThreshold { mention = "@here" } diff --git a/wazuh-notify-go/types/types.go b/wazuh-notify-go/types/types.go index d4971be..c543cc4 100644 --- a/wazuh-notify-go/types/types.go +++ b/wazuh-notify-go/types/types.go @@ -17,6 +17,7 @@ type Params struct { type PriorityMap struct { ThreatMap []int `yaml:"threat_map"` MentionThreshold int `yaml:"mention_threshold"` + Color int `yaml:"color"` } type Message struct { diff --git a/wazuh-notify-go/wazuh-notify-config.yaml b/wazuh-notify-go/wazuh-notify-config.yaml index b79d5b1..e455811 100644 --- a/wazuh-notify-go/wazuh-notify-config.yaml +++ b/wazuh-notify-go/wazuh-notify-config.yaml @@ -19,18 +19,23 @@ priority_map: - threat_map: [15,14,13,12] mention_threshold: 1 + color: 0xcc3300 - threat_map: [11,10,9] mention_threshold: 1 + color: 0xff9966 - threat_map: [8,7,6] mention_threshold: 5 + color: 0xffcc00 - threat_map: [5,4] mention_threshold: 5 + color: 0x99cc33 - threat_map: [3,2,1,0] mention_threshold: 5 + color: 0x339900 sender: "Wazuh (IDS)" From c7aed4a9a7fa645c09d73fd51ae0d2b589f2f09e Mon Sep 17 00:00:00 2001 From: darius Date: Mon, 13 May 2024 16:03:00 +0200 Subject: [PATCH 27/33] alot of ifs to loop --- wazuh-notify-go/notification/discord.go | 35 ++----------------------- wazuh-notify-go/services/init.go | 11 +++++++- wazuh-notify-go/services/mapping.go | 22 ---------------- wazuh-notify-go/types/types.go | 2 ++ 4 files changed, 14 insertions(+), 56 deletions(-) delete mode 100644 wazuh-notify-go/services/mapping.go diff --git a/wazuh-notify-go/notification/discord.go b/wazuh-notify-go/notification/discord.go index f6b977a..ac543a5 100644 --- a/wazuh-notify-go/notification/discord.go +++ b/wazuh-notify-go/notification/discord.go @@ -45,45 +45,14 @@ func SendDiscord(params types.Params) { params.Click } - var color int - var mention string - - switch params.Priority { - case 1: - color = params.PriorityMaps[4].Color - if params.WazuhMessage.Parameters.Alert.Rule.Firedtimes >= params.PriorityMaps[4].MentionThreshold { - mention = "@here" - } - case 2: - color = params.PriorityMaps[3].Color - if params.WazuhMessage.Parameters.Alert.Rule.Firedtimes >= params.PriorityMaps[3].MentionThreshold { - mention = "@here" - } - case 3: - color = params.PriorityMaps[2].Color - if params.WazuhMessage.Parameters.Alert.Rule.Firedtimes >= params.PriorityMaps[2].MentionThreshold { - mention = "@here" - } - case 4: - color = params.PriorityMaps[1].Color - if params.WazuhMessage.Parameters.Alert.Rule.Firedtimes >= params.PriorityMaps[1].MentionThreshold { - mention = "@here" - } - case 5: - color = params.PriorityMaps[0].Color - if params.WazuhMessage.Parameters.Alert.Rule.Firedtimes >= params.PriorityMaps[0].MentionThreshold { - mention = "@here" - } - } - message := types.Message{ Username: params.Sender, - Content: mention, + Content: params.Mention, Embeds: []types.Embed{ { Title: params.Sender, Description: embedDescription, - Color: color, + Color: params.Color, }, }, } diff --git a/wazuh-notify-go/services/init.go b/wazuh-notify-go/services/init.go index 88aabb6..4f0ddf2 100644 --- a/wazuh-notify-go/services/init.go +++ b/wazuh-notify-go/services/init.go @@ -8,6 +8,7 @@ import ( "gopkg.in/yaml.v2" "os" "path" + "slices" "strings" "wazuh-notify/log" "wazuh-notify/types" @@ -74,7 +75,15 @@ func wazuhInput() { json.NewDecoder(reader).Decode(&wazuhData) - inputParams.Priority = mapPriority() + for i, _ := range configParams.PriorityMaps { + if slices.Contains(configParams.PriorityMaps[i].ThreatMap, wazuhData.Parameters.Alert.Rule.Level) { + inputParams.Color = inputParams.PriorityMaps[i].Color + if inputParams.WazuhMessage.Parameters.Alert.Rule.Firedtimes >= inputParams.PriorityMaps[i].MentionThreshold { + inputParams.Mention = "@here" + } + inputParams.Priority = 5 - i + } + } inputParams.Tags += strings.Join(wazuhData.Parameters.Alert.Rule.Groups, ",") diff --git a/wazuh-notify-go/services/mapping.go b/wazuh-notify-go/services/mapping.go deleted file mode 100644 index c289491..0000000 --- a/wazuh-notify-go/services/mapping.go +++ /dev/null @@ -1,22 +0,0 @@ -package services - -import "slices" - -func mapPriority() int { - if slices.Contains(configParams.PriorityMaps[4].ThreatMap, wazuhData.Parameters.Alert.Rule.Level) { - return 1 - } - if slices.Contains(configParams.PriorityMaps[3].ThreatMap, wazuhData.Parameters.Alert.Rule.Level) { - return 2 - } - if slices.Contains(configParams.PriorityMaps[2].ThreatMap, wazuhData.Parameters.Alert.Rule.Level) { - return 3 - } - if slices.Contains(configParams.PriorityMaps[1].ThreatMap, wazuhData.Parameters.Alert.Rule.Level) { - return 4 - } - if slices.Contains(configParams.PriorityMaps[0].ThreatMap, wazuhData.Parameters.Alert.Rule.Level) { - return 5 - } - return 0 -} diff --git a/wazuh-notify-go/types/types.go b/wazuh-notify-go/types/types.go index c543cc4..ce40ca3 100644 --- a/wazuh-notify-go/types/types.go +++ b/wazuh-notify-go/types/types.go @@ -10,6 +10,8 @@ type Params struct { FullMessage string `yaml:"full_message,omitempty"` ExcludedRules string `yaml:"excluded_rules,omitempty"` ExcludedAgents string `yaml:"excluded_agents,omitempty"` + Color int + Mention string WazuhMessage WazuhMessage PriorityMaps []PriorityMap `yaml:"priority_map"` } From 09f26a10dc00d3e3a02d391d716ab572aedb5292 Mon Sep 17 00:00:00 2001 From: darius Date: Fri, 17 May 2024 18:42:16 +0200 Subject: [PATCH 28/33] hmmmmmmmmmmmmmmmmmmmm --- wazuh-notify-go/services/init.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wazuh-notify-go/services/init.go b/wazuh-notify-go/services/init.go index 4f0ddf2..0a39453 100644 --- a/wazuh-notify-go/services/init.go +++ b/wazuh-notify-go/services/init.go @@ -75,6 +75,10 @@ func wazuhInput() { json.NewDecoder(reader).Decode(&wazuhData) + inputParams.Tags += strings.Join(wazuhData.Parameters.Alert.Rule.Groups, ",") + + inputParams.WazuhMessage = wazuhData + for i, _ := range configParams.PriorityMaps { if slices.Contains(configParams.PriorityMaps[i].ThreatMap, wazuhData.Parameters.Alert.Rule.Level) { inputParams.Color = inputParams.PriorityMaps[i].Color @@ -85,10 +89,6 @@ func wazuhInput() { } } - inputParams.Tags += strings.Join(wazuhData.Parameters.Alert.Rule.Groups, ",") - - inputParams.WazuhMessage = wazuhData - Filter() log.Log("Wazuh data loaded") From 09329e0bc2c9553034f1a26b3ab6574d3c63ef38 Mon Sep 17 00:00:00 2001 From: Rudi Klein Date: Sat, 18 May 2024 21:23:35 +0200 Subject: [PATCH 29/33] Merger of module and notifier. Rename of files to 'notify' --- .env | 4 +- requirements.txt | 2 +- wazuh-notify-config.yaml | 32 --- wazuh-notify-test-event.json | 76 +++++++ wazuh-notify.py | 105 ++++++++++ wazuh_notify_module.py | 391 +++++++++++++++++++++++++++++++++++ 6 files changed, 575 insertions(+), 35 deletions(-) delete mode 100755 wazuh-notify-config.yaml create mode 100644 wazuh-notify-test-event.json create mode 100755 wazuh-notify.py create mode 100755 wazuh_notify_module.py diff --git a/.env b/.env index be7ea5c..2b864a6 100755 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ -DISCORD_WEBHOOK=https://discord.com/api/webhooks/1235943329854783530/lgAd6On2gtLPCAZ0HABXCJvVFut7zTL0eGwYs7akkSQ7LEZA2hGtqKYag5vXMdBXJv6L - +DISCORD_URL=https://discord.com/api/webhooks/1235943329854783530/lgAd6On2gtLPCAZ0HABXCJvVFut7zTL0eGwYs7akkSQ7LEZA2hGtqKYag5vXMdBXJv6L +NTFY_URL=https://ntfy.sh/__KleinTest diff --git a/requirements.txt b/requirements.txt index 6a36804..5b5f95f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -requests~=2.25.1 +requests~=2.31.0 PyYAML~=5.4.1 python-dotenv~=1.0.1 \ No newline at end of file diff --git a/wazuh-notify-config.yaml b/wazuh-notify-config.yaml deleted file mode 100755 index 5f6a6c3..0000000 --- a/wazuh-notify-config.yaml +++ /dev/null @@ -1,32 +0,0 @@ ---- -#start of yaml - -# This is the yaml config file for both the wazuh-ntfy-notifier.py and wazuh-discord-notifier.py. -# The yaml needs to be in the same folder as the wazuh-ntfy-notifier.py and wazuh-discord-notifier.py - -targets: "discord, ntfy" -full_message: "discord, ntfy" - -# Exclude rules that are listed in the ossec.conf active response definition. - -excluded_rules: "5401, 5403" -excluded_agents: "999" - -# Priority mapping from 0-15 (Wazuh events: threat levels) to 1-5 ( in notification) -# https://documentation.wazuh.com/current/user-manual/ruleset/rules-classification.html - -priority_5: [ 15,14,13,12 ] -priority_4: [ 11,10,9 ] -priority_3: [ 8,7,6 ] -priority_2: [ 5,4 ] -priority_1: [ 3,2,1,0 ] - -sender: "Wazuh (IDS)" -click: "https://google.com" - - -#end of yaml -... - - - diff --git a/wazuh-notify-test-event.json b/wazuh-notify-test-event.json new file mode 100644 index 0000000..b7105eb --- /dev/null +++ b/wazuh-notify-test-event.json @@ -0,0 +1,76 @@ +{ + "version": 1, + "origin": { + "name": "worker01", + "module": "wazuh-execd" + }, + "command": "add", + "parameters": { + "extra_args": [], + "alert": { + "timestamp": "2021-02-01T20:58:44.830+0000", + "rule": { + "level": 15, + "description": "Shellshock attack detected", + "id": "31168", + "mitre": { + "id": [ + "T1068", + "T1190" + ], + "tactic": [ + "Privilege Escalation", + "Initial Access" + ], + "technique": [ + "Exploitation for Privilege Escalation", + "Exploit Public-Facing Application" + ] + }, + "info": "CVE-2014-6271https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-6271", + "firedtimes": 2, + "mail": true, + "groups": [ + "web", + "accesslog", + "attack" + ], + "pci_dss": [ + "11.4" + ], + "gdpr": [ + "IV_35.7.d" + ], + "nist_800_53": [ + "SI.4" + ], + "tsc": [ + "CC6.1", + "CC6.8", + "CC7.2", + "CC7.3" + ] + }, + "agent": { + "id": "000", + "name": "wazuh-server" + }, + "manager": { + "name": "wazuh-server" + }, + "id": "1612213124.6448363", + "full_log": "192.168.0.223 - - [01/Feb/2021:20:58:43 +0000] \"GET / HTTP/1.1\" 200 612 \"-\" \"() { :; }; /bin/cat /etc/passwd\"", + "decoder": { + "name": "web-accesslog" + }, + "data": { + "protocol": "GET", + "srcip": "192.168.0.223", + "id": "200", + "url": "/" + }, + "location": "/var/log/nginx/access.log" + }, + "program": "/var/ossec/active-response/bin/firewall-drop" + } +} \ No newline at end of file diff --git a/wazuh-notify.py b/wazuh-notify.py new file mode 100755 index 0000000..3e56d8b --- /dev/null +++ b/wazuh-notify.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 + +# This script is adapted version of the Python active response script sample, provided by Wazuh, in the documentation: +# https://documentation.wazuh.com/current/user-manual/capabilities/active-response/custom-active-response-scripts.html +# It is provided under the below copyright statement: +# +# Copyright (C) 2015-2022, Wazuh Inc. +# All rights reserved. +# +# This program is free software; you can redistribute it +# and/or modify it under the terms of the GNU General Public +# License (version 2) as published by the FSF - Free Software +# Foundation. +# +# This adapted version is free software. Rudi Klein, april 2024 + +import json +import sys + +import requests + +from wazuh_notify_module import build_notification +from wazuh_notify_module import construct_basic_message +from wazuh_notify_module import exclusions_check +from wazuh_notify_module import get_arguments +from wazuh_notify_module import get_config +from wazuh_notify_module import get_env +from wazuh_notify_module import load_message +from wazuh_notify_module import logger +from wazuh_notify_module import set_environment +from wazuh_notify_module import threat_mapping + + +def main(argv): + # Load the YAML config + config: dict = get_config() + + # Path variables assignments + wazuh_path, ar_path, config_path = set_environment() + + # Get the arguments used with running the script + arg_url, arg_sender, arg_destination, arg_message, arg_priority, arg_tags, arg_click = get_arguments() + + # Check if we are in test mode (test_mode setting in config yaml). If so, load test event instead of live event. + if config.get("test_mode"): + logger(config, "In test mode: using test message wazuh-notify-test-event.json") + + with (open('wazuh-notify-test-event.json') as event_file): + data: dict = json.loads(event_file.read()) + + else: + logger(config, "In live mode: using live message") + data = load_message() + + # Extract the 'alert' section of the (JSON) event + alert = data["parameters"]["alert"] + + # Check the config for any exclusion rules + fire_notification = exclusions_check(config, alert) + + if not fire_notification: + logger(config, "Event excluded, no notification sent. Exiting") + exit() + + # Include a specific control sequence for Discord bold text + if "discord" in config["targets"]: + accent: str = "**" + else: + accent: str = "" + + # Create the notification text to be sent. + notification: str = construct_basic_message(accent, alert) + logger(config, "Notification constructed") + + # todo Not used? + # Get the mapping from event threat level to priority (Discord/ntfy), color (Discord) and mention_flag (Discord) + priority, color, mention = threat_mapping(config, alert['rule']['level'], + alert['rule']['firedtimes']) + + result = "" + # Prepare the messaging platform specific request and execute + if "discord" in config["targets"]: + caller = "discord" + discord_url, _, _ = get_env() + payload = build_notification(caller, config, notification, alert, priority, color, mention) + result = requests.post(discord_url, json=payload) + exit(result) + + if "ntfy" in config["targets"]: + caller = "ntfy" + ntfy_url, _, _ = get_env() + payload = build_notification(caller, config, notification, alert, priority, color, mention) + result = requests.post(ntfy_url, json=payload) + exit(result) + + if "slack" in config["targets"]: + caller = "slack" + slack_url, _, _ = get_env() + payload = build_notification(caller, config, notification, alert, priority, color, mention) + result = requests.post(slack_url, json=payload) + exit(result) + + +if __name__ == "__main__": + main(sys.argv) diff --git a/wazuh_notify_module.py b/wazuh_notify_module.py new file mode 100755 index 0000000..311cada --- /dev/null +++ b/wazuh_notify_module.py @@ -0,0 +1,391 @@ +#!/usr/bin/env python3 + +import datetime +import getopt +import json +import os +import sys +import time +from os.path import join, dirname +from pathlib import PureWindowsPath, PurePosixPath + +import yaml +from dotenv import load_dotenv + + +# Define paths: wazuh_path = wazuh root directory +# ar_path = active-responses.log path, +# config_path = wazuh-notify-config.yaml +# +def set_environment() -> tuple: + # todo fix reference when running manually/in process + + set_wazuh_path = "/home/rudi/pycharm" + # set_wazuh_path = os.path.abspath(os.path.join(__file__, "../../..")) + set_ar_path = '{0}/logs/wazuh-notifier.log'.format(set_wazuh_path) + set_config_path = '{0}/etc/wazuh-notify-config.yaml'.format(set_wazuh_path) + + return set_wazuh_path, set_ar_path, set_config_path + + +# Set paths for use in this module +wazuh_path, ar_path, config_path = set_environment() + + +# Set structured timestamps for logging and notifications. + + +def set_time_format(): + now_message = time.strftime('%A, %d %b %Y %H:%M:%S') + now_logging = time.strftime('%Y/%m/%d %H:%M:%S') + now_time = time.strftime('%H:%M') + now_weekday = time.strftime('%A') + return now_message, now_logging, now_weekday, now_time + + +# Logger +def logger(config, message): + # todo fix logging + + _, log_path, _ = set_environment() + + if config.get('extended_print', True): + print(time.strftime('%Y/%m/%d %H:%M:%S'), "|", message) + + if config.get("extended_logging"): + with open(ar_path, mode="a") as log_file: + ar_name_posix = str(PurePosixPath(PureWindowsPath(log_path[log_path.find("active-response"):]))) + log_file.write( + str(datetime.datetime.now().strftime( + '%Y/%m/%d %H:%M:%S')) + " " + ar_name_posix + ": " + message + "\n") + else: + pass + + +# Get the content of the .env file + + +def get_env(): + config: dict = get_config() + + try: + dotenv_path = join(dirname(__file__), '.env') + load_dotenv(dotenv_path) + if not os.path.isfile(dotenv_path): + logger(config, ".env not found") + raise Exception(dotenv_path, "file not found") + + # Retrieve url from .env + discord_url = os.getenv("DISCORD_URL") + ntfy_url = os.getenv("NTFY_URL") + slack_url = os.getenv("SLACK_URL") + + except Exception as err: + # output error, and return with an error code + logger(config, str(Exception(err.args))) + exit(err) + + return discord_url, ntfy_url, slack_url + + +# Process configuration settings from wazuh-notify-config.yaml + + +def get_config(): + # DO NOT USE logger() IN THIS FUNCTION. IT WILL CREATE A RECURSION LOOP! + + this_config_path: str = "" + + try: + _, _, this_config_path = set_environment() + + with open(this_config_path, 'r') as ntfier_config: + config: dict = yaml.safe_load(ntfier_config) + except (FileNotFoundError, PermissionError, OSError): + print(time.strftime('%Y/%m/%d %H:%M:%S') + " | " + this_config_path + " missing") + + print(time.strftime('%Y/%m/%d %H:%M:%S') + " | " + "reading config: " + this_config_path) + config['targets'] = config.get('targets', 'ntfy, discord') + config['excluded_rules'] = config.get('excluded_rules', '') + config['excluded_agents'] = config.get('excluded_agents', '') + config['excluded_days'] = config.get('excluded_days', '') + config['excluded_hours'] = config.get('excluded_hours', '') + config['test_mode'] = config.get('test_mode', True) + config['extended_logging'] = config.get('extended_logging', True) + config['extended_print'] = config.get('extended_print', True) + + config['sender'] = 'Wazuh (IDS)' + config['click'] = 'https://wazuh.org' + + return config + + +# Show configuration settings from wazuh-notify-config.yaml + + +def view_config(): + _, _, this_config_path, _ = set_environment() + + try: + with open(this_config_path, 'r') as ntfier_config: + print(ntfier_config.read()) + except (FileNotFoundError, PermissionError, OSError): + print(this_config_path + " does not exist or is not accessible") + return + + # Get params during execution. Params found here override config settings. + + +def get_arguments(): + config: dict = get_config() + # Short options + options: str = "u:s:p:m:t:c:hv" + + # Long options + long_options: list = ["url=", "sender=", "destination=", "priority=", "message=", "tags=", "click=", "help", + "view"] + + help_text: str = """ + -u, --url is the url for the server, ending with a "/". + -s, --sender is the sender of the message, either an app name or a person. + -d, --destination is the NTFY subscription or Discord title, to send the message to. + -p, --priority is the priority of the message, ranging from 1 (lowest), to 5 (highest). + -m, --message is the text of the message to be sent. + -t, --tags is an arbitrary strings of tags (keywords), seperated by a "," (comma). + -c, --click is a link (URL) that can be followed by tapping/clicking inside the message. + -h, --help shows this help message. Must have no value argument. + -v, --view show config. + + """ + url: str + sender: str + destination: str + message: str + priority: int + tags: str + click: str + + url, sender, destination, message, priority, tags, click = "", "", "", "", 0, "", "" + + argument_list: list = sys.argv[1:] + + if not argument_list: + logger(config, 'No argument list found (no arguments provided with script execution') + return url, sender, destination, message, priority, tags, click + + else: + + try: + + logger(config, "Parsing arguments") + + # Parsing arguments + arguments, values = getopt.getopt(argument_list, options, long_options) + + # checking each argument + for current_argument, current_value in arguments: + + if current_argument in ("-h", "--help"): + print(help_text) + exit() + + elif current_argument in ("-v", "--view"): + view_config() + exit() + + elif current_argument in ("-u", "--url"): + url: str = current_value + + elif current_argument in ("-s", "--sender"): + sender: str = current_value + + elif current_argument in ("-d", "--destination"): + destination: str = current_value + + elif current_argument in ("-p", "--priority"): + priority: int = int(current_value) + + elif current_argument in ("-m", "--message"): + message: str = current_value + + elif current_argument in ("-t", "--tags"): + tags: str = current_value + + elif current_argument in ("-c", "--click"): + click: str = current_value + + except getopt.error as err: + # output error, and return with an error code + + logger(config, str(err)) + + return url, sender, destination, message, priority, tags, click + + +# Receive and load message from Wazuh + + +def load_message(): + config: dict = get_config() + + # get alert from stdin + + logger(config, "Loading event message from STDIN") + + input_str: str = "" + for line in sys.stdin: + input_str: str = line + break + + data: json = json.loads(input_str) + + if data.get("command") == "add": + logger(config, "Relevant event data found") + return data + else: + # todo fix error message + sys.exit(1) + + +# Check if there are reasons not to process this event (as per config yaml) + + +def exclusions_check(config, alert): + # Set some environment + now_message, now_logging, now_weekday, now_time = set_time_format() + + # Check the exclusion records from the configuration yaml + ex_hours: tuple = config.get('excluded_hours') + + # Start hour may not be later than end hours. End hour may not exceed 00:00 midnight to avoid day jump + ex_hours = [ex_hours[0], "23:59"] if (ex_hours[1] >= '23:59' or ex_hours[1] < ex_hours[0]) else ex_hours + + # Get some more exclusion records from the config + ex_days = config.get('excluded_days') + ex_agents = config.get("excluded_agents") + ex_rules = config.get("excluded_rules") + + # Check agent and rule from within the event + ev_agent = alert['agent']['id'] + ev_rule = alert['rule']['id'] + + # Let's assume all lights are green + ex_hours_eval, ex_weekday_eval, ev_rule_eval, ev_agent_eval = True, True, True, True + + # Evaluate the conditions for sending a notification. Any False will cause the notification to be discarded. + if (now_time > ex_hours[0]) and (now_time < ex_hours[1]): + logger(config, "excluded: event inside exclusion time frame") + ex_hours_eval = False + elif now_weekday in ex_days: + logger(config, "excluded: event inside excluded weekdays") + ex_weekday_eval = False + elif ev_rule in ex_rules: + logger(config, "excluded: event id inside exclusion list") + ev_rule_eval = False + elif ev_agent in ex_agents: + logger(config, "excluded: event agent inside exclusion list") + ev_rule_eval = False + + notification_eval = ex_hours_eval and ex_weekday_eval and ev_rule_eval and ev_agent + + return notification_eval + + +# Map the event threat level to the appropriate 5-level priority scale and color for use in the notification platforms. + + +def threat_mapping(config, threat_level, fired_times): + # Map threat level v/s priority + + p_map = config.get('priority_map') + + for i in range(len(p_map)): + + if threat_level in p_map[i]["threat_map"]: + color_mapping = p_map[i]["color"] + priority_mapping = 5 - i + if fired_times >= p_map[i]["mention_threshold"]: + mention_flag = "@here" + else: + mention_flag = "" + return priority_mapping, color_mapping, mention_flag + else: + return 0, 0, 0 + + +# Construct the message that will be sent to the notifier platforms + + +def construct_basic_message(accent: str, data: dict) -> str: + # Adding the BOLD text string for Discord use + + basic_msg: str = (accent + + "Agent:" + " " + accent + data["agent"]["name"] + " (" + data["agent"][ + "id"] + ")" + "\n" + accent + + "Rule id: " + accent + data["rule"]["id"] + "\n" + accent + + "Rule: " + accent + data["rule"]["description"] + "\n" + accent + + "Description: " + accent + data["full_log"] + "\n" + accent + + "Threat level: " + accent + str(data["rule"]["level"]) + "\n" + accent + + "Times fired: " + accent + str(data["rule"]["firedtimes"]) + "\n") + + return basic_msg + + +def build_notification(caller, config, notification, alert, priority, color, mention): + click: str = config.get('click') + sender: str = config.get('sender') + priority: str = str(priority) + tags = (str(alert['rule']['groups']).replace("[", "") + .replace("]", "") + .replace("'", "") + .replace(",", ", ") + ) + full_event: str = str(json.dumps(alert, indent=4) + .replace('"', '') + .replace('{', '') + .replace('}', '') + .replace('[', '') + .replace(']', '') + .replace(',', ' ') + ) + # Add the full alert data to the notification + if caller in config["full_message"]: + notification: str = ("\n\n" + notification + "\n" + + "**" + "__Full event__" + "**" + "\n" + "```\n" + full_event + "```") + + # Add Priority & tags to the notification + notification = (notification + "\n\n" + "Priority: " + priority + "\n" + + "Tags: " + tags + "\n\n" + click) + + # Prepare the messaging platform specific notification and execute + if "discord" in config["targets"]: + url, _, _ = get_env() + + payload = {"username": "sender", + "content": mention, + "embeds": [{"description": notification, + "color": color, + "title": sender}]} + return payload + + if "ntfy" in config["targets"]: + caller = "ntfy" + ntfy_url, _, _ = get_env() + + payload = {"username": "sender", + "content": mention, + "embeds": [{"description": notification, + "color": color, + "title": sender}]} + return payload + + if "slack" in config["targets"]: + caller = "slack" + slack_url, _, _ = get_env() + + payload = {"username": "sender", + "content": mention, + "embeds": [{"description": notification, + "color": color, + "title": sender}]} + return payload From e0a99d1c7a67658a8459f71c53f74feb0facd9a2 Mon Sep 17 00:00:00 2001 From: Rudi Klein Date: Sat, 18 May 2024 21:30:17 +0200 Subject: [PATCH 30/33] Purge of old files --- test.go | 11 -- wazuh-active-response.py | 128 ---------------- wazuh-discord-notifier.py | 66 -------- wazuh-notifier-config.yaml | 31 ---- wazuh-notifier.py | 86 ----------- wazuh-ntfy-notifier.py | 100 ------------ wazuh_notifier_module.py | 301 ------------------------------------- 7 files changed, 723 deletions(-) delete mode 100644 test.go delete mode 100755 wazuh-active-response.py delete mode 100755 wazuh-discord-notifier.py delete mode 100755 wazuh-notifier-config.yaml delete mode 100755 wazuh-notifier.py delete mode 100755 wazuh-ntfy-notifier.py delete mode 100755 wazuh_notifier_module.py diff --git a/test.go b/test.go deleted file mode 100644 index 7bfc947..0000000 --- a/test.go +++ /dev/null @@ -1,11 +0,0 @@ -package main - -import ( - "fmt" - "os" -) - -func main() { - fmt.Println("hier") - os.Stderr.Write([]byte("hier2")) -} diff --git a/wazuh-active-response.py b/wazuh-active-response.py deleted file mode 100755 index 8e54941..0000000 --- a/wazuh-active-response.py +++ /dev/null @@ -1,128 +0,0 @@ -#!/usr/bin/env python3 - -# This script is adapted version of the Python active response script sample, provided by Wazuh, in the documentation: -# https://documentation.wazuh.com/current/user-manual/capabilities/active-response/custom-active-response-scripts.html -# It is provided under the below copyright statement: -# -# Copyright (C) 2015-2022, Wazuh Inc. -# All rights reserved. -# -# This program is free software; you can redistribute it -# and/or modify it under the terms of the GNU General Public -# License (version 2) as published by the FSF - Free Software -# Foundation. -# -# This adapted version is free software. Rudi Klein, april 2024 - -import os -import sys - -from wazuh_notifier_module import construct_basic_message -from wazuh_notifier_module import get_config -from wazuh_notifier_module import parameters_deconstruct -from wazuh_notifier_module import set_environment -from wazuh_notifier_module import threat_mapping - -# Path variable assignments - -wazuh_path, ar_path, config_path, notifier_path = set_environment() - - -def main(argv): - - # validate json and get command - - # data = load_message(argv) - # This example event can be used for troubleshooting. Comment out the line above and uncomment the line below. - data: dict = {"version": 1, "origin": {"name": "worker01", "module": "wazuh-execd"}, "command": "add", - "parameters": {"extra_args": [], "alert": {"timestamp": "2021-02-01T20:58:44.830+0000", - "rule": {"level": 15, - "description": "Shellshock attack detected", - "id": "31168", "mitre": {"id": ["T1068", "T1190"], - "tactic": [ - "Privilege Escalation", - "Initial Access"], - "technique": [ - "Exploitation for Privilege Escalation", - "Exploit Public-Facing Application"]}, - "info": "CVE-2014-6271https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-6271", - "firedtimes": 2, "mail": "true", - "groups": ["web", "accesslog", "attack"], - "pci_dss": ["11.4"], "gdpr": ["IV_35.7.d"], - "nist_800_53": ["SI.4"], - "tsc": ["CC6.1", "CC6.8", "CC7.2", "CC7.3"]}, - "agent": {"id": "000", "name": "wazuh-server"}, - "manager": {"name": "wazuh-server"}, - "id": "1612213124.6448363", - "full_log": "192.168.0.223 - - [01/Feb/2021:20:58:43 +0000] \"GET / HTTP/1.1\" 200 612 \"-\" \"() { :; }; /bin/cat /etc/passwd\"", - "decoder": {"name": "web-accesslog"}, - "data": {"protocol": "GET", "srcip": "192.168.0.223", - "id": "200", "url": "/"}, - "location": "/var/log/nginx/access.log"}, - "program": "/var/ossec/active-response/bin/firewall-drop"}} - - alert = data["parameters"]["alert"] - - # Get the threat level from the event (message) - threat_level = data["parameters"]["alert"]["rule"]["level"] - - parameters: dict = parameters_deconstruct(argv, alert) - - # Get the YAML config if any - config: dict = get_config() - - # Get the mapping between threat level (event) and priority (Discord/ntfy) - threat_priority = threat_mapping(threat_level, config.get('np_1'), config.get('np_2'), - config.get('np_3'), config.get('np_4'), config.get('np_5')) - - if "discord" in config["targets"]: - accent: str = "**" - elif "ntfy" in config["targets"]: - accent: str = "" - else: - accent: str = "" - - notifier_message: str = construct_basic_message(argv, accent, - parameters.get('a_id', '000'), - parameters.get('a_name', 'agent not found'), - parameters.get('e_id', '9999'), - parameters.get('e_description', 'Event not found'), - parameters.get('e_level', '9999'), - parameters.get('e_fired_times', '3') - ) - - if "discord" in config["targets"]: - - discord_notifier: str = '{0}/active-response/bin/wazuh-discord-notifier.py'.format(wazuh_path) - discord_exec: str = "python3 " + discord_notifier + " " - - discord_message: str = notifier_message - - if "discord" in config["full_message"]: - discord_message: str = (discord_message + "\n" + accent + "__Full event__" + - accent + parameters['e_full_event'] + '"') - else: - discord_message: str = discord_message + '"' - - discord_command: str = discord_exec + discord_message - os.system(discord_command) - - if "ntfy" in config["targets"]: - - ntfy_notifier: str = '{0}/active-response/bin/wazuh-ntfy-notifier.py'.format(wazuh_path) - ntfy_exec: str = "python3 " + ntfy_notifier + " " - ntfy_message: str = notifier_message - - # If the full message flag is set, the full message PLUS the closing parenthesis will be added - if "ntfy" in config["full_message"]: - ntfy_message: str = ntfy_message + "\n" + "Full event" + parameters['e_full_event'] + '"' - - else: - ntfy_message: str = ntfy_message + '"' - - ntfy_command: str = ntfy_exec + ntfy_message - os.system(ntfy_command) - - -if __name__ == "__main__": - main(sys.argv) diff --git a/wazuh-discord-notifier.py b/wazuh-discord-notifier.py deleted file mode 100755 index 4dd58e6..0000000 --- a/wazuh-discord-notifier.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python3 - -# This script is free software. -# -# Copyright (C) 2024, Rudi Klein. -# All rights reserved. -# -# This program is free software; you can redistribute it -# and/or modify it under the terms of the GNU General Public -# License (version 2) as published by the FSF - Free Software -# Foundation. -# -# This script is executed by the active response script (wazuh-active-response.py), which is triggered by rules firing. -# -# Discord is a voice, video and text communication service used by over a hundred million people to hang out and talk -# with their friends and communities. It allows for receiving message using webhooks. -# For more information: https://discord.com. - - -import requests - -from wazuh_notifier_module import color_mapping -from wazuh_notifier_module import get_arguments -from wazuh_notifier_module import get_config -from wazuh_notifier_module import get_env -from wazuh_notifier_module import set_environment -from wazuh_notifier_module import set_time - -# Get path values -wazuh_path, ar_path, config_path, notifier_path = set_environment() - -# Get time value -now_message, now_logging = set_time() - -# Get some paths. -discord_url, ntfy_url = get_env() - -# Get the yaml config -config: dict = get_config() - -# the POST builder. Prepares https and sends the request. - - -def discord_command(n_url, n_sender, n_destination, n_priority, n_message, n_tags, n_click): - color = color_mapping(n_priority) - - x_message = (now_message + - "\n\n" + n_message + "\n\n" + - "Priority: " + n_priority + "\n" + - "Tags: " + n_tags + "\n\n" + n_click - ) - n_data = {"username": n_sender, "embeds": [{"color": color, "description": x_message, "title": n_destination}]} - - requests.post(n_url, json=n_data) - - -# Remove 1st argument from the list of command line arguments -# argument_list: list = sys.argv[1:] - -notifier = "discord" - -url, sender, destination, priority, message, tags, click = get_arguments() - - -# Finally, execute the POST request -discord_command(discord_url, sender, destination, priority, message, tags, click) diff --git a/wazuh-notifier-config.yaml b/wazuh-notifier-config.yaml deleted file mode 100755 index 2fbe808..0000000 --- a/wazuh-notifier-config.yaml +++ /dev/null @@ -1,31 +0,0 @@ ---- -#start of yaml - -# This is the yaml config file for both the wazuh-ntfy-notifier.py and wazuh-discord-notifier.py. -# The yaml needs to be in the same folder as the wazuh-ntfy-notifier.py and wazuh-discord-notifier.py - -# COMMON (custom-wazuh-notifiers.py) configuration settings start here. -# 1 = messages will be sent through this message server. 0 = messages will NOT be sent through this message server. - -targets: "discord,ntfy" - -# Exclude rules that are listed in the ossec.conf active response definition. - -excluded_rules: "5401, 5403" -excluded_agents: "999" - -# Priority mapping from 1-12 (Wazuh events) to 1-5 (Discord and ntfy notification) - -notifier_priority_1: 12, 11, 10 -notifier_priority_2: 9, 8 -notifier_priority_3: 7, 6 -notifier_priority_4: 5, 4 -notifier_priority_5: 3 ,2, 1 - -sender: "Wazuh (IDS)" -click: "https://google.com" - - -#end of yaml -... - diff --git a/wazuh-notifier.py b/wazuh-notifier.py deleted file mode 100755 index c13bbc8..0000000 --- a/wazuh-notifier.py +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env python3 - -# This script is free software. -# -# Copyright (C) 2024, Rudi Klein. -# All rights reserved. -# -# This program is free software; you can redistribute it -# and/or modify it under the terms of the GNU General Public -# License (version 2) as published by the FSF - Free Software -# Foundation. -# -# This script is executed by the active response script (wazuh-active-response.py), which is triggered by rules firing. -# -# Discord is a voice, video and text communication service used by over a hundred million people to hang out and talk -# with their friends and communities. It allows for receiving message using webhooks. -# For more information: https://discord.com. - - -import json - -import requests - -from wazuh_notifier_module import get_arguments -from wazuh_notifier_module import get_env -from wazuh_notifier_module import get_yaml_config -from wazuh_notifier_module import set_environment -from wazuh_notifier_module import set_time -from wazuh_notifier_module import threat_priority_mapping - -# Setup the environment - -# Get time value -now_message, now_logging = set_time() - -# Get .env values -discord_webhook, ntfy_webhook = get_env() - -# Get path values -wazuh_path, ar_path, config_path = set_environment() - - -# the POST builders for the targets. Prepares https and sends the request. - -def discord_command(url, sender, destination, priority, message, tags, click): - x_message = (now_message + - "\n\n" + message + "\n\n" + - "Priority: " + priority + "\n" + - "Tags: " + tags + "\n\n" + click - ) - data = {"username": sender, "embeds": [{"description": x_message, "title": destination}]} - - requests.post(url, json=data) - - -def ntfy_command(url, sender, destination, priority, message, tags, click): - header = "" - if sender != "": header = header + '"Title"' + ": " + '"' + sender + '"' + ", " - if tags != "": header = header + '"Tags"' + ": " + '"' + tags + '"' + ", " - if click != "": header = header + '"Click"' + ": " + '"' + click + '"' + ", " - if priority != "": header = header + '"Priority"' + ": " + '"' + priority + '"' - header = json.loads("{" + header + "}") - x_message = now_message + "\n\n" + message - - # todo POST the request **** NEEDS future TRY **** - requests.post(url + destination, data=x_message, headers=header) - - -# Get the YAML config if any -config: dict = get_yaml_config() - -# Get the command line arguments -if get_arguments() is None: - url, sender, destination, message, priority, tags, click = "", "", "", "", "", "", "" -else: - url, sender, destination, priority, message, tags, click = get_arguments() - -# Get the threat level from the event (message) -threat_level = message[message.find('Threat level:') + 13:message.find('Threat level:') + 15].replace(" ", "") - -# Get the mapping between threat level (event) and priority (Discord/ntfy) -threat_priority = threat_priority_mapping(threat_level, config.get('np_1'), config.get('np_2'), - config.get('np_3'), config.get('np_4'), config.get('np_5')) - -# Finally, execute the POST request -# discord_command(discord_webhook, sender, destination, priority, message, tags, click) diff --git a/wazuh-ntfy-notifier.py b/wazuh-ntfy-notifier.py deleted file mode 100755 index 19cd77a..0000000 --- a/wazuh-ntfy-notifier.py +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env python3 - -# This script is free software. -# -# Copyright (C) 2024, Rudi Klein. -# All rights reserved. -# -# This program is free software; you can redistribute it -# and/or modify it under the terms of the GNU General Public -# License (version 2) as published by the FSF - Free Software -# Foundation. -# -# This script is executed by the active response script (wazuh-active-response.py), which is triggered by rules firing. -# -# ntfy (pronounced notify) is a simple HTTP-based pub-sub notification service. -# It allows you to send notifications to your phone or desktop via scripts from any computer, and/or using a REST API. -# It's infinitely flexible, and 100% free software. For more information: https://ntfy.sh. - -import json -import sys - -import requests - -from wazuh_notifier_module import get_arguments as ga -from wazuh_notifier_module import get_yaml_config as yc -from wazuh_notifier_module import set_basic_defaults as bd -from wazuh_notifier_module import set_environment as se -from wazuh_notifier_module import set_time as st -from wazuh_notifier_module import threat_priority_mapping as tpm - -# Get path values -wazuh_path, ar_path, config_path = se() - -# Get time value -now_message, now_logging = st() - -# the POST builder - - -def ntfy_command(n_server, n_sender, n_destination, n_priority, n_message, n_tags, n_click): - n_header = "" - if n_sender != "": n_header = n_header + '"Title"' + ": " + '"' + n_sender + '"' + ", " - if n_tags != "": n_header = n_header + '"Tags"' + ": " + '"' + n_tags + '"' + ", " - if n_click != "": n_header = n_header + '"Click"' + ": " + '"' + n_click + '"' + ", " - if n_priority != "": n_header = n_header + '"Priority"' + ": " + '"' + n_priority + '"' - n_header = json.loads("{" + n_header + "}") - x_message = now_message + "\n\n" + n_message - -# todo POST the request **** NEEDS future TRY **** - requests.post(n_server + n_destination, data=x_message, headers=n_header) - - -# Remove 1st argument from the list of command line arguments -argument_list = sys.argv[1:] - -# Short options -options: str = "u:s:d:p:m:t:c:hv" - -# Long options -long_options: list = ["server=", "sender=", "destination=", "priority=", "message=", "tags=", "click", "help", "view"] - -# Defining who I am -notifier = "ntfy" - -# Retrieve the hard-coded basic defaults. -(d_server, d_sender, d_destination, d_priority, d_message, d_tags, d_click, d_notifier_priority_1, - d_notifier_priority_2, d_notifier_priority_3, d_notifier_priority_4, d_notifier_priority_5) = bd(notifier) - -# Use the values from the config yaml if available. Overrides the basic defaults. -yc_args = [notifier, d_server, d_sender, d_destination, d_priority, d_message, d_tags, d_click, d_notifier_priority_1, - d_notifier_priority_2, d_notifier_priority_3, d_notifier_priority_4, d_notifier_priority_5] - -(server, sender, destination, priority, message, tags, click, notifier_priority_1, notifier_priority_2, - notifier_priority_3, notifier_priority_4, notifier_priority_5) = yc(*yc_args) - -# Get params during execution. Params found here, override minimal defaults and/or config settings. - -# noinspection PyRedeclaration -a_sender, a_destination, a_message, a_priority, a_tags, a_click = ga(notifier, options, long_options) - -if a_sender != '': sender = a_sender -if a_destination != '': destination = a_destination -if a_priority != "": priority = a_priority -if a_tags != "": tags = a_tags -if a_click != "": click = a_click - -# Get the threat level from the message and map it to priority - -threat_level = message[message.find('Threat level:') + 13:message.find('Threat level:') + 15].replace(" ", "") - -# Get the mapping between threat level (event) and priority (Discord/ntfy) - -# noinspection PyRedeclaration -priority = tpm(threat_level, notifier_priority_1, notifier_priority_2, notifier_priority_3, - notifier_priority_4, notifier_priority_5) - - -# Finally, execute the POST request -ntfy_command(server, sender, destination, priority, message, tags, click) - diff --git a/wazuh_notifier_module.py b/wazuh_notifier_module.py deleted file mode 100755 index 515d8d3..0000000 --- a/wazuh_notifier_module.py +++ /dev/null @@ -1,301 +0,0 @@ -import datetime -import getopt -import json -import os -import sys -import time -from os.path import join, dirname -from pathlib import PureWindowsPath, PurePosixPath - -import yaml -from dotenv import load_dotenv - - -def set_environment() -> tuple: - # todo fix reference when running manually/in process - - set_wazuh_path = "/home/rudi/pycharm" - # set_wazuh_path = os.path.abspath(os.path.join(__file__, "../../..")) - set_ar_path = '{0}/logs/active-responses.log'.format(set_wazuh_path) - set_config_path = '{0}/etc/wazuh-notify-config.yaml'.format(set_wazuh_path) - set_notifier_path = '{0}/active-response/bin'.format(set_wazuh_path) - - return set_wazuh_path, set_ar_path, set_config_path, set_notifier_path - - -# Define paths: wazuh_path = wazuh root directory -# ar_path = active-responses.log path, -# config_path = wazuh-notifier-wazuh-notify-config.yaml - -wazuh_path, ar_path, config_path, notifier_path = set_environment() - - -# Debug writer -def write_debug_file(ar_name, msg): - with open(ar_path, mode="a") as log_file: - ar_name_posix = str(PurePosixPath(PureWindowsPath(ar_name[ar_name.find("active-response"):]))) - log_file.write( - str(datetime.datetime.now().strftime('%Y/%m/%d %H:%M:%S')) + " " + ar_name_posix + ": " + msg + "\n") - - -def get_env(): - try: - dotenv_path = join(dirname(__file__), '.env') - load_dotenv(dotenv_path) - if not os.path.isfile(dotenv_path): - raise Exception(dotenv_path, "file not found") - - # Retrieve url from .env - discord_url = os.getenv("DISCORD_URL") - ntfy_url = os.getenv("NTFY_URL") - - except Exception as err: - # output error, and return with an error code - print(str(Exception(err.args))) - exit(err) - - return discord_url, ntfy_url - - -# Set structured timestamp for logging and discord/ntfy message. - - -def set_time(): - now_message = time.strftime('%a, %d %b %Y %H:%M:%S') - now_logging = time.strftime('%Y/%m/%d %H:%M:%S') - return now_message, now_logging - - -# Import configuration settings from wazuh-notify-config.yaml - - -def import_config(): - try: - _, _, this_config_path, _ = set_environment() - - with open(this_config_path, 'r') as ntfier_config: - config: dict = yaml.safe_load(ntfier_config) - return config - except (FileNotFoundError, PermissionError, OSError): - return None - - -# Process configuration settings from wazuh-notify-config.yaml - - -def get_config(): - config = import_config() - - config['np_5'] = config.get('np_1', [15, 14, 13, 12]) - config['np_4'] = config.get('np_2', [11, 10, 9]) - config['np_3'] = config.get('np_3', [8, 7, 6]) - config['np_2'] = config.get('np_4', [5, 4]) - config['np_1'] = config.get('np_5', [3, 2, 1, 0]) - config['targets'] = config.get('targets', 'ntfy, discord') - config['excluded_rules'] = config.get('excluded_rules', '') - config['excluded_agents'] = config.get('excluded_agents', '') - config['sender'] = 'Wazuh (IDS)' - config['click'] = 'https://wazuh.org' - - return config - - -# Show configuration settings from wazuh-notify-config.yaml - - -def view_config(): - _, _, this_config_path, _ = set_environment() - - try: - with open(this_config_path, 'r') as ntfier_config: - print(ntfier_config.read()) - except (FileNotFoundError, PermissionError, OSError): - print(this_config_path + " does not exist or is not accessible") - return - - -# Logging the Wazuh active Response request - - -def ar_log(): - now = set_time() - _, this_ar_path, _, _ = set_environment() - msg = '{0} {1} {2}'.format(now, os.path.realpath(__file__), 'Post JSON Alert') - f = open(this_ar_path, 'a') - f.write(msg + '\n') - f.close() - - -def threat_mapping(threat_level, np_1, np_2, np_3, np_4, np_5): - # Map threat level v/s priority - - if threat_level in np_1: - priority_mapping = "1" - elif threat_level in np_2: - priority_mapping = "2" - elif threat_level in np_3: - priority_mapping = "3" - elif threat_level in np_4: - priority_mapping = "4" - elif threat_level in np_5: - priority_mapping = "5" - else: - priority_mapping = "3" - - return priority_mapping - - -def color_mapping(priority): - # Map priority to color - - if priority == 1: - priority_color = 0x339900 - elif priority == 2: - priority_color = 0x99cc33 - elif priority == 3: - priority_color = 0xffcc00 - elif priority == 4: - priority_color = 0xff9966 - elif priority == 5: - priority_color = 0xcc3300 - else: - priority_color = 0xffcc00 - - return priority_color - - -def get_arguments(): - # Get params during execution. Params found here, override minimal defaults and/or config settings. - - # Short options - options: str = "u:s:p:m:t:c:hv" - - # Long options - long_options: list = ["url=", "sender=", "destination=", "priority=", "message=", "tags=", "click=", "help", - "view"] - - help_text: str = """ - -u, --url is the url for the server, ending with a "/". - -s, --sender is the sender of the message, either an app name or a person. - -d, --destination is the NTFY subscription or Discord title, to send the message to. - -p, --priority is the priority of the message, ranging from 1 (lowest), to 5 (highest). - -m, --message is the text of the message to be sent. - -t, --tags is an arbitrary strings of tags (keywords), seperated by a "," (comma). - -c, --click is a link (URL) that can be followed by tapping/clicking inside the message. - -h, --help shows this help message. Must have no value argument. - -v, --view show config. - - """ - url: str - sender: str - destination: str - message: str - priority: int - tags: str - click: str - - url, sender, destination, message, priority, tags, click = "", "", "", "", 0, "", "" - - argument_list: list = sys.argv[1:] - - if not argument_list: - return url, sender, destination, message, priority, tags, click - - else: - - try: - # Parsing argument - arguments, values = getopt.getopt(argument_list, options, long_options) - - # checking each argument - for current_argument, current_value in arguments: - - if current_argument in ("-h", "--help"): - print(help_text) - exit() - - elif current_argument in ("-v", "--view"): - view_config() - exit() - - elif current_argument in ("-u", "--url"): - url: str = current_value - - elif current_argument in ("-s", "--sender"): - sender: str = current_value - - elif current_argument in ("-d", "--destination"): - destination: str = current_value - - elif current_argument in ("-p", "--priority"): - priority: int = current_value - - elif current_argument in ("-m", "--message"): - message: str = current_value - - elif current_argument in ("-t", "--tags"): - tags: str = current_value - - elif current_argument in ("-c", "--click"): - click: str = current_value - - except getopt.error as err: - # output error, and return with an error code - print(str(err)) - - return url, sender, destination, message, priority, tags, click - - -def load_message(argv): - # get alert from stdin - input_str: str = "" - for line in sys.stdin: - input_str: str = line - break - - data: json = json.loads(input_str) - - if data.get("command") == "add": - return data - else: - # todo fix error message - sys.exit(1) - - -def parameters_deconstruct(argv, event_keys): - config: dict = get_config() - - a_id: str = str(event_keys["agent"]["id"]) - a_name: str = str(event_keys["agent"]["name"]) - e_id: str = str(event_keys["rule"]["id"]) - e_description: str = str(event_keys["rule"]["description"]) - e_level: str = str(event_keys["rule"]["level"]) - e_fired_times: str = str(event_keys["rule"]["firedtimes"]) - e_full_event: str = str(json.dumps(event_keys, indent=4).replace('"', '') - .replace('{', '') - .replace('}', '') - .replace('[', '') - .replace(']', '') - ) - - if e_id not in config["excluded_rules"] or a_id not in config["excluded_agents"]: - parameters: dict = dict(a_id=a_id, a_name=a_name, e_id=e_id, e_description=e_description, e_level=e_level, - e_fired_times=e_fired_times, e_full_event=e_full_event) - return parameters - - -def construct_basic_message(argv, accent: str, a_id: str, a_name: str, e_id: str, e_description: str, e_level: str, - e_fired_times: str) -> str: - # Adding the BOLD text string to the Discord message. Ntfy has a different message format. - - basic_message: str = ("--message " + '"' + - accent + "Agent: " + accent + a_name + " (" + a_id + ")" + "\n" + - accent + "Event id: " + accent + e_id + "\n" + - accent + "Description: " + accent + e_description + "\n" + - accent + "Threat level: " + accent + e_level + "\n" + - # Watch this last addition to the string. It should include the closing quote for the - # basic_message string. It must be closed by -> '"'. This will be done outside this function - # in order to enable another specific addition (event_full_message) in the calling procedure. - accent + "Times fired: " + accent + e_fired_times + "\n") - - return basic_message From c09c516299b27fa414996a8037abca78e3dcb1eb Mon Sep 17 00:00:00 2001 From: Rudi Klein Date: Sat, 18 May 2024 21:35:12 +0200 Subject: [PATCH 31/33] yaml added --- wazuh-notify-config.yaml | 58 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 wazuh-notify-config.yaml diff --git a/wazuh-notify-config.yaml b/wazuh-notify-config.yaml new file mode 100644 index 0000000..1dd5c3b --- /dev/null +++ b/wazuh-notify-config.yaml @@ -0,0 +1,58 @@ +--- +# Start of wazuh notifier configuration yaml. + +# This is the yaml config file for wazuh-active-response (for both the Python and Go version) + +targets: "discord" # Platforms in this string with comma seperated values are triggered. +full_message: "ntfy, slack" # Platforms in this string will enable the sending of the full event information. + +# Exclude rule events that are enabled in the ossec.conf active response definition. +# These settings provide an easier way to disable events from firing the notifiers. + +excluded_rules: "99999, 00000" # Enter as a string with comma seperated values +excluded_agents: "99999" # Enter as a string with comma seperated values + +# Priority mapping from 0-15 (Wazuh threat levels) to 1-5 (in notifications) and their respective colors (Discord) +# https://documentation.wazuh.com/current/user-manual/ruleset/rules-classification.html +# Enter as lists of integers. + + +priority_map: + - threat_map: [ 15,14,13,12 ] + mention_threshold: 1 + color: 0xcc3300 + - threat_map: [ 11,10,9 ] + mention_threshold: 1 + color: 0xff9966 + - threat_map: [ 8,7,6 ] + mention_threshold: 5 + color: 0xffcc00 + - threat_map: [ 5,4 ] + mention_threshold: 20 + color: 0x99cc33 + - threat_map: [ 3,2,1,0 ] + mention_threshold: 20 + color: 0x339900 + +# The next 2 settings are used to add information to the messages. +sender: "Wazuh (IDS)" +click: "https://documentation.wazuh.com/" + +# From here on the settings are ONLY used by the Python version of wazuh-active-response. + +# Below settings provide for a window that enable/disables events from firing the notifiers. +excluded_days: "" # Enter as a string with comma seperated values. Be aware of your regional settings. +excluded_hours: [ "23:59", "00:00" ] # Enter as a tuple of string values. Be aware of your regional settings. + +# The next settings are used for testing. Test mode will add the example event in wazuh-notify-test-event.json instead of the +# message received through wazuh. This enables testing for particular events when the test event is customized. +test_mode: True + +# Enabling this parameter provides more logging to the wazuh-notifier log. +extended_logging: True + +# Enabling this parameter provides extended logging to the console. +extended_print: True + +# End of wazuh notifier configuration yaml +... From 3e5ed201823be5e74cf395262a7bf25df3dab4ad Mon Sep 17 00:00:00 2001 From: Rudi Klein Date: Sat, 18 May 2024 21:39:09 +0200 Subject: [PATCH 32/33] python to sub-directory --- requirements.txt => wazuh-notify-python/requirements.txt | 0 wazuh-notify.py => wazuh-notify-python/wazuh-notify.py | 0 .../wazuh_notify_module.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename requirements.txt => wazuh-notify-python/requirements.txt (100%) rename wazuh-notify.py => wazuh-notify-python/wazuh-notify.py (100%) rename wazuh_notify_module.py => wazuh-notify-python/wazuh_notify_module.py (100%) diff --git a/requirements.txt b/wazuh-notify-python/requirements.txt similarity index 100% rename from requirements.txt rename to wazuh-notify-python/requirements.txt diff --git a/wazuh-notify.py b/wazuh-notify-python/wazuh-notify.py similarity index 100% rename from wazuh-notify.py rename to wazuh-notify-python/wazuh-notify.py diff --git a/wazuh_notify_module.py b/wazuh-notify-python/wazuh_notify_module.py similarity index 100% rename from wazuh_notify_module.py rename to wazuh-notify-python/wazuh_notify_module.py From da447de865c1a792ed1ac6aee7ab31b45132f5d8 Mon Sep 17 00:00:00 2001 From: Rudi Klein Date: Sat, 18 May 2024 21:50:24 +0200 Subject: [PATCH 33/33] gitignore added --- golive.bsh | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100755 golive.bsh diff --git a/golive.bsh b/golive.bsh new file mode 100755 index 0000000..df5c880 --- /dev/null +++ b/golive.bsh @@ -0,0 +1,10 @@ +#!/bin/bash +echo "Going live!" +sudo cp /home/rudi/pycharm/wazuh-notifier.py /var/ossec/active-response/bin +sudo cp /home/rudi/pycharm/wazuh_notifier_module.py /var/ossec/active-response/bin +sudo cp /home/rudi/pycharm/etc/wazuh-notify-config.yaml /var/ossec/etc +sudo cp /home/rudi/pycharm/wazuh-test-event.json /var/ossec/etc + +echo copied: wazuh-notifier.py, wazuh_notifier_module.py +echo "Live!" +