# Wazuh Active Response - Automated Incident Response

> Configuring the Wazuh 4.14 Active Response module - built-in and custom response scripts, stateful and stateless modes, integration with detection rules

Source: https://opennix.org/en/docs/wazuh/capabilities/wazuh-active-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:

1. The agent collects log data from the endpoint
2. Data is forwarded to the Wazuh server for analysis
3. The rule engine matches the event against the ruleset
4. When a rule with a configured Active Response triggers, the server creates a command
5. The command is sent to the agent (or executed locally on the server)
6. The agent runs the corresponding response script
7. 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.

```xml
<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.

```xml
<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.

```xml
<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.

```xml
<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:

```xml
<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:

```xml
<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 alert
- `server` - script runs on the Wazuh server
- `defined-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:

```xml
<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:

```json
{
  "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

```bash
#!/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 0
```

### Python Script Example

```python
#!/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

1. Place the script in `/var/ossec/active-response/bin/` on the agent
2. Set permissions: `chmod 750 /var/ossec/active-response/bin/my-script.sh`
3. Set ownership to `root:wazuh`
4. Add `<command>` and `<active-response>` blocks to `ossec.conf` on the server
5. Restart the Wazuh manager to apply changes

## Rule-triggered Responses

### Binding to Specific Rules

```xml
<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

```xml
<active-response>
  <command>host-deny</command>
  <location>local</location>
  <rules_group>web_attacks</rules_group>
  <timeout>1800</timeout>
</active-response>
```

### Binding to Severity Level

```xml
<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:

```bash
docker exec wazuh-manager /var/ossec/bin/wazuh-control reload
```

### Manual Script Execution

For testing, run the response script manually on the agent:

```bash
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-drop
```

### Checking the Active Response Log

The response script execution log is located on the agent:

```bash
tail -f /var/ossec/logs/active-responses.log
```

### Using wazuh-logtest

Use `wazuh-logtest` to verify that rules with associated responses trigger correctly:

```bash
docker exec -it wazuh-manager /var/ossec/bin/wazuh-logtest
```

## Comparison 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](/docs/wazuh/getting-started/wazuh-architecture/)

## Troubleshooting

### Script Does Not Execute

- Check script permissions: `ls -la /var/ossec/active-response/bin/`
- Confirm the script has `750` permissions and `root:wazuh` ownership
- Review `/var/ossec/logs/ossec.log` for 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 `delete` command

### 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](/docs/wazuh/installation/wazuh-agent-installation/)

More about Wazuh components: [Platform Components](/docs/wazuh/getting-started/wazuh-components/)

