Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .tests/postfix-sasl-bf/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
parsers:
- crowdsecurity/postfix-logs
- crowdsecurity/syslog-logs
- crowdsecurity/dateparse-enrich
scenarios:
- Guezli/postfix-sasl-bf
postoverflows:
- ""
log_file: postfix-sasl-bf.log
log_type: syslog
ignore_parsers: true
Empty file.
3 changes: 3 additions & 0 deletions .tests/postfix-sasl-bf/postfix-sasl-bf.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
May 11 04:02:33 host1 postfix/smtpd[26897]: warning: unknown[1.2.3.4]: SASL LOGIN authentication failed: authentication failure
May 11 04:02:34 host1 postfix/smtpd[26897]: warning: unknown[1.2.3.4]: SASL LOGIN authentication failed: authentication failure
May 11 04:02:35 host1 postfix/smtpd[26897]: warning: unknown[1.2.3.4]: SASL LOGIN authentication failed: authentication failure
14 changes: 14 additions & 0 deletions .tests/postfix-sasl-bf/scenario.assert
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
len(results) == 1
"1.2.3.4" in results[0].Overflow.GetSources()
results[0].Overflow.Sources["1.2.3.4"].IP == "1.2.3.4"
results[0].Overflow.Sources["1.2.3.4"].GetScope() == "Ip"
results[0].Overflow.Sources["1.2.3.4"].GetValue() == "1.2.3.4"
results[0].Overflow.Alert.Events[0].GetMeta("datasource_path") == "postfix-sasl-bf.log"
results[0].Overflow.Alert.Events[0].GetMeta("datasource_type") == "file"
results[0].Overflow.Alert.Events[0].GetMeta("log_type") == "postfix"
results[0].Overflow.Alert.Events[0].GetMeta("log_type_enh") == "spam-attempt"
results[0].Overflow.Alert.Events[0].GetMeta("service") == "postfix"
results[0].Overflow.Alert.Events[0].GetMeta("source_hostname") == "unknown"
results[0].Overflow.Alert.Events[0].GetMeta("source_ip") == "1.2.3.4"
results[0].Overflow.Alert.GetScenario() == "Guezli/postfix-sasl-bf"
results[0].Overflow.Alert.Remediation == true
47 changes: 47 additions & 0 deletions scenarios/Guezli/postfix-sasl-bf.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
## Postfix SASL slow / distributed bruteforce

Detects slow or distributed SASL LOGIN bruteforce attempts against postfix.

The official `crowdsecurity/postfix-spam` scenario is tuned for fast spam waves
(`capacity: 5`, `leakspeed: 10s`). This scenario covers the opposite threat
model: distributed attackers each performing 1-2 SASL login attempts per hour
across many IPs in a /24, where the fast-pattern bucket leaks faster than it
fills.

Threshold: **3 SASL LOGIN failures from the same IP within ~2h** -> ban.

### Requirements

- `crowdsecurity/postfix-logs` parser (part of the `crowdsecurity/postfix`
collection)

### Acquisition example

For Mailcow's postfix container:

```yaml
source: docker
container_name:
- mailcowdockerized-postfix-mailcow-1
labels:
type: syslog
```

For a standalone postfix with file-based syslog:

```yaml
filenames:
- /var/log/mail.log
labels:
type: syslog
```

### Notes

- The `behavior` label is `pop3/imap:bruteforce` because the hub taxonomy has
no dedicated `smtp:bruteforce` entry. This follows the precedent set by
`hitech95/email-generic-bf`.
- Companion to `crowdsecurity/postfix-spam`, which catches fast-pattern spam
waves; this one fills the slow / distributed gap.
- Tuning notes, installer and detailed background:
https://github.com/Guezli/postfix-sasl-bf
39 changes: 39 additions & 0 deletions scenarios/Guezli/postfix-sasl-bf.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Detect slow / distributed SASL LOGIN bruteforce against postfix.
#
# The official `crowdsecurity/postfix-spam` scenario is tuned for fast
# spam waves (capacity 5 / leak 10s). It misses distributed bruteforces
# where many IPs each try only a couple of times per hour, because the
# bucket leaks faster than it fills.
#
# This scenario uses a much slower leak so a single IP that fails
# 3+ SASL LOGINs within ~2h triggers a ban.
#
# Requires the official `crowdsecurity/postfix-logs` parser, which
# already extracts source_ip from lines like:
# "warning: unknown[<IP>]: SASL LOGIN authentication failed: ..."

type: leaky
name: Guezli/postfix-sasl-bf
description: "Detect slow / distributed SASL bruteforce against postfix"
filter: |
evt.Meta.log_type == 'postfix'
&& evt.Parsed.message contains 'SASL'
&& evt.Parsed.message contains 'authentication failed'
groupby: evt.Meta.source_ip
# bucket holds 2 tokens, leaks 1 token every 2h
# => 3 fails within ~2h overflow the bucket
capacity: 2
leakspeed: 7200s
blackhole: 2h
labels:
service: postfix
remediation: true
confidence: 3
spoofable: 0
classification:
- attack.T1110
behavior: "pop3/imap:bruteforce"
label: "Postfix SASL Bruteforce"
references:
- https://www.postfix.org/SASL_README.html
- https://hub.crowdsec.net/author/crowdsecurity/collections/postfix