Wazuh Active Response - Automated Incident Response
The Active Response (AR) module in Wazuh provides automated incident response capabilities. When a detection rule fires, the server sends a command to the agent to execute a countermeasure - blocking an IP address, disabling a user account, or running a custom script. This mechanism reduces response time from minutes to seconds and neutralizes threats before an analyst intervenes.
How Active Response Works
Active Response operates in conjunction with the Wazuh rule engine. The incident processing sequence is as follows:
- The agent collects log data from the endpoint
- Data is forwarded to the Wazuh server for analysis
- The rule engine matches the event against the ruleset
- When a rule with a configured Active Response triggers, the server creates a command
- The command is sent to the agent (or executed locally on the server)
- The agent runs the corresponding response script
- Execution results are logged for auditing
Responses can be bound to specific rules by their identifier (rules_id), rule group (rules_group), or minimum severity level (level).
Built-in Response Scripts
Wazuh ships with a set of ready-made scripts located in /var/ossec/active-response/bin/ on agents.
firewall-drop
Blocks the attacker’s IP address at the network level. On Linux, it uses iptables; on Windows, it leverages Windows Firewall. This script is commonly used for automated blocking of brute-force attack sources, port scanners, and other network-based threats.
<command>
<name>firewall-drop</name>
<executable>firewall-drop</executable>
<timeout_allowed>yes</timeout_allowed>
</command>
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>5710,5711</rules_id>
<timeout>600</timeout>
</active-response>In this example, when rules 5710 or 5711 trigger (multiple failed SSH authentication attempts), the attacker’s IP address is blocked for 600 seconds.
host-deny
Adds the IP address to /etc/hosts.deny (TCP Wrappers). This operates on systems that support the TCP Wrappers mechanism - primarily Linux and BSD.
<command>
<name>host-deny</name>
<executable>host-deny</executable>
<timeout_allowed>yes</timeout_allowed>
</command>
<active-response>
<command>host-deny</command>
<location>local</location>
<level>8</level>
<timeout>3600</timeout>
</active-response>disable-account
Deactivates a user account. On Linux, it executes passwd -l; on Windows, it disables the account via net user. This script is applied when compromised user credentials are detected.
<command>
<name>disable-account</name>
<executable>disable-account</executable>
<timeout_allowed>yes</timeout_allowed>
</command>
<active-response>
<command>disable-account</command>
<location>local</location>
<rules_group>authentication_failures</rules_group>
<timeout>900</timeout>
</active-response>restart-wazuh
Restarts the Wazuh agent service. Used for applying updated configurations or recovering agent functionality after a failure.
<command>
<name>restart-wazuh</name>
<executable>restart-wazuh</executable>
<timeout_allowed>no</timeout_allowed>
</command>Stateful and Stateless Responses
Wazuh supports two Active Response operating modes.
Stateful (with rollback)
Stateful responses automatically revert the executed action after a specified timeout period. For example, firewall-drop with a 600-second timeout blocks an IP address and automatically unblocks it after 10 minutes. This mode is suitable for temporary threat containment and prevents permanent blocking of legitimate users due to false positives.
To enable stateful mode, specify <timeout_allowed>yes</timeout_allowed> in the <command> block and set the <timeout> value in the <active-response> block.
Stateless (no rollback)
Stateless responses execute an action once without automatic reversal. These are used for irreversible operations: sending notifications, creating system snapshots, or writing entries to an audit log.
For stateless mode, set <timeout_allowed> to no and omit <timeout> from the <active-response> block.
Active Response Configuration
Configuration consists of two XML blocks in ossec.conf on the Wazuh server.
The command Block
Defines the executable script and its parameters:
<command>
<name>my-custom-response</name>
<executable>my-script.sh</executable>
<timeout_allowed>yes</timeout_allowed>
</command>| Parameter | Description |
|---|---|
name | Unique command name referenced from the active-response block |
executable | Script name in the /var/ossec/active-response/bin/ directory |
timeout_allowed | Enables stateful mode with automatic rollback (yes/no) |
The active-response Block
Defines trigger conditions and execution parameters:
<active-response>
<command>my-custom-response</command>
<location>local</location>
<rules_id>100100,100101</rules_id>
<timeout>300</timeout>
</active-response>| Parameter | Description |
|---|---|
command | Command name from the <command> block |
location | Execution target: local (on the agent), server, defined-agent, all |
rules_id | Comma-separated list of rule IDs |
rules_group | Rule group that triggers the response |
level | Minimum rule severity level |
timeout | Seconds before automatic rollback (stateful) |
repeated_offenders | Escalating timeouts for repeat offenders |
The location Parameter
local- script runs on the agent that generated the alertserver- script runs on the Wazuh serverdefined-agent- script runs on a specified agent (requires<agent_id>)all- script runs on all agents
Repeated Offenders
The repeated_offenders parameter enables escalating block durations for recurring incidents from the same source:
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>5710</rules_id>
<timeout>600</timeout>
<repeated_offenders>1800,3600,86400</repeated_offenders>
</active-response>In this example: first block is 10 minutes, second is 30 minutes, third is 1 hour, fourth and subsequent blocks last 24 hours.
Custom Response Scripts
Wazuh allows creating custom Active Response scripts in any programming language. The script receives alert data via standard input (STDIN) in JSON format.
Input Data Format
The script receives a JSON object with the following fields:
{
"version": 1,
"origin": {
"name": "my-custom-response",
"module": "active-response"
},
"command": "add",
"parameters": {
"alert": {
"rule": {
"id": "5710",
"level": 10,
"description": "Multiple SSH authentication failures"
},
"data": {
"srcip": "192.168.1.100"
},
"agent": {
"id": "001",
"name": "web-server"
}
}
}
}The command field accepts either add (execute the action) or delete (revert the action for stateful scripts).
Bash Script Example
#!/bin/bash
LOCAL=$(dirname "$0")
LOG_FILE="/var/ossec/logs/active-responses.log"
read -r INPUT_JSON
COMMAND=$(echo "$INPUT_JSON" | jq -r '.command')
SRCIP=$(echo "$INPUT_JSON" | jq -r '.parameters.alert.data.srcip // empty')
if [ -z "$SRCIP" ]; then
echo "$(date) - No source IP found" >> "$LOG_FILE"
exit 1
fi
if [ "$COMMAND" = "add" ]; then
echo "$(date) - Blocking IP: $SRCIP" >> "$LOG_FILE"
iptables -A INPUT -s "$SRCIP" -j DROP
elif [ "$COMMAND" = "delete" ]; then
echo "$(date) - Unblocking IP: $SRCIP" >> "$LOG_FILE"
iptables -D INPUT -s "$SRCIP" -j DROP
fi
exit 0Python Script Example
#!/usr/bin/env python3
import sys
import json
import datetime
LOG_FILE = "/var/ossec/logs/active-responses.log"
def log_message(message):
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
with open(LOG_FILE, "a") as f:
f.write(f"{timestamp} - {message}\n")
def main():
input_data = sys.stdin.readline().strip()
if not input_data:
log_message("No input received")
sys.exit(1)
try:
alert = json.loads(input_data)
except json.JSONDecodeError:
log_message("Invalid JSON input")
sys.exit(1)
command = alert.get("command", "")
srcip = alert.get("parameters", {}).get("alert", {}).get("data", {}).get("srcip", "")
if not srcip:
log_message("No source IP in alert")
sys.exit(1)
if command == "add":
log_message(f"Custom action triggered for IP: {srcip}")
# Custom remediation logic here
elif command == "delete":
log_message(f"Custom action reverted for IP: {srcip}")
# Revert logic here
sys.exit(0)
if __name__ == "__main__":
main()Deploying a Custom Script
- Place the script in
/var/ossec/active-response/bin/on the agent - Set permissions:
chmod 750 /var/ossec/active-response/bin/my-script.sh - Set ownership to
root:wazuh - Add
<command>and<active-response>blocks toossec.confon the server - Restart the Wazuh manager to apply changes
Rule-triggered Responses
Binding to Specific Rules
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>31104,31105,31106</rules_id>
<timeout>600</timeout>
</active-response>Binding to a Rule Group
<active-response>
<command>host-deny</command>
<location>local</location>
<rules_group>web_attacks</rules_group>
<timeout>1800</timeout>
</active-response>Binding to Severity Level
<active-response>
<command>disable-account</command>
<location>local</location>
<level>12</level>
<timeout>0</timeout>
</active-response>With <timeout>0</timeout>, the action executes without automatic rollback - the account remains locked until manually re-enabled.
Testing Active Response
Validating Configuration
Before deployment, verify the configuration syntax:
docker exec wazuh-manager /var/ossec/bin/wazuh-control reloadManual Script Execution
For testing, run the response script manually on the agent:
echo '{"version":1,"origin":{"name":"test","module":"active-response"},"command":"add","parameters":{"alert":{"data":{"srcip":"10.0.0.99"}}}}' | /var/ossec/active-response/bin/firewall-dropChecking the Active Response Log
The response script execution log is located on the agent:
tail -f /var/ossec/logs/active-responses.logUsing wazuh-logtest
Use wazuh-logtest to verify that rules with associated responses trigger correctly:
docker exec -it wazuh-manager /var/ossec/bin/wazuh-logtestComparison with Other Platforms
| Capability | Wazuh Active Response | Splunk SOAR | ELK Watcher |
|---|---|---|---|
| Automated response | Built-in, agent-level | Via playbooks and integrations | Via actions (email, webhook) |
| Endpoint execution | Yes, agent runs the script | Through connectors and apps | No, server-side actions only |
| Stateful with rollback | Yes, built-in timeout | Through playbook logic | No |
| Custom scripts | Bash, Python, any language | Python playbooks | No, only preset actions |
| Cost | Free (open source) | Commercial license | Basic free, advanced paid |
| Configuration complexity | Low (XML configuration) | High (visual editor) | Medium (JSON/YAML) |
More about detection rules: Wazuh Architecture
Troubleshooting
Script Does Not Execute
- Check script permissions:
ls -la /var/ossec/active-response/bin/ - Confirm the script has
750permissions androot:wazuhownership - Review
/var/ossec/logs/ossec.logfor errors - Verify the name in
<executable>matches the script filename
Block Is Not Automatically Removed
- Confirm
<timeout_allowed>yes</timeout_allowed>is set in the<command>block - Verify a
<timeout>value is defined in the<active-response>block - Check that the script properly handles the
deletecommand
False Positives
- Refine trigger conditions: use
<rules_id>instead of<level> - Add exceptions through whitelists in Wazuh rules
- Raise the trigger threshold in the detection rule
- Start with a longer timeout and reduce it as you fine-tune
Response Executes but Is Not Logged
- Check the existence and permissions of
/var/ossec/logs/active-responses.log - Verify the script writes execution results to the log
More about agent installation: Installing the Wazuh Agent
More about Wazuh components: Platform Components