Files
wazuh/configs/RULES.md
T
2026-07-31 16:44:09 +00:00

390 lines
14 KiB
Markdown

# Custom Rules Reference
Detailed reference for every rule in `local_rules.xml`, organized by service. Includes rule purpose, dependencies, and expected firing behavior.
## Table of contents
- [SSH](#ssh)
- [pfSense](#pfsense)
- [Reputation / attack lookups](#reputation--attack-lookups)
- [Dovecot: suppression and recon](#dovecot-suppression-and-recon)
- [Dovecot: successful login primitive](#dovecot-successful-login-primitive)
- [Gitea](#gitea)
- [Postfix mail correlation](#postfix-mail-correlation)
---
## SSH
### 100001 - SSH failed auth from specific IP (example)
```xml
<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>
```
Example rule inherited from the Wazuh default `local_rules.xml`. Fires when shipped rule 5716 (SSH auth failure) matches AND source IP is exactly 1.1.1.1. Placeholder; replace or delete when the SSH ruleset is actually built out.
---
## pfSense
Note: rule IDs 87699, 87761, 87762 are below the standard user range (100000+). These override or extend Wazuh's shipped pfSense correlation to work with the wrapped log format arriving from the homelab pfSense box.
### 87699 - pfSense wrapped parent (bridge rule)
```xml
<rule id="87699" level="0">
<decoded_as>pfsense-wrapped</decoded_as>
<description>pfSense wrapped syslog parent rule.</description>
</rule>
```
Level 0 (no alert). Exists so downstream rules can chain off `if_sid>87699</if_sid>` and get evaluated against wrapped pfSense logs.
### 87761 - pfSense firewall drop event
```xml
<rule id="87761" level="5">
<if_sid>87699</if_sid>
<action>block</action>
<description>pfSense firewall drop event (wrapped).</description>
<group>pfsense,firewall_block,...</group>
</rule>
```
Level 5. Fires on any pfSense firewall block action. Sets up correlation for 87762.
### 87762 - Multiple pfSense blocks from same source
```xml
<rule id="87762" level="10" frequency="18" timeframe="45" ignore="240">
<if_matched_sid>87761</if_matched_sid>
<same_srcip />
<description>Multiple pfSense firewall block events from same source (wrapped).</description>
<mitre><id>T1110</id></mitre>
<group>pfsense,</group>
</rule>
```
Level 10. Fires when 18 firewall drops from the same source IP occur within 45 seconds. `ignore="240"` prevents re-firing on the same source for 240 seconds after triggering. Mapped to MITRE T1110 (brute force).
---
## Reputation / attack lookups
### 100100 - AlienVault reputation match
```xml
<rule id="100100" level="10">
<if_group>web|attack|attacks</if_group>
<list field="srcip" lookup="address_match_key">etc/lists/blacklist-alienvault</list>
<description>IP address found in AlienVault reputation database.</description>
</rule>
```
Level 10. Fires when any web-related or attack-tagged event has a source IP that matches the AlienVault OTX blacklist CDB. Requires the AlienVault list to be maintained and rebuilt periodically via `wazuh-makelists`.
---
## Dovecot: suppression and recon
Rules 100200-100206 handle Dovecot-related events. Most are level 0 suppressions for expected internal traffic; 100205/100206 detect port scanning against IMAP.
### 100200 - Suppress mailcow watchdog auth attempts
```xml
<rule id="100200" level="0">
<if_sid>9705</if_sid>
<match>watchdog@invalid</match>
<description>Dovecot: mailcow watchdog health check (ignored)</description>
</rule>
```
Mailcow's internal watchdog attempts to auth as `watchdog@invalid` to verify Dovecot is responsive. This suppresses those alerts.
### 100201 - Suppress internal Docker IMAP probes
```xml
<rule id="100201" level="0">
<if_sid>9707</if_sid>
<match>rip=172.22.1.</match>
<description>Dovecot: mailcow watchdog IMAP probe disconnect (ignored)</description>
</rule>
```
Suppresses disconnect events originating from the mailcow Docker network (172.22.1.x). These are watchdog probes and inter-container health checks.
### 100202 - Suppress own-mailbox routine disconnects
```xml
<rule id="100202" level="0">
<if_sid>9706</if_sid>
<match>imap(chris@wittenberger.us)</match>
<description>Dovecot: own-mailbox routine session disconnect (ignored)</description>
</rule>
```
Suppresses routine IMAP session disconnects for the administrator's own mailbox. Prevents noise from Chris's own mail clients.
### 100203 - Suppress managesieve health checks
```xml
<rule id="100203" level="0">
<if_sid>9706</if_sid>
<match>managesieve-login: Disconnected: Connection closed (no auth attempts</match>
<description>Mailcow watchdog managesieve healthcheck - suppressed</description>
</rule>
```
Suppresses managesieve-login disconnects that occur without auth attempts. These are internal health checks against the sieve rules service.
### 100205 - No-auth disconnect primitive
```xml
<rule id="100205" level="0">
<if_sid>9700</if_sid>
<match>no auth attempts</match>
<description>Dovecot: connection with no auth attempt from $(srcip)</description>
<group>dovecot,recon,</group>
</rule>
```
Level 0 primitive rule. Fires on Dovecot disconnects where the client never attempted authentication. This is a port scan or reconnaissance signature. Level 0 keeps individual events from alerting; correlation happens in 100206.
### 100206 - Aggressive port scanning correlation
```xml
<rule id="100206" level="8" frequency="20" timeframe="300">
<if_matched_sid>100205</if_matched_sid>
<same_srcip />
<description>Dovecot: aggressive port scanning from $(srcip) - possible attack precursor</description>
<group>dovecot,recon,attack,</group>
</rule>
```
Level 8. Fires when a single IP triggers 20 no-auth-disconnect events within 5 minutes. Distinguishes legitimate one-off scanners (Shodan, Censys, Onyphe) from aggressive reconnaissance that typically precedes actual attacks.
---
## Dovecot: successful login primitive
### 100204 - Successful Dovecot login (suppressed for alerting)
```xml
<rule id="100204" level="0" overwrite="no">
<if_sid>9701</if_sid>
<description>Dovecot successful login - suppressed (routine IMAP polling)</description>
</rule>
```
Level 0. Fires on any successful Dovecot login (matches shipped rule 9701). Suppressed from alerting because successful logins are constant background traffic. Critically, this rule STILL FIRES INTERNALLY when 9701 matches, which is what rule 100311 needs for compromise correlation.
**Do not delete this rule.** Rule 100311 references it via `if_sid>100204</if_sid>`. Removing it breaks the compromise correlation chain.
---
## Gitea
Rules 100400-100470 cover Gitea authentication, account lifecycle, SSH key management, and repository events.
### 100400 - Gitea event parent
```xml
<rule id="100400" level="0">
<decoded_as>gitea</decoded_as>
<description>Gitea event (parent)</description>
</rule>
```
Level 0. Gates all Gitea-specific child rules. Fires on any event decoded by the `gitea` decoder.
### 100401, 100402 - Router polling / HTTP request suppression
Suppress Gitea's high-volume "router polling" and "router completed" HTTP request logs. These are routine background traffic and would flood the alert log.
### 100410 - Failed Gitea auth
```xml
<rule id="100410" level="5">
<if_sid>100400</if_sid>
<match>Failed authentication attempt</match>
<description>Gitea: failed authentication attempt</description>
<mitre><id>T1110</id></mitre>
</rule>
```
Level 5. Fires on individual failed Gitea login attempts.
### 100411 - Gitea brute force
```xml
<rule id="100411" level="10" frequency="5" timeframe="120">
<if_matched_sid>100410</if_matched_sid>
<description>Gitea: possible brute force (5+ failed logins in 2 min)</description>
</rule>
```
Level 10. Fires when 5 or more failed Gitea logins occur within 2 minutes. Note: does NOT use `same_srcip` currently. Consider adding it if distributed attacks against Gitea become an issue.
### 100420, 100421 - Account lifecycle
- **100420** (level 5): New user account created
- **100421** (level 7): User account deleted (higher severity because account deletion is unusual and worth investigating)
### 100430, 100431 - SSH key management
- **100430** (level 5): SSH key added to a Gitea account
- **100431** (level 3): SSH key removed from a Gitea account (routine)
Track key changes because SSH keys grant repo access; unauthorized key additions could indicate account takeover.
### 100440 - Access token activity
Level 5. Fires on access token creation or use. Access tokens are alternate auth for Gitea and worth monitoring for unusual patterns.
### 100450 - Password reset / recovery
Level 5. Fires on password reset flows. Unusual reset patterns can indicate account takeover attempts.
### 100460 - Repository deleted
Level 7. Higher severity because repo deletion is destructive and worth immediate attention.
### 100470 - 2FA event
Level 5. Fires on 2FA setup, disable, or use events. Monitoring 2FA state changes helps detect account-security downgrade attacks.
---
## Postfix mail correlation
Rules 100310-100316 detect various brute force patterns against the mailcow SMTP submission service. All key off shipped rule 3332 (Postfix SASL failure) with srcip populated by the `mailcow-postfix-sasl-fail` decoder.
### 100310 - Single-source brute force
```xml
<rule id="100310" level="10" frequency="5" timeframe="120">
<if_matched_sid>3332</if_matched_sid>
<same_srcip />
<description>Postfix SASL: brute force login attempt from $(srcip)</description>
<group>authentication_failed,brute_force,</group>
</rule>
```
Level 10. Fires when 5 SASL failures come from the same source IP within 2 minutes. Classic single-source brute force.
### 100311 - Account compromise (brute force followed by successful login)
```xml
<rule id="100311" level="14" timeframe="120">
<if_matched_sid>100310</if_matched_sid>
<if_sid>100204</if_sid>
<same_srcip />
<description>Mail: successful login after brute force from $(srcip) - ACCOUNT COMPROMISED</description>
</rule>
```
Level 14. Fires when a Dovecot successful login (rule 100204) occurs from the same source IP that just triggered a brute force alert (100310), within 2 minutes. Indicates a successful compromise.
### 100312 - Distributed brute force targeting one user
```xml
<rule id="100312" level="10" frequency="5" timeframe="300">
<if_matched_sid>3332</if_matched_sid>
<same_user />
<different_srcip />
<description>Postfix SASL: distributed brute force targeting user $(dstuser)</description>
</rule>
```
Level 10. Fires when 5 SASL failures target the same username from different source IPs within 5 minutes. Catches credential stuffing that rotates IPs.
### 100313 - Persistent single-source brute force
```xml
<rule id="100313" level="12" frequency="3" timeframe="86400">
<if_matched_sid>100310</if_matched_sid>
<same_srcip />
<description>Postfix SASL: persistent brute force from $(srcip) - 3+ bursts in 24h</description>
</rule>
```
Level 12. Fires when the same IP triggers 100310 three or more times in 24 hours. Confirms a persistent adversary rather than a one-off scan.
### 100314 - Subnet-coordinated brute force
```xml
<rule id="100314" level="12" frequency="15" timeframe="3600">
<if_matched_sid>3332</if_matched_sid>
<same_field>src_subnet</same_field>
<description>Postfix SASL: coordinated brute force from subnet $(src_subnet).0/24</description>
</rule>
```
Level 12. Fires when 15 SASL failures come from the same /24 subnet within 1 hour. Uses the `src_subnet` field extracted by the mailcow-postfix-sasl-fail decoder. Catches botnets rotating through hosting provider ranges.
### 100315 - Confirmed credential stuffing
```xml
<rule id="100315" level="10" frequency="10" timeframe="600">
<if_matched_sid>3332</if_matched_sid>
<same_user />
<different_srcip />
<description>Postfix SASL: credential stuffing against user $(dstuser) from multiple IPs</description>
</rule>
```
Level 10. Higher-confidence version of 100312. Requires 10 attempts against the same username from 10 different IPs within 10 minutes. Very low false positive rate.
### 100316 - High volume attack detection
```xml
<rule id="100316" level="8" frequency="50" timeframe="3600">
<if_matched_sid>3332</if_matched_sid>
<description>Postfix SASL: high volume of authentication failures (50+ in 1h)</description>
</rule>
```
Level 8. Volume alarm. Fires on 50 SASL failures in an hour regardless of source or target. Catches attacks that rotate both IPs and usernames.
---
## Rule interaction summary
| Rule | Triggers when | Correlates with |
|------|---------------|----------------|
| 100310 | 5 failures, same IP, 2min | Base for 100311, 100313 |
| 100311 | 100310 + 100204 success | Terminal alert (compromise) |
| 100312 | 5 failures, same user, different IPs, 5min | Standalone |
| 100313 | 100310 3x in 24h | Persistence signal |
| 100314 | 15 failures from same /24, 1h | Subnet coordination |
| 100315 | 10 failures, same user, different IPs, 10min | High-confidence credential stuffing |
| 100316 | 50 failures / hour | Volume alarm |
Detection coverage matrix by attack shape:
| Attack pattern | Rule that catches it |
|----------------|---------------------|
| Single-IP brute force | 100310 |
| Compromise after brute force | 100311 |
| Distributed against one user | 100312, 100315 |
| Same attacker returning repeatedly | 100313 |
| Botnet in same subnet range | 100314 |
| High-volume general attack | 100316 |
## Testing rules
For any new correlation rule, remember: `wazuh-logtest` does NOT test rule correlation (frequency counters don't accumulate between separate logtest invocations). Test correlation rules by:
1. Injecting real events at the appropriate rate into the log path
2. Watching alerts.log for the rule to fire
For simple field-match rules, `wazuh-logtest` works fine.
Last updated: 2026-07-31