Wazuh Third-Party Integrations - Osquery, MISP, SOAR
Wazuh 4.14 extends its monitoring and response capabilities through integration with third-party security platforms. The Osquery module provides advanced endpoint telemetry, threat intelligence platforms (MISP, OpenCTI) enable alert enrichment with indicators of compromise, and SOAR solutions automate the full incident response lifecycle.
Osquery Integration
Osquery is a tool for querying operating system state through a SQL-like interface. Wazuh integrates with Osquery via the wodle module, enabling scheduled queries on agents with centralized result analysis on the server.
Configuring the Osquery Wodle
The module is configured in the agent configuration file /var/ossec/etc/ossec.conf:
<wodle name="osquery">
<disabled>no</disabled>
<run_daemon>yes</run_daemon>
<log_path>/var/log/osquery/osqueryd.results.log</log_path>
<config_path>/etc/osquery/osquery.conf</config_path>
<add_labels>yes</add_labels>
</wodle>Module parameters:
| Parameter | Description | Default |
|---|---|---|
<disabled> | Enable or disable the module | yes |
<run_daemon> | Manage the osqueryd daemon | yes |
<log_path> | Path to the Osquery results log | /var/log/osquery/osqueryd.results.log |
<config_path> | Path to the Osquery configuration | /etc/osquery/osquery.conf |
<add_labels> | Append agent labels to results | yes |
Osquery Pack Configuration
Osquery uses JSON configuration files to define scheduled queries:
{
"schedule": {
"system_info": {
"query": "SELECT hostname, cpu_brand, physical_memory FROM system_info;",
"interval": 3600,
"description": "System information"
},
"open_ports": {
"query": "SELECT DISTINCT pid, port, protocol, address FROM listening_ports WHERE port != 0;",
"interval": 300,
"description": "Open network ports"
},
"installed_packages": {
"query": "SELECT name, version, source FROM deb_packages UNION SELECT name, version, source FROM rpm_packages;",
"interval": 86400,
"description": "Installed packages inventory"
},
"suspicious_processes": {
"query": "SELECT p.name, p.path, p.cmdline, p.uid, u.username FROM processes p LEFT JOIN users u ON p.uid = u.uid WHERE p.on_disk = 0;",
"interval": 60,
"description": "Processes running from deleted binaries"
}
}
}Detection Rules for Osquery
Wazuh analyzes Osquery results through its rule engine. Built-in rules cover basic scenarios, but custom rules are recommended for advanced monitoring:
<rule id="100200" level="10">
<decoded_as>osquery</decoded_as>
<field name="osquery.name">suspicious_processes</field>
<description>Osquery: process running from deleted binary detected on $(agent.name)</description>
<mitre>
<id>T1036</id>
</mitre>
<group>osquery,process_anomaly,</group>
</rule>For more on creating detection rules, see the Wazuh Capabilities section.
Threat Intelligence - MISP
MISP (Malware Information Sharing Platform) is a platform for sharing indicators of compromise (IoC). The Wazuh integration automatically checks observables (IP addresses, domains, file hashes) against the MISP IoC database.
MISP Integration Architecture
Wazuh Alert --> Custom Integration Script --> MISP API --> IoC Lookup
|
Match Found? --> Enriched AlertConfiguration
- Generate an API key in MISP: Administration - Auth Keys - Add authentication key
- Install the integration script:
#!/usr/bin/env python3
"""MISP integration for Wazuh - IoC lookup."""
import sys
import json
import requests
from typing import Optional
MISP_URL = "https://misp.example.com"
MISP_API_KEY = "YOUR_MISP_API_KEY"
VERIFY_SSL = True
def search_ioc(value: str, ioc_type: str) -> Optional[dict]:
"""Search for an IoC in MISP."""
headers = {
"Authorization": MISP_API_KEY,
"Accept": "application/json",
"Content-Type": "application/json",
}
payload = {
"returnFormat": "json",
"value": value,
"type": ioc_type,
}
response = requests.post(
f"{MISP_URL}/attributes/restSearch",
json=payload,
headers=headers,
verify=VERIFY_SSL,
)
if response.status_code == 200:
results = response.json()
if results.get("response", {}).get("Attribute"):
return results["response"]["Attribute"][0]
return None
def main():
alert_file = sys.argv[1]
with open(alert_file) as f:
alert = json.load(f)
src_ip = alert.get("data", {}).get("srcip")
if src_ip:
result = search_ioc(src_ip, "ip-src")
if result:
print(json.dumps({
"misp_match": True,
"event_id": result.get("event_id"),
"category": result.get("category"),
"comment": result.get("comment"),
}))
if __name__ == "__main__":
main()- Configure in
ossec.conf:
<integration>
<name>custom-misp</name>
<hook_url>https://misp.example.com</hook_url>
<api_key>YOUR_MISP_API_KEY</api_key>
<level>7</level>
<group>web,sshd,firewall,</group>
<alert_format>json</alert_format>
</integration>IoC Types for Lookup
| IoC Type | Wazuh Alert Source | MISP Type |
|---|---|---|
| Source IP | data.srcip | ip-src |
| Destination IP | data.dstip | ip-dst |
| Domain | data.hostname | domain |
| File hash (MD5) | syscheck.md5_after | md5 |
| File hash (SHA256) | syscheck.sha256_after | sha256 |
| URL | data.url | url |
Threat Intelligence - OpenCTI
OpenCTI is a cyber threat intelligence management platform supporting STIX/TAXII standards.
Integration via TAXII Feed
Wazuh can consume IoCs from OpenCTI through a TAXII server:
- Configure a collection export in OpenCTI with a TAXII endpoint
- Create a periodic synchronization script that converts IoCs to Wazuh CDB list format:
#!/bin/bash
# Sync OpenCTI IoC to Wazuh CDB lists
TAXII_URL="https://opencti.example.com/taxii2/feeds"
OUTPUT_DIR="/var/ossec/etc/lists"
# Download IP indicators
curl -sk -H "Authorization: Bearer ${OPENCTI_TOKEN}" \
"${TAXII_URL}/ip-indicators" \
| jq -r '.objects[] | select(.type=="indicator") | .pattern' \
| sed "s/\[ipv4-addr:value = '//;s/'\]//" \
| while read ip; do echo "${ip}:malicious"; done \
> "${OUTPUT_DIR}/opencti-malicious-ips"
# Reload Wazuh rules
/var/ossec/bin/wazuh-control reload- Reference the CDB list in rules:
<rule id="100210" level="12">
<if_sid>5710</if_sid>
<list field="srcip" lookup="address_match_key">etc/lists/opencti-malicious-ips</list>
<description>Connection from IP flagged in OpenCTI threat intelligence: $(srcip)</description>
<mitre>
<id>T1071</id>
</mitre>
<group>threat_intelligence,opencti,</group>
</rule>Ticketing Systems - Jira
The Jira integration automatically creates issues when critical alerts fire.
Configuring the Jira Integration
- Generate an API Token in Jira: Account Settings - Security - API Tokens
- Install the integration script:
#!/usr/bin/env python3
"""Jira integration for Wazuh - automatic ticket creation."""
import sys
import json
import requests
from base64 import b64encode
JIRA_URL = "https://company.atlassian.net"
JIRA_USER = "security-bot@company.com"
JIRA_TOKEN = "YOUR_JIRA_API_TOKEN"
JIRA_PROJECT = "SEC"
def create_ticket(alert: dict) -> dict:
"""Create a Jira issue from a Wazuh alert."""
rule = alert.get("rule", {})
agent = alert.get("agent", {})
level = rule.get("level", 0)
if level >= 12:
priority = "Critical"
elif level >= 10:
priority = "High"
elif level >= 7:
priority = "Medium"
else:
priority = "Low"
issue_data = {
"fields": {
"project": {"key": JIRA_PROJECT},
"summary": f"[Wazuh] {rule.get('description', 'Security Alert')}",
"description": (
f"*Alert Details*\n"
f"- Rule ID: {rule.get('id')}\n"
f"- Level: {level}\n"
f"- Agent: {agent.get('name')} ({agent.get('id')})\n"
f"- Timestamp: {alert.get('timestamp')}\n"
f"- Full log: {alert.get('full_log', 'N/A')}\n"
),
"issuetype": {"name": "Bug"},
"priority": {"name": priority},
"labels": ["wazuh", "security-alert"],
}
}
auth = b64encode(f"{JIRA_USER}:{JIRA_TOKEN}".encode()).decode()
headers = {
"Authorization": f"Basic {auth}",
"Content-Type": "application/json",
}
response = requests.post(
f"{JIRA_URL}/rest/api/3/issue",
json=issue_data,
headers=headers,
)
return response.json()
def main():
alert_file = sys.argv[1]
with open(alert_file) as f:
alert = json.load(f)
create_ticket(alert)
if __name__ == "__main__":
main()- Configure in
ossec.conf:
<integration>
<name>custom-jira</name>
<hook_url>https://company.atlassian.net</hook_url>
<api_key>YOUR_JIRA_API_TOKEN</api_key>
<level>10</level>
<alert_format>json</alert_format>
</integration>Ticketing Systems - ServiceNow
ServiceNow integration is implemented through the REST API or webhooks.
REST API Configuration
#!/usr/bin/env python3
"""ServiceNow integration for Wazuh - incident creation."""
import sys
import json
import requests
SNOW_INSTANCE = "company.service-now.com"
SNOW_USER = "wazuh-integration"
SNOW_PASSWORD = "YOUR_PASSWORD"
def create_incident(alert: dict) -> dict:
"""Create a ServiceNow incident from a Wazuh alert."""
rule = alert.get("rule", {})
agent = alert.get("agent", {})
level = rule.get("level", 0)
urgency_map = {range(12, 16): "1", range(10, 12): "2", range(7, 10): "3"}
urgency = "3"
for level_range, urg in urgency_map.items():
if level in level_range:
urgency = urg
break
incident = {
"short_description": f"Wazuh Alert: {rule.get('description')}",
"description": json.dumps(alert, indent=2),
"urgency": urgency,
"category": "Security",
"subcategory": "Threat Detection",
"assignment_group": "Security Operations",
"caller_id": "wazuh-integration",
}
response = requests.post(
f"https://{SNOW_INSTANCE}/api/now/table/incident",
json=incident,
auth=(SNOW_USER, SNOW_PASSWORD),
headers={"Content-Type": "application/json", "Accept": "application/json"},
)
return response.json()
def main():
alert_file = sys.argv[1]
with open(alert_file) as f:
alert = json.load(f)
create_incident(alert)
if __name__ == "__main__":
main()Alternative: ServiceNow Webhook
ServiceNow supports Inbound REST API, enabling the built-in webhook mechanism:
<integration>
<name>custom-servicenow</name>
<hook_url>https://company.service-now.com/api/now/table/incident</hook_url>
<api_key>BASE64_ENCODED_CREDENTIALS</api_key>
<level>10</level>
<alert_format>json</alert_format>
</integration>SOAR - Shuffle
Shuffle provides a visual workflow builder for automating incident response.
Advanced Shuffle Configuration
Beyond the basic webhook integration described in SIEM Integrations , Shuffle supports bidirectional communication with Wazuh through the API.
Example Workflow: IP Blocking via Active Response
- Trigger: webhook receives an alert from Wazuh
- Condition: check alert level >= 10 and srcip presence
- Enrichment: query IP reputation via AbuseIPDB
- Decision: if IP is in the blocklist, proceed
- Action: call the Wazuh API to trigger Active Response:
{
"command": "firewall-drop0",
"arguments": ["-", "srcip", "${srcip}"],
"alert": {
"data": {
"srcip": "${srcip}"
}
}
}- Notification: send the result to a Slack channel
Monitoring Shuffle Health
To monitor alert delivery to Shuffle, configure a health-check rule:
<rule id="100220" level="3">
<decoded_as>json</decoded_as>
<field name="integration">shuffle</field>
<description>Shuffle integration: alert forwarded successfully</description>
<group>integration_health,</group>
</rule>SOAR - Cortex XSOAR
Cortex XSOAR (formerly Demisto) is a commercial SOAR platform by Palo Alto Networks.
Integration via Syslog
Cortex XSOAR accepts data over syslog. Configure syslog forwarding in Wazuh:
<syslog_output>
<server>xsoar.example.com</server>
<port>1514</port>
<format>json</format>
<level>7</level>
</syslog_output>Integration via REST API
Cortex XSOAR provides a REST API for incident creation:
#!/usr/bin/env python3
"""Cortex XSOAR integration for Wazuh."""
import sys
import json
import requests
XSOAR_URL = "https://xsoar.example.com"
XSOAR_API_KEY = "YOUR_XSOAR_API_KEY"
def create_incident(alert: dict) -> dict:
"""Create a Cortex XSOAR incident."""
rule = alert.get("rule", {})
severity_map = {
range(0, 4): 0.5, # Info
range(4, 7): 1, # Low
range(7, 10): 2, # Medium
range(10, 12): 3, # High
range(12, 16): 4, # Critical
}
level = rule.get("level", 0)
severity = 1
for level_range, sev in severity_map.items():
if level in level_range:
severity = sev
break
incident = {
"name": f"Wazuh: {rule.get('description')}",
"type": "Wazuh Alert",
"severity": severity,
"details": json.dumps(alert),
"labels": [
{"type": "rule_id", "value": str(rule.get("id"))},
{"type": "agent", "value": alert.get("agent", {}).get("name", "")},
],
}
headers = {
"Authorization": XSOAR_API_KEY,
"Content-Type": "application/json",
"Accept": "application/json",
}
response = requests.post(
f"{XSOAR_URL}/incident",
json=incident,
headers=headers,
)
return response.json()
def main():
alert_file = sys.argv[1]
with open(alert_file) as f:
alert = json.load(f)
create_incident(alert)
if __name__ == "__main__":
main()Playbooks for Wazuh
Cortex XSOAR supports playbooks - automated response workflows. Recommended playbooks for Wazuh alerts:
| Playbook | Trigger | Actions |
|---|---|---|
| Brute Force Response | Rule group: authentication_failed | Block IP, notify SOC, create ticket |
| Malware Triage | Rule group: syscheck + VirusTotal match | Isolate host, collect artifacts, analyze |
| Vulnerability Management | Rule group: vulnerability-detector | Prioritize CVE, assign owner |
Troubleshooting
Osquery Returns No Results
- Check the Osquery daemon status:
systemctl status osqueryd- Verify the
<log_path>in the wodle matches the actual log location:
ls -la /var/log/osquery/osqueryd.results.log- Validate the Osquery configuration:
osqueryi --config_path /etc/osquery/osquery.conf --config_checkMISP Integration Errors
- SSL errors: ensure the MISP certificate is trusted on the Wazuh server, or set
VERIFY_SSL = Falsefor testing - 403 Forbidden: verify the API key permissions - at minimum,
auth_keyaccess to attributes is required - Timeouts: increase the requests timeout to 30 seconds for large IoC databases
Jira/ServiceNow Tickets Not Created
- Verify network connectivity:
curl -sk https://company.atlassian.net/rest/api/3/serverInfo- Confirm the API token has not expired
- Check project permissions - the integration user must have permission to create issues
Shuffle Workflow Not Triggering
- Review integratord logs:
grep shuffle /var/ossec/logs/integrations.log- Confirm the webhook URL in Shuffle is active (green indicator)
- Verify the workflow is published (not in Draft mode)
For guidance on writing custom integration scripts, see the Custom Integration Development section.