Hi all,
while reviewing my mail logs (NS8, mail + crowdsec on the same node) I found about 170,000 failed SMTP AUTH attempts in seven weeks that CrowdSec never reacted to. Nothing got in, but I think the pattern is worth sharing, because it looks deliberately built to stay below the default thresholds.
What I see
- ~156,000 of the failures came from a single /22 (37 addresses of one /24 were active), continuously from the end of July to mid September.
- Each address tries one login every ~10 minutes, 24 hours a day, with a different user name each time (
info,admin,sales, first names…). Per address that is only ~6 attempts per hour – all together still ~220 per hour. - The same would work with addresses from completely different networks (botnet); in my case they happened to sit in one /24.
Why CrowdSec stays silent
SMTP AUTH is checked by dovecot, so the only scenario that sees these failures is crowdsecurity/dovecot-spam: capacity 3, leakspeed 360s, grouped by source IP. One attempt every 10 minutes leaks away faster (one per 6 minutes) than it fills the bucket – it can never overflow, no matter how many weeks it runs. The postfix scenarios do not look at SASL LOGIN authentication failed at all. For SSH the hub has ssh-slow-bf for exactly this case; for mail there is nothing comparable.
Idea: a slow scenario for mail, per IP and per /24
I tested these two on a replay of synthetic logs (CrowdSec 1.7.8, -dsn file://… -no-api) and they now run on my node as local scenarios:
type: leaky
name: local/dovecot-slow-bf
description: "Detect slow dovecot/SASL password guessing from one IP"
filter: "evt.Meta.log_type == 'dovecot_logs' && evt.Meta.dovecot_login_result == 'auth_failed'"
groupby: evt.Meta.source_ip
capacity: 10
leakspeed: 30m
blackhole: 1h
labels:
service: mail
type: bruteforce
remediation: true
type: leaky
name: local/dovecot-slow-bf-range
description: "Detect distributed slow dovecot/SASL password guessing from one /24"
filter: "evt.Meta.log_type == 'dovecot_logs' && evt.Meta.dovecot_login_result == 'auth_failed'"
groupby: IpToRange(evt.Meta.source_ip, "/24")
distinct: evt.Meta.source_ip + evt.Meta.dovecot_user
capacity: 30
leakspeed: 5m
blackhole: 1h
scope:
type: Range
expression: IpToRange(evt.Meta.source_ip, "/24")
labels:
service: mail
type: bruteforce
remediation: true
Replay results: one address trying every 10 minutes is caught after 2 h 20 min (15 events); 37 addresses of one /24 at the same pace are caught as a range after 8 minutes; a real user who mistypes a password twice, three times a day, never triggers either of them. I keep the range scenario in simulation mode for now, because a /24 ban can hit a provider.
Files go to state/crowdsec_config/scenarios/ of the crowdsec instance (bind-mounted to /etc/crowdsec), then restart the crowdsec service; cscli simulation enable <name> for a dry run.
Two related observations about the ban time
- With dynamic ban time enabled, the value from the UI is in minutes and grows linearly:
(number of earlier bans + 1) * 4minutes with the default of 4. A first offender is back after 4–8 minutes; my most persistent address needed 57 bans to reach about 4 hours. Would a doubling per repeat (4 min, 8, 16, … with a cap) be an option for the module?profiles.yaml.localis regenerated at every service start, so this cannot be changed locally in a durable way. - For one address that is also on the community blocklist, CrowdSec logged a local ban (
216m ban on Ip …) several times, but the attempts continued every 4 minutes through the ban windows, and the firewall bouncer logged no “decision added” at those moments. After a restart of the node the address was blocked. I have not found the cause yet – has anyone seen local decisions not reaching the bouncer when a CAPI decision for the same IP exists?
Does this match what others see in their logs? If the slow scenarios look reasonable, maybe they – or something better – could ship with the crowdsec module for nodes that run mail.
Thanks!