Upload files to "configs"
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
# Custom Decoders Reference
|
||||
|
||||
Detailed reference for every custom decoder in `local_decoder.xml`. Documents what each decoder matches, what fields it extracts, and why it exists.
|
||||
|
||||
## Table of contents
|
||||
|
||||
- [pfsense-wrapped and children](#pfsense-wrapped-and-children)
|
||||
- [mailcow-journald-unwrap and child](#mailcow-journald-unwrap-and-child)
|
||||
- [gitea and gitea-auth-fail](#gitea-and-gitea-auth-fail)
|
||||
- [mailcow-dovecot-success](#mailcow-dovecot-success)
|
||||
- [mailcow-postfix-sasl-fail](#mailcow-postfix-sasl-fail)
|
||||
|
||||
---
|
||||
|
||||
## pfsense-wrapped and children
|
||||
|
||||
**Purpose.** Parse pfSense filter log entries that arrive wrapped in additional syslog headers from the pfSense box.
|
||||
|
||||
**Structure.** One parent decoder plus four children that extract different sections of the log line via chained `offset="after_regex"` extractions.
|
||||
|
||||
### pfsense-wrapped (parent)
|
||||
|
||||
```xml
|
||||
<decoder name="pfsense-wrapped">
|
||||
<prematch>filterlog</prematch>
|
||||
</decoder>
|
||||
```
|
||||
|
||||
Matches any log line containing `filterlog` (the pfSense filter daemon).
|
||||
|
||||
### pfsense-wrapped-fields (first child)
|
||||
|
||||
```xml
|
||||
<decoder name="pfsense-wrapped-fields">
|
||||
<parent>pfsense-wrapped</parent>
|
||||
<regex>filterlog\S* \S*,\S*,\S*,(\S*),\S*,\S*,(\S*),</regex>
|
||||
<order>id,action</order>
|
||||
</decoder>
|
||||
```
|
||||
|
||||
Extracts rule ID and action (block/pass) from the CSV-like pfSense filter log.
|
||||
|
||||
### pfsense-wrapped-fields (second child)
|
||||
|
||||
```xml
|
||||
<regex offset="after_regex">\S*,\S*,\S*,\S*,\S*,\S*,\S*,\S*,\S*,(\S*),\S*,(\S*),(\S*),</regex>
|
||||
<order>protocol,srcip,dstip</order>
|
||||
```
|
||||
|
||||
Continues from where the first child left off. Extracts protocol, source IP, destination IP.
|
||||
|
||||
### pfsense-wrapped-fields (third child)
|
||||
|
||||
```xml
|
||||
<regex offset="after_regex">(\d*),(\d*),\S*</regex>
|
||||
<order>srcport,dstport</order>
|
||||
```
|
||||
|
||||
Extracts source and destination ports.
|
||||
|
||||
### pfsense-wrapped-fields (fourth child)
|
||||
|
||||
```xml
|
||||
<regex offset="after_regex">datalength=(\S*)|(\d*)</regex>
|
||||
<order>length</order>
|
||||
```
|
||||
|
||||
Extracts packet length.
|
||||
|
||||
**Note.** All four children share the same name deliberately. Wazuh's chained extraction pattern supports multiple children with identical names when each uses `offset="after_regex"` to continue from the previous child's position.
|
||||
|
||||
**Referenced by.** Rules 87699, 87761, 87762 (pfSense correlation chain).
|
||||
|
||||
---
|
||||
|
||||
## mailcow-journald-unwrap and child
|
||||
|
||||
**Purpose.** Strip mailcow's outer journald syslog envelope to expose the inner Postfix log for further decoding.
|
||||
|
||||
```xml
|
||||
<decoder name="mailcow-journald-unwrap">
|
||||
<prematch>postfix\(\d+\): \w+ \d+ \d+:\d+:\d+ \w+ \.+\(\d+\):</prematch>
|
||||
</decoder>
|
||||
|
||||
<decoder name="mailcow-journald-unwrap-child">
|
||||
<parent>mailcow-journald-unwrap</parent>
|
||||
<regex offset="after_parent">\.+</regex>
|
||||
<order>extra_data</order>
|
||||
</decoder>
|
||||
```
|
||||
|
||||
**Status.** Present but not actively referenced by current rules. Reserved for future use if additional Postfix log types need parsing beyond what `mailcow-postfix-sasl-fail` handles.
|
||||
|
||||
---
|
||||
|
||||
## gitea and gitea-auth-fail
|
||||
|
||||
**Purpose.** Parse Gitea log lines for authentication and account activity.
|
||||
|
||||
### gitea (parent)
|
||||
|
||||
```xml
|
||||
<decoder name="gitea">
|
||||
<program_name>gitea</program_name>
|
||||
</decoder>
|
||||
```
|
||||
|
||||
Matches on syslog program name `gitea`. Sets up the child decoder chain for Gitea-specific extraction.
|
||||
|
||||
### gitea-auth-fail (child)
|
||||
|
||||
```xml
|
||||
<decoder name="gitea-auth-fail">
|
||||
<parent>gitea</parent>
|
||||
<prematch>Failed authentication attempt</prematch>
|
||||
<regex>Failed authentication attempt for (\S+) from (\d+.\d+.\d+.\d+)</regex>
|
||||
<order>user, srcip</order>
|
||||
</decoder>
|
||||
```
|
||||
|
||||
Fires on Gitea failed login attempts. Extracts the attempted username and the source IP.
|
||||
|
||||
Sample log this matches:
|
||||
```
|
||||
2026/05/29 14:19:59 routers/web/auth/auth.go:309:SignInPost() [W] Failed authentication attempt for admin from 192.0.2.100
|
||||
```
|
||||
|
||||
**Referenced by.** Rules 100400 through 100470 (all Gitea rules).
|
||||
|
||||
---
|
||||
|
||||
## mailcow-dovecot-success
|
||||
|
||||
**Purpose.** Extract source IP from any Dovecot event containing `rip=IP,`. Universally useful because it captures the source IP from successful logins, disconnects, port scan probes, and reconnaissance activity - all of which include the `rip=` field.
|
||||
|
||||
```xml
|
||||
<decoder name="mailcow-dovecot-success">
|
||||
<parent>dovecot</parent>
|
||||
<regex>rip=(\S+),</regex>
|
||||
<order>srcip</order>
|
||||
</decoder>
|
||||
```
|
||||
|
||||
**Design note.** Despite the name suggesting it only handles successful logins, this decoder actually populates srcip on ALL Dovecot events that include `rip=`. This was the result of iterative debugging - simpler patterns worked better than more specific ones. Consider renaming to `mailcow-dovecot-rip` in a future refactor.
|
||||
|
||||
**Field extracted:**
|
||||
- `srcip` - source IP from the `rip=` field
|
||||
|
||||
**Referenced by.** Rules 100200-100206 (Dovecot suppression and recon), 100311 (compromise correlation, indirectly via rule 100204).
|
||||
|
||||
---
|
||||
|
||||
## mailcow-postfix-sasl-fail
|
||||
|
||||
**Purpose.** Extract source IP, subnet, and target username from mailcow-wrapped Postfix SASL authentication failure logs. Enables correlation on all three fields.
|
||||
|
||||
```xml
|
||||
<decoder name="mailcow-postfix-sasl-fail">
|
||||
<parent>postfix</parent>
|
||||
<prematch type="pcre2">postfix/\S+smtpd\S*: warning: \S+: SASL </prematch>
|
||||
<regex type="pcre2">unknown\[((\d+\.\d+\.\d+)\.\d+)\]: SASL \S+ authentication failed.*sasl_username=(\S+)</regex>
|
||||
<order>srcip, src_subnet, dstuser</order>
|
||||
</decoder>
|
||||
```
|
||||
|
||||
**Fields extracted:**
|
||||
|
||||
| Field | Example | Notes |
|
||||
|-------|---------|-------|
|
||||
| `srcip` | `192.0.2.100` | Full IPv4 source address |
|
||||
| `src_subnet` | `192.0.2` | First three octets, for /24 correlation |
|
||||
| `dstuser` | `admin` | The sasl_username the attacker attempted |
|
||||
|
||||
**Why PCRE2.** OSSEC regex lacks the `.*` quantifier needed to match the variable content between "authentication failed" and "sasl_username=" in Postfix logs. PCRE2 mode handles it cleanly.
|
||||
|
||||
**Why not shipped decoder alone.** Wazuh's shipped `postfix-sasl` decoder assumes standard Postfix log format like `postfix/smtpd[PID]: ...`. Mailcow wraps every log in an outer journald envelope: `Jul 31 02:00:03 mailcow postfix[142]: Jul 30 21:00:03 e239f49ad459 postfix/submission/smtpd: ...`. The shipped decoder's anchored regex fails on the wrapped format, so srcip never gets extracted, and correlation on `same_srcip` never fires.
|
||||
|
||||
**IPv4-only.** This decoder requires dotted-decimal IPv4 addresses. IPv6 attackers (rare against SMTP submission but possible) would fall through to the shipped `postfix` decoder alone with no field extraction. Add a separate IPv6 decoder if that becomes an issue.
|
||||
|
||||
**Referenced by.** Rules 3332 (shipped Postfix SASL failure alert), 100310-100316 (all mail correlation rules).
|
||||
|
||||
---
|
||||
|
||||
## Testing decoders
|
||||
|
||||
For any decoder change, verify with `wazuh-logtest`:
|
||||
|
||||
```bash
|
||||
sudo /var/ossec/bin/wazuh-logtest
|
||||
```
|
||||
|
||||
Paste a real log line at the prompt. Look at Phase 2:
|
||||
|
||||
- Verify the `name:` matches the decoder you expect
|
||||
- Verify each field in `<order>` shows up as `field_name: 'value'`
|
||||
|
||||
If Phase 2 doesn't show the expected fields, the regex isn't matching. Common causes:
|
||||
|
||||
1. Regex has `^` anchor but log doesn't start at that position
|
||||
2. XML entities not escaped (`<` should be `<`, `>` should be `>`)
|
||||
3. OSSEC regex used where PCRE2 syntax is required
|
||||
4. Prematch too strict, not matching the log line
|
||||
5. Parent decoder didn't match, so children never evaluated
|
||||
|
||||
See `README.md` for full troubleshooting notes.
|
||||
|
||||
Last updated: 2026-07-31
|
||||
@@ -0,0 +1,389 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user