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
|
||||
Reference in New Issue
Block a user