# Wazuh 4.14 Detection Rules - Syntax and Logic

> Wazuh 4.14 rule syntax covering XML elements, classification levels 0-15, composite rules, MITRE ATT&CK mapping, CDB lists, and testing

Source: https://opennix.org/en/docs/wazuh/rules-decoders/wazuh-rules/


Wazuh rules define the threat detection logic: which events are security-relevant, what severity level to assign, and what metadata to attach to the alert. Each rule is described in XML and contains trigger conditions, a description, and a classification. This guide provides a detailed look at rule syntax, the severity level system, composite rules, and testing tools.

## XML Rule Structure

Every rule is enclosed in a `<rule>` element within a `<group>` container:

```xml
<group name="syslog,sshd,">
  <rule id="100001" level="5">
    <decoded_as>sshd</decoded_as>
    <match>Failed password</match>
    <description>SSH authentication failure.</description>
    <mitre>
      <id>T1110</id>
    </mitre>
    <group>authentication_failed,pci_dss_10.2.4,</group>
  </rule>
</group>
```

### Rule Element Attributes

| Attribute | Required | Description |
|---|---|---|
| `id` | Yes | Unique numeric identifier (1-999999) |
| `level` | Yes | Alert severity level (0-15) |
| `frequency` | No | Number of matches required to trigger |
| `timeframe` | No | Time window in seconds for `frequency` |
| `ignore` | No | Suppression duration for repeated alerts (seconds) |
| `maxsize` | No | Maximum event size |
| `noalert` | No | If `1`, the rule triggers without generating an alert |
| `overwrite` | No | If `yes`, overwrites the rule with the same `id` |

## Trigger Condition Elements

### Content Matching

**match** - searches for a substring or pattern in the log body:

```xml
<match>Failed password</match>
```

Supports `negate="yes"` (inversion) and `type` (osmatch, osregex, pcre2) attributes:

```xml
<match type="pcre2" negate="yes">successful|accepted</match>
```

**regex** - searches using a regular expression:

```xml
<regex>Failed password for (\S+) from (\S+)</regex>
```

**decoded_as** - triggers when the event matches a specific decoder:

```xml
<decoded_as>sshd</decoded_as>
```

**category** - triggers by decoder category:

```xml
<category>firewall</category>
```

### Extracted Field Matching

**field** - checks the value of a field extracted by a decoder:

```xml
<field name="log_type">error</field>
```

**srcip** / **dstip** - matches source or destination IP (supports CIDR notation):

```xml
<srcip>192.168.1.0/24</srcip>
<srcip negate="yes">10.0.0.0/8</srcip>
```

**srcport** / **dstport** - source or destination port:

```xml
<dstport>22</dstport>
```

**user** - username:

```xml
<user>root</user>
```

**hostname** - source hostname:

```xml
<hostname>web-server-01</hostname>
```

**program_name** - program name from the syslog header:

```xml
<program_name>sshd</program_name>
```

**action** - action extracted by the decoder:

```xml
<action>blocked</action>
```

**status** - event status:

```xml
<status>403</status>
```

**url** - URL from the event:

```xml
<url type="pcre2">/admin|/wp-login\.php</url>
```

**protocol** - transport protocol:

```xml
<protocol>TCP</protocol>
```

### Time Filters

**time** - time-of-day range (format hh:mm-hh:mm):

```xml
<time>22:00-06:00</time>
```

**weekday** - day of the week:

```xml
<weekday>weekend</weekday>
```

### Metadata and Description

**description** - human-readable rule description (required):

```xml
<description>Multiple SSH authentication failures from same source.</description>
```

**info** - supplementary information:

```xml
<info type="link">https://attack.mitre.org/techniques/T1110/</info>
<info type="text">Possible brute force attack detected.</info>
```

**group** - tags for grouping and filtering (comma-separated, trailing comma required):

```xml
<group>authentication_failed,brute_force,pci_dss_10.2.4,</group>
```

**mitre** - MITRE ATT&CK technique mapping:

```xml
<mitre>
  <id>T1110</id>
  <id>T1078</id>
</mitre>
```

**options** - additional behavioral flags:

```xml
<options>no_log</options>
<options>alert_by_email</options>
```

## Rule Classification Levels (0-15)

Wazuh uses 16 severity levels. Assigning the correct level is critical for alert prioritization.

| Level | Title | Description |
|---|---|---|
| 0 | Ignored | Event is ignored, no alert generated. Used to suppress false positives |
| 2 | System low priority notification | Status messages with no security significance |
| 3 | Successful/Authorized events | Legitimate actions such as successful logins or firewall permits |
| 4 | System low priority error | Configuration issues, minor system problems |
| 5 | User generated error | Authentication errors (failed login, wrong password) |
| 6 | Low relevance attack | Ineffective attacks, frequent IDS triggers with no real threat |
| 7 | Bad word matching | Events containing suspicious keywords but lacking clear classification |
| 8 | First time seen | Action performed for the first time (first login, new process) |
| 9 | Error from invalid source | Actions from unknown or suspicious sources |
| 10 | Multiple user generated errors | Repeated authentication failures (possible brute-force attack) |
| 11 | Integrity checking warning | Binary modifications, rootkit detection |
| 12 | High importance event | Critical system warnings, application-level attacks |
| 13 | Unusual error (high importance) | Patterns matching common attack methodologies |
| 14 | High importance security event | Correlation-driven alerts, probable attacks |
| 15 | Severe attack | Confirmed attack, immediate response required. No chance of false positives |

Rules at level 12 and above trigger real-time notifications (when alert integrations are configured).

## Rule Ordering and Overwriting

### Load Order

Wazuh loads rules in the following order:

1. Default rules from `/var/ossec/ruleset/rules/` (in alphabetical file order)
2. Custom rules from `/var/ossec/etc/rules/` (including `local_rules.xml`)

Custom rules are processed after default rules and can override them.

### Overwriting Default Rules

To modify the behavior of a default rule, use the `overwrite` attribute:

```xml
<!-- Overwrite default rule 5710 (change the level) -->
<rule id="5710" level="10" overwrite="yes">
  <decoded_as>sshd</decoded_as>
  <match>illegal user|invalid user</match>
  <description>SSH: attempt to login using a non-existent user.</description>
  <mitre>
    <id>T1110.001</id>
  </mitre>
  <group>authentication_failed,pci_dss_10.2.4,</group>
</rule>
```

An overwritten rule must contain the complete definition, not just the changed fields.

### Suppressing False Positives

To suppress a false positive, create a child rule with level 0:

```xml
<rule id="100100" level="0">
  <if_sid>5710</if_sid>
  <srcip>10.0.0.50</srcip>
  <description>Suppress SSH invalid user alert from monitoring system.</description>
</rule>
```

## Composite Rules (Correlation)

Composite rules detect patterns based on event sequences or frequencies.

### Parent Rule Linkage (if_sid)

**if_sid** - the rule triggers only if the specified rule has already fired:

```xml
<rule id="100010" level="10">
  <if_sid>5710</if_sid>
  <same_source_ip />
  <description>Multiple SSH invalid user attempts from same source.</description>
</rule>
```

**if_group** - triggers on group membership:

```xml
<rule id="100011" level="8">
  <if_group>authentication_failed</if_group>
  <match>root</match>
  <description>Authentication failure for root account.</description>
</rule>
```

**if_level** - triggers based on the level of a preceding event:

```xml
<rule id="100012" level="12">
  <if_level>10</if_level>
  <same_source_ip />
  <description>High severity event followed by another from same source.</description>
</rule>
```

### Frequency Rules (frequency + timeframe)

**if_matched_sid** - triggers when the specified rule fires `frequency` times within `timeframe` seconds:

```xml
<rule id="100020" level="10" frequency="5" timeframe="120">
  <if_matched_sid>5710</if_matched_sid>
  <same_source_ip />
  <description>SSH brute force: 5 invalid user attempts in 2 minutes.</description>
  <mitre>
    <id>T1110.001</id>
  </mitre>
  <group>brute_force,pci_dss_11.4,</group>
</rule>
```

**if_matched_group** - same concept, but by rule group:

```xml
<rule id="100021" level="10" frequency="8" timeframe="300">
  <if_matched_group>authentication_failed</if_matched_group>
  <same_source_ip />
  <description>Multiple authentication failures from same IP in 5 minutes.</description>
</rule>
```

### Field Correlation Elements

| Element | Description |
|---|---|
| `<same_source_ip />` | Source IP matches across correlated events |
| `<different_source_ip />` | Source IP differs |
| `<same_dst_ip />` | Destination IP matches |
| `<different_dst_ip />` | Destination IP differs |
| `<same_user />` | Username matches |
| `<different_user />` | Username differs |
| `<same_id />` | Identifier matches |
| `<same_location />` | Log source matches |
| `<same_protocol />` | Protocol matches |
| `<different_protocol />` | Protocol differs |

## CDB Lists (Constant Database)

CDB lists enable field value lookups against external reference tables (IP lists, IoCs, allowed processes).

### List Format

A list file contains key:value pairs (the value is optional):

```
192.168.1.100:known_scanner
10.0.0.50:monitoring_system
172.16.0.0/12:
```

### Usage in Rules

```xml
<rule id="100030" level="12">
  <if_sid>5710</if_sid>
  <list field="srcip" lookup="address_match_key">etc/lists/malicious-ips</list>
  <description>SSH login attempt from known malicious IP.</description>
</rule>
```

Lookup types:

| Type | Description |
|---|---|
| `match_key` | Exact key match |
| `not_match_key` | Key is absent from the list |
| `address_match_key` | IP address match (with CIDR support) |
| `not_address_match_key` | IP address is absent from the list |
| `match_key_value` | Key and value match |

### Registering Lists

Lists must be registered in `ossec.conf`:

```xml
<ruleset>
  <list>etc/lists/malicious-ips</list>
</ruleset>
```

After adding or modifying a list, reload the manager:

```bash
/var/ossec/bin/wazuh-control reload
```

## Default Ruleset Overview

Default Wazuh rules are organized by category into separate files.

| File | Category | Example Rules |
|---|---|---|
| `0015-ossec_rules.xml` | Wazuh internal | Agent status, manager errors |
| `0095-sshd_rules.xml` | SSH | Authentication, brute force, invalid user |
| `0100-syslog_rules.xml` | Syslog | Linux system events |
| `0200-attack_rules.xml` | Attacks | Common attack detection |
| `0210-pam_rules.xml` | PAM | PAM authentication |
| `0230-postfix_rules.xml` | Mail | Postfix mail server |
| `0240-apache_rules.xml` | Apache | Apache web server |
| `0250-nginx_rules.xml` | Nginx | Nginx web server |
| `0350-amazon_rules.xml` | AWS | CloudTrail, GuardDuty |
| `0470-windows_rules.xml` | Windows | Windows Event Log |
| `0575-win-firewall_rules.xml` | Windows Firewall | Windows Defender Firewall |
| `0620-win-security_rules.xml` | Windows Security | Windows security auditing |
| `0800-sysmon_rules.xml` | Sysmon | Windows process monitoring |
| `0840-docker_rules.xml` | Docker | Container events |

A complete listing of rule files is available in `/var/ossec/ruleset/rules/`.

## MITRE ATT&CK Mapping

Wazuh supports mapping rules to MITRE ATT&CK techniques and tactics, enabling ATT&CK matrix coverage visualization in the Dashboard.

```xml
<rule id="100050" level="12">
  <if_sid>5710</if_sid>
  <frequency>10</frequency>
  <timeframe>60</timeframe>
  <same_source_ip />
  <description>SSH brute force attack detected.</description>
  <mitre>
    <id>T1110.001</id>  <!-- Brute Force: Password Guessing -->
  </mitre>
</rule>
```

Technique identifiers must be verified against the official [MITRE ATT&CK matrix](https://attack.mitre.org/).

### Compliance Standard Groups

Rules may include tags that map to regulatory standards:

```xml
<group>pci_dss_10.2.4,gdpr_IV_32.2,hipaa_164.312.b,nist_800_53_AU.14,tsc_CC6.1,</group>
```

## Testing Rules with wazuh-logtest

The `wazuh-logtest` utility enables interactive rule testing.

### Interactive Mode

```bash
/var/ossec/bin/wazuh-logtest
```

Enter a log line and receive the analysis result:

```
Type one log per line

Mar  5 10:15:01 server sshd[12345]: Failed password for invalid user admin from 192.168.1.100 port 22 ssh2

**Phase 1: Completed pre-decoding.
       full event: 'Mar  5 10:15:01 server sshd[12345]: Failed password for invalid user admin from 192.168.1.100 port 22 ssh2'
       timestamp: 'Mar  5 10:15:01'
       hostname: 'server'
       program_name: 'sshd'

**Phase 2: Completed decoding.
       name: 'sshd'
       parent: 'sshd'
       srcip: '192.168.1.100'
       srcport: '22'
       srcuser: 'admin'

**Phase 3: Completed filtering (rules).
       id: '5710'
       level: '5'
       description: 'sshd: Attempt to login using a non-existent user.'
       groups: '['syslog', 'sshd', 'invalid_login', 'authentication_failed']'
```

### Batch Mode

To test a set of logs from a file:

```bash
cat test-logs.txt | /var/ossec/bin/wazuh-logtest -q
```

### Verifying a Specific Rule

To confirm that a specific rule triggers on a specific log, use the `-U` flag:

```bash
echo "test log line" | /var/ossec/bin/wazuh-logtest -U 100001:5:sshd
```

The command returns exit code 0 if the rule with id=100001, level=5, and decoder sshd fired.

### Verbose Output

For debugging, use the `-v` flag:

```bash
/var/ossec/bin/wazuh-logtest -v
```

## Rule ID Ranges

When creating custom rules, use identifiers in the 100000-999999 range to avoid conflicts with default rules.

| Range | Purpose |
|---|---|
| 100000-109999 | General security detections |
| 110000-119999 | Application-specific rules |
| 120000-129999 | AI/ML pipeline monitoring |
| 147000-147999 | File integrity and YARA |
| 191000-191999 | Privilege escalation |

## Rule Authoring Checklist

When creating a new rule, verify that all items are addressed:

- [ ] Rule identifier (`id`) is within the correct range
- [ ] Level (`level`) matches the event severity
- [ ] Description (`description`) is clear and actionable
- [ ] MITRE ATT&CK mapping (`mitre/id`) is specified and verified
- [ ] The decoder extracts the required fields before the rule fires
- [ ] The rule has been tested with `wazuh-logtest`
- [ ] No catastrophic backtracking risk in regular expressions
- [ ] Group tags (`group`) are populated and end with a comma

## Related Sections

- [Wazuh Decoders](/docs/wazuh/rules-decoders/wazuh-decoders/) - log data extraction
- [Log Analysis](/docs/wazuh/capabilities/wazuh-log-data-collection/) - log collection and processing
- [Active Response](/docs/wazuh/capabilities/wazuh-active-response/) - actions triggered by rules
- [Troubleshooting](/docs/wazuh/operations/wazuh-troubleshooting/) - diagnosing rule issues

