This commit is contained in:
82
wazuh/custom-integrations/custom-iris.py
Normal file
82
wazuh/custom-integrations/custom-iris.py
Normal file
@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python3
|
||||
# custom-iris.py
|
||||
# Custom Wazuh integration script to send alerts to DFIR-IRIS
|
||||
|
||||
import sys
|
||||
import json
|
||||
import requests
|
||||
from requests.auth import HTTPBasicAuth
|
||||
|
||||
# Function to create a formatted string from alert details
|
||||
def format_alert_details(alert_json):
|
||||
rule = alert_json.get("rule", {})
|
||||
agent = alert_json.get("agent", {})
|
||||
|
||||
# Extracting MITRE information from the nested 'rule' structure
|
||||
mitre = rule.get("mitre", {})
|
||||
mitre_ids = ', '.join(mitre.get("id", ["N/A"]))
|
||||
mitre_tactics = ', '.join(mitre.get("tactic", ["N/A"]))
|
||||
mitre_techniques = ', '.join(mitre.get("technique", ["N/A"]))
|
||||
|
||||
details = [
|
||||
f"Rule ID: {rule.get('id', 'N/A')}",
|
||||
f"Rule Level: {rule.get('level', 'N/A')}",
|
||||
f"Rule Description: {rule.get('description', 'N/A')}",
|
||||
f"Agent ID: {agent.get('id', 'N/A')}",
|
||||
f"Agent Name: {agent.get('name', 'N/A')}",
|
||||
f"MITRE IDs: {mitre_ids}",
|
||||
f"MITRE Tactics: {mitre_tactics}",
|
||||
f"MITRE Techniques: {mitre_techniques}",
|
||||
f"Location: {alert_json.get('location', 'N/A')}",
|
||||
f"Full Log: {alert_json.get('full_log', 'N/A')}"
|
||||
]
|
||||
return '\n'.join(details)
|
||||
|
||||
# Read parameters when integration is run
|
||||
alert_file = sys.argv[1]
|
||||
api_key = sys.argv[2]
|
||||
hook_url = sys.argv[3]
|
||||
|
||||
# Read the alert file
|
||||
with open(alert_file) as f:
|
||||
alert_json = json.load(f)
|
||||
|
||||
# Prepare alert details
|
||||
alert_details = format_alert_details(alert_json)
|
||||
|
||||
# Convert Wazuh rule levels(0-15) -> IRIS severity(1-6)
|
||||
alert_level = alert_json.get("rule", {}).get("level")
|
||||
if(alert_level < 5):
|
||||
severity = 2
|
||||
elif(alert_level >= 5 and alert_level < 7):
|
||||
severity = 3
|
||||
elif(alert_level >= 7 and alert_level < 10):
|
||||
severity = 4
|
||||
elif(alert_level >= 10 and alert_level < 13):
|
||||
severity = 5
|
||||
elif(alert_level >= 13):
|
||||
severity = 6
|
||||
else:
|
||||
severity = 1
|
||||
|
||||
# Generate request
|
||||
# Reference: https://docs.dfir-iris.org/_static/iris_api_reference_v2.0.1.html#tag/Alerts/operation/post-case-add-alert
|
||||
payload = json.dumps({
|
||||
"alert_title": alert_json.get("rule", {}).get("description", "No Description"),
|
||||
"alert_description": alert_details,
|
||||
"alert_source": "Wazuh",
|
||||
"alert_source_ref": alert_json.get("id", "Unknown ID"),
|
||||
"alert_source_link": "https://WAZUH-IP-OR-FQDN/app/wazuh", # Replace with actual Wazuh URL
|
||||
"alert_severity_id": severity,
|
||||
"alert_status_id": 2, # 'New' status
|
||||
"alert_source_event_time": alert_json.get("timestamp", "Unknown Timestamp"),
|
||||
"alert_note": "",
|
||||
"alert_tags": f"wazuh,{alert_json.get('agent', {}).get('name', 'N/A')}",
|
||||
"alert_customer_id": 1, # '1' for default 'IrisInitialClient'
|
||||
"alert_source_content": alert_json # raw log
|
||||
})
|
||||
|
||||
# Send request to IRIS
|
||||
response = requests.post(hook_url, data=payload, headers={"Authorization": "Bearer " + api_key, "content-type": "application/json"}, verify=False)
|
||||
|
||||
sys.exit(0)
|
179
wazuh/custom-integrations/custom-misp.py
Normal file
179
wazuh/custom-integrations/custom-misp.py
Normal file
@ -0,0 +1,179 @@
|
||||
#!/var/ossec/framework/python/bin/python3
|
||||
## MISP API Integration
|
||||
#
|
||||
import sys
|
||||
import os
|
||||
from socket import socket, AF_UNIX, SOCK_DGRAM
|
||||
from datetime import date, datetime, timedelta
|
||||
import time
|
||||
import requests
|
||||
from requests.exceptions import ConnectionError
|
||||
import json
|
||||
import ipaddress
|
||||
import hashlib
|
||||
import re
|
||||
pwd = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||
socket_addr = '{0}/queue/sockets/queue'.format(pwd)
|
||||
def send_event(msg, agent = None):
|
||||
if not agent or agent["id"] == "000":
|
||||
string = '1:misp:{0}'.format(json.dumps(msg))
|
||||
else:
|
||||
string = '1:[{0}] ({1}) {2}->misp:{3}'.format(agent["id"], agent["name"], agent["ip"] if "ip" in agent else "any", json.dumps(msg))
|
||||
sock = socket(AF_UNIX, SOCK_DGRAM)
|
||||
sock.connect(socket_addr)
|
||||
sock.send(string.encode())
|
||||
sock.close()
|
||||
false = False
|
||||
# Read configuration parameters
|
||||
alert_file = open(sys.argv[1])
|
||||
# Read the alert file
|
||||
alert = json.loads(alert_file.read())
|
||||
alert_file.close()
|
||||
# New Alert Output if MISP Alert or Error calling the API
|
||||
alert_output = {}
|
||||
# MISP Server Base URL
|
||||
misp_base_url = "https://**your misp instance**/attributes/restSearch/"
|
||||
# MISP Server API AUTH KEY
|
||||
misp_api_auth_key = "*Your API Key"
|
||||
# API - HTTP Headers
|
||||
misp_apicall_headers = {"Content-Type":"application/json", "Authorization":f"{misp_api_auth_key}", "Accept":"application/json"}
|
||||
## Extract Sysmon for Windows/Sysmon for Linux and Sysmon Event ID
|
||||
event_source = alert["rule"]["groups"][0]
|
||||
event_type = alert["rule"]["groups"][2]
|
||||
## Regex Pattern used based on SHA256 lenght (64 characters)
|
||||
regex_file_hash = re.compile('\w{64}')
|
||||
if event_source == 'windows':
|
||||
if event_type == 'sysmon_event1':
|
||||
try:
|
||||
wazuh_event_param = regex_file_hash.search(alert["data"]["win"]["eventdata"]["hashes"]).group(0)
|
||||
except IndexError:
|
||||
sys.exit()
|
||||
elif event_type == 'sysmon_event3' and alert["data"]["win"]["eventdata"]["destinationIsIpv6"] == 'false':
|
||||
try:
|
||||
dst_ip = alert["data"]["win"]["eventdata"]["destinationIp"]
|
||||
if ipaddress.ip_address(dst_ip).is_global:
|
||||
wazuh_event_param = dst_ip
|
||||
else:
|
||||
sys.exit()
|
||||
except IndexError:
|
||||
sys.exit()
|
||||
elif event_type == 'sysmon_event3' and alert_output["data"]["win"]["eventdata"]["destinationIsIpv6"] == 'true':
|
||||
sys.exit()
|
||||
elif event_type == 'sysmon_event6':
|
||||
try:
|
||||
wazuh_event_param = regex_file_hash.search(alert["data"]["win"]["eventdata"]["hashes"]).group(0)
|
||||
except IndexError:
|
||||
sys.exit()
|
||||
elif event_type == 'sysmon_event7':
|
||||
try:
|
||||
wazuh_event_param = regex_file_hash.search(alert["data"]["win"]["eventdata"]["hashes"]).group(0)
|
||||
except IndexError:
|
||||
sys.exit()
|
||||
elif event_type == 'sysmon_event_15':
|
||||
try:
|
||||
wazuh_event_param = regex_file_hash.search(alert["data"]["win"]["eventdata"]["hashes"]).group(0)
|
||||
except IndexError:
|
||||
sys.exit()
|
||||
elif event_type == 'sysmon_event_22':
|
||||
try:
|
||||
wazuh_event_param = alert["data"]["win"]["eventdata"]["queryName"]
|
||||
except IndexError:
|
||||
sys.exit()
|
||||
elif event_type == 'sysmon_event_23':
|
||||
try:
|
||||
wazuh_event_param = regex_file_hash.search(alert["data"]["win"]["eventdata"]["hashes"]).group(0)
|
||||
except IndexError:
|
||||
sys.exit()
|
||||
elif event_type == 'sysmon_event_24':
|
||||
try:
|
||||
wazuh_event_param = regex_file_hash.search(alert["data"]["win"]["eventdata"]["hashes"]).group(0)
|
||||
except IndexError:
|
||||
sys.exit()
|
||||
elif event_type == 'sysmon_event_25':
|
||||
try:
|
||||
wazuh_event_param = regex_file_hash.search(alert["data"]["win"]["eventdata"]["hashes"]).group(0)
|
||||
except IndexError:
|
||||
sys.exit()
|
||||
else:
|
||||
sys.exit()
|
||||
misp_search_value = "value:"f"{wazuh_event_param}"
|
||||
misp_search_url = ''.join([misp_base_url, misp_search_value])
|
||||
try:
|
||||
misp_api_response = requests.get(misp_search_url, headers=misp_apicall_headers, verify=False)
|
||||
except ConnectionError:
|
||||
alert_output["misp"] = {}
|
||||
alert_output["integration"] = "misp"
|
||||
alert_output["misp"]["error"] = 'Connection Error to MISP API'
|
||||
send_event(alert_output, alert["agent"])
|
||||
else:
|
||||
misp_api_response = misp_api_response.json()
|
||||
# Check if response includes Attributes (IoCs)
|
||||
if (misp_api_response["response"]["Attribute"]):
|
||||
# Generate Alert Output from MISP Response
|
||||
alert_output["misp"] = {}
|
||||
alert_output["misp"]["source"] = {}
|
||||
alert_output["misp"]["event_id"] = misp_api_response["response"]["Attribute"][0]["event_id"]
|
||||
alert_output["misp"]["category"] = misp_api_response["response"]["Attribute"][0]["category"]
|
||||
alert_output["misp"]["value"] = misp_api_response["response"]["Attribute"][0]["value"]
|
||||
alert_output["misp"]["type"] = misp_api_response["response"]["Attribute"][0]["type"]
|
||||
alert_output["misp"]["source"]["description"] = alert["rule"]["description"]
|
||||
send_event(alert_output, alert["agent"])
|
||||
elif event_source == 'linux':
|
||||
if event_type == 'sysmon_event3' and alert["data"]["eventdata"]["destinationIsIpv6"] == 'false':
|
||||
try:
|
||||
dst_ip = alert["data"]["eventdata"]["DestinationIp"]
|
||||
if ipaddress.ip_address(dst_ip).is_global:
|
||||
wazuh_event_param = dst_ip
|
||||
misp_search_value = "value:"f"{wazuh_event_param}"
|
||||
misp_search_url = ''.join([misp_base_url, misp_search_value])
|
||||
try:
|
||||
misp_api_response = requests.get(misp_search_url, headers=misp_apicall_headers, verify=False)
|
||||
except ConnectionError:
|
||||
alert_output["misp"] = {}
|
||||
alert_output["integration"] = "misp"
|
||||
alert_output["misp"]["error"] = 'Connection Error to MISP API'
|
||||
send_event(alert_output, alert["agent"])
|
||||
else:
|
||||
misp_api_response = misp_api_response.json()
|
||||
# Check if response includes Attributes (IoCs)
|
||||
if (misp_api_response["response"]["Attribute"]):
|
||||
# Generate Alert Output from MISP Response
|
||||
alert_output["misp"] = {}
|
||||
alert_output["misp"]["event_id"] = misp_api_response["response"]["Attribute"][0]["event_id"]
|
||||
alert_output["misp"]["category"] = misp_api_response["response"]["Attribute"][0]["category"]
|
||||
alert_output["misp"]["value"] = misp_api_response["response"]["Attribute"][0]["value"]
|
||||
alert_output["misp"]["type"] = misp_api_response["response"]["Attribute"][0]["type"]
|
||||
send_event(alert_output, alert["agent"])
|
||||
else:
|
||||
sys.exit()
|
||||
except IndexError:
|
||||
sys.exit()
|
||||
else:
|
||||
sys.exit()
|
||||
elif event_source == 'ossec' and event_type == "syscheck_entry_added":
|
||||
try:
|
||||
wazuh_event_param = alert["syscheck"]["sha256_after"]
|
||||
except IndexError:
|
||||
sys.exit()
|
||||
misp_search_value = "value:"f"{wazuh_event_param}"
|
||||
misp_search_url = ''.join([misp_base_url, misp_search_value])
|
||||
try:
|
||||
misp_api_response = requests.get(misp_search_url, headers=misp_apicall_headers, verify=false)
|
||||
except ConnectionError:
|
||||
alert_output["misp"] = {}
|
||||
alert_output["integration"] = "misp"
|
||||
alert_output["misp"]["error"] = 'Connection Error to MISP API'
|
||||
send_event(alert_output, alert["agent"])
|
||||
else:
|
||||
misp_api_response = misp_api_response.json()
|
||||
# Check if response includes Attributes (IoCs)
|
||||
if (misp_api_response["response"]["Attribute"]):
|
||||
# Generate Alert Output from MISP Response
|
||||
alert_output["misp"] = {}
|
||||
alert_output["misp"]["event_id"] = misp_api_response["response"]["Attribute"][0]["event_id"]
|
||||
alert_output["misp"]["category"] = misp_api_response["response"]["Attribute"][0]["category"]
|
||||
alert_output["misp"]["value"] = misp_api_response["response"]["Attribute"][0]["value"]
|
||||
alert_output["misp"]["type"] = misp_api_response["response"]["Attribute"][0]["type"]
|
||||
send_event(alert_output, alert["agent"])
|
||||
else:
|
||||
sys.exit()
|
42
wazuh/custom-integrations/local_rules.xml
Normal file
42
wazuh/custom-integrations/local_rules.xml
Normal file
@ -0,0 +1,42 @@
|
||||
<!-- Local rules -->
|
||||
|
||||
<!-- Modify it at your will. -->
|
||||
<!-- Copyright (C) 2015, Wazuh Inc. -->
|
||||
|
||||
<!-- Example -->
|
||||
<group name="local,syslog,sshd,">
|
||||
|
||||
<!--
|
||||
Dec 10 01:02:02 host sshd[1234]: Failed none for root from 1.1.1.1 port 1066 ssh2
|
||||
-->
|
||||
<rule id="100001" level="5">
|
||||
<if_sid>5716</if_sid>
|
||||
<srcip>1.1.1.1</srcip>
|
||||
<description>sshd: authentication failed from IP 1.1.1.1.</description>
|
||||
<group>authentication_failed,pci_dss_10.2.4,pci_dss_10.2.5,</group>
|
||||
</rule>
|
||||
|
||||
</group>
|
||||
|
||||
<group name="misp,">
|
||||
<rule id="100620" level="10">
|
||||
<field name="integration">misp</field>
|
||||
<match>misp</match>
|
||||
<description>MISP Events</description>
|
||||
<options>no_full_log</options>
|
||||
</rule>
|
||||
<rule id="100621" level="5">
|
||||
<if_sid>100620</if_sid>
|
||||
<field name="misp.error">\.+</field>
|
||||
<description>MISP - Error connecting to API</description>
|
||||
<options>no_full_log</options>
|
||||
<group>misp_error,</group>
|
||||
</rule>
|
||||
<rule id="100622" level="12">
|
||||
<field name="misp.category">\.+</field>
|
||||
<description>MISP - IoC found in Threat Intel - Category: $(misp.category), Attribute: $(misp.value)</description>
|
||||
<options>no_full_log</options>
|
||||
<group>misp_alert,</group>
|
||||
</rule>
|
||||
</group>
|
||||
|
Reference in New Issue
Block a user