Seems like failtoban would've dealt with this ... DoS and log storage filler better

Thank you very much, I really appreciate it. I’m here for tests.

Fully agree, this isn’t applicable in LAN.

That’s true for this case but guessing the domain could be easy:

[root@node state]# telnet 192.168.3.141 25
Trying 192.168.3.141...
Connected to 192.168.3.141.
Escape character is '^]'.
220-node.ns8rockytest2.com ESMTP Postfix

The domain could be ns8rockytest2.com

If we lowered the attempts to 2 and raised the timeframe to 2 days instead of 1 minute, this case would have been banned after 2 days.
I think we need to check for alerts instead of bans to ban it much faster.

hard each attempt is done after 19m

Following 10 alerts are inside a timeframe of 5 minutes.
If we don’t look for IPs but for range 81.30.107.0/24 we would have banned it after the first 10 tries.

Each attempts of a unique IP but if we check the range, it should work

we need the matches in the logs for

2025-06-30T01:40:22-07:00 [1:crowdsec1:crowdsec1] time="2025-06-30T08:40:22Z" level=info msg="Ip 81.30.107.67 performed 'crowdsecurity/postfix-non-smtp-command' (1 events over 0s) at 2025-06-30 08:40:22.032615581 +0000 UTC"
2025-06-30T01:40:22-07:00 [1:crowdsec1:crowdsec1] time="2025-06-30T08:40:22Z" level=info msg="(localhost/crowdsec) crowdsecurity/postfix-non-smtp-command by ip 81.30.107.67 (IR/215930) : 8m ban on Ip 81.30.107.67"
2025-07-01T19:40:23-07:00 [1:crowdsec1:crowdsec1] time="2025-07-02T02:40:23Z" level=info msg="Ip 81.30.107.177 performed 'crowdsecurity/postfix-non-smtp-command' (1 events over 0s) at 2025-07-02 02:40:23.282585287 +0000 UTC"
2025-07-01T19:40:24-07:00 [1:crowdsec1:crowdsec1] time="2025-07-02T02:40:24Z" level=info msg="(localhost/crowdsec) crowdsecurity/postfix-non-smtp-command by ip 81.30.107.177 (IR/215930) : 4m ban on Ip 81.30.107.177"
1 Like

root@r3-pve:/# cscli explain --file log --type postfix/smtpd --verbose

line: 2025-07-01T20:34:43-07:00 [1:mail1:postfix/smtpd] warning: unknown[81.30.107.136]: SASL LOGIN authentication failed: (reason unavailable), sasl_username=cao
	├ s00-raw
	|	├ 🔴 crowdsecurity/cri-logs
	|	├ 🔴 crowdsecurity/docker-logs
	|	├ 🔴 crowdsecurity/syslog-logs
	|	└ 🟢 crowdsecurity/non-syslog (+5 ~8)
	|		└ update evt.ExpectMode : %!s(int=0) -> 1
	|		└ update evt.Stage :  -> s01-parse
	|		└ update evt.Line.Raw :  -> 2025-07-01T20:34:43-07:00 [1:mail1:postfix/smtpd] warning: unknown[81.30.107.136]: SASL LOGIN authentication failed: (reason unavailable), sasl_username=cao
	|		└ update evt.Line.Src :  -> /log
	|		└ update evt.Line.Time : 0001-01-01 00:00:00 +0000 UTC -> 2025-07-03 12:44:15.465071454 +0000 UTC
	|		└ create evt.Line.Labels.type : postfix/smtpd
	|		└ update evt.Line.Process : %!s(bool=false) -> true
	|		└ update evt.Line.Module :  -> file
	|		└ create evt.Parsed.message : 2025-07-01T20:34:43-07:00 [1:mail1:postfix/smtpd] warning: unknown[81.30.107.136]: SASL LOGIN authentication failed: (reason unavailable), sasl_username=cao
	|		└ create evt.Parsed.program : postfix/smtpd
	|		└ update evt.Time : 0001-01-01 00:00:00 +0000 UTC -> 2025-07-03 12:44:15.465207154 +0000 UTC
	|		└ create evt.Meta.datasource_path : /log
	|		└ create evt.Meta.datasource_type : file
	├ s01-parse
	|	├ 🔴 crowdsecurity/apache2-logs
	|	├ 🔴 crowdsecurity/dovecot-logs
	|	├ 🔴 crowdsecurity/mariadb-logs
	|	├ 🔴 crowdsecurity/nextcloud-logs
	|	├ 🔴 crowdsecurity/nginx-logs
	|	├ 🔴 crowdsecurity/nginx-proxy-manager-logs
	|	├ 🔴 crowdsecurity/pgsql-logs
	|	├ 🟢 crowdsecurity/postfix-logs (+8 ~1)
	|		├ update evt.Stage : s01-parse -> s02-enrich
	|		├ create evt.Parsed.message_failure :  (reason unavailable), sasl_username=cao
	|		├ create evt.Parsed.remote_addr : 81.30.107.136
	|		├ create evt.Parsed.remote_host : unknown
	|		├ create evt.Meta.service : postfix
	|		├ create evt.Meta.source_hostname : unknown
	|		├ create evt.Meta.source_ip : 81.30.107.136
	|		├ create evt.Meta.log_type : postfix
	|		├ create evt.Meta.log_type_enh : spam-attempt
	|	├ 🔴 crowdsecurity/postscreen-logs
	|	├ 🔴 proftpd-logs
	|	├ 🔴 crowdsecurity/sshd-logs
	|	├ 🔴 crowdsecurity/traefik-logs
	|	└ 🔴 vsftpd-logs
	├ s02-enrich
	|	├ 🔴 crowdsecurity/dateparse-enrich
	|	├ 🟢 crowdsecurity/geoip-enrich (+13)
	|		├ create evt.Enriched.IsInEU : false
	|		├ create evt.Enriched.IsoCode : IR
	|		├ create evt.Enriched.Latitude : 35.698000
	|		├ create evt.Enriched.Longitude : 51.411500
	|		├ create evt.Enriched.SourceRange : 81.30.107.0/24
	|		├ create evt.Enriched.ASNNumber : 215930
	|		├ create evt.Enriched.ASNOrg : Cipher Operations Doo Beograd - Novi Beograd
	|		├ create evt.Enriched.ASNumber : 215930
	|		├ create evt.Meta.ASNNumber : 215930
	|		├ create evt.Meta.ASNOrg : Cipher Operations Doo Beograd - Novi Beograd
	|		├ create evt.Meta.IsInEU : false
	|		├ create evt.Meta.IsoCode : IR
	|		├ create evt.Meta.SourceRange : 81.30.107.0/24
	|	├ 🔴 crowdsecurity/http-logs
	|	├ 🔴 nethserver/nethvoice-whitelist-http-probing
	|	├ 🔴 crowdsecurity/nextcloud-whitelist
	|	└ 🟢 crowdsecurity/whitelists (unchanged)
	├-------- parser success 🟢
	├ Scenarios
		└ 🟢 crowdsecurity/postfix-spam

so the scenario that should ban this is postfix-spam

but why your crowdsec instance has not triggered the ban

Is it possible to log the alerts to make it work?

Maybe we could change the datasources to check range “/24” instead of full IP?

I think it just means that this specific log entry is recognized as postfix-spam but if the attempts are not in time, there’s no ban.

I need to test something

vim /etc/crowdsec/scenarios/distributed-postfix-bruteforce.yaml

type: leaky
name: custom/distributed-postfix-bruteforce
description: "Detect distributed brute-force attacks against Postfix SASL authentication (multiple IPs in the same /24 subnet)"
filter: |
  evt.Meta.log_type_enh == 'spam-attempt'
leakspeed: 10m
capacity: 10
groupby: evt.Enriched.SourceRange
blackhole: 5m
labels:
  service: postfix
  remediation: true
  confidence: 2
  spoofable: 1
  behavior: "smtp:distributed-bruteforce"
  label: "Postfix Distributed Bruteforce"

verify it is enabled

root@r3-pve:/# cscli scenarios list
root@r3-pve:/# cscli scenarios install custom/distributed-postfix-bruteforce

Explanation
filter: Matches your SASL failed attempts.
groupby: Uses evt.Enriched.SourceRange (which, thanks to geoip-enrich, should be the /24 subnet of the source IP).
capacity: 10 (ban after 10 failed attempts in 1 minute from the same subnet).
leakspeed: 10m (window of 10 minute).
blackhole: 5m (no re-processing for 5 minutes after a ban).
remediation: true (enables ban).
spoofable: 1 (distributed attacks are more likely to be spoofed, so confidence is a bit lower).
1 Like

That looks really promising.

It’s triggered but didn’t ban…but I think I’m missing some configuration

	├-------- parser success 🟢
	├ Scenarios
		├ 🟢 crowdsecurity/postfix-spam
		└ 🟢 custom/distributed-postfix-bruteforce

does the scenario is enabled, how did you test ?

Yes, it’s enabled:

custom/distributed-postfix-bruteforce             🏠  enabled,local           /etc/crowdsec/scenarios/distributed-postfix-bruteforce.yaml     

I test using curl from different machines in LAN (LAN ban is enabled)

curl -k smtps://test:wrongpass@192.168.3.141

what is the log line ???

maybe the parser cannot catch this one ?

This is the log line:

2025-07-03T16:08:08+02:00 [1:mail1:postfix/smtpd] warning: ns8rockytest.com[192.168.3.144]: SASL PLAIN authentication failed: (reason unavailable), sasl_username=test

The pattern should match:

'warning: %{POSTFIX_HOSTNAME:remote_host}\[%{IP:remote_addr}\]: SASL ((?i)LOGIN|PLAIN|(?:CRAM|DIGEST)-MD5) authentication failed:%{GREEDYDATA:message_failure}'

1 Like

you have enabled the ban on the LAN, you have produced 10 attempts from 10 different IP in less than 10 minutes ?

No, I used 5 machines and produced 2 attempts per machine. I prepared the command on CLI to be fast. The IPs need all to be different?

I also lowered the capacity to 3 and it didn’t work.

1 Like

03 16:19:24 r3-pve.rocky9-pve3.org rspamd[78579]: (rspamd_proxy) ; milter; rspamd_milter_process_command: got connection from 192.168.12.1:36164
Jul 03 16:19:24 r3-pve.rocky9-pve3.org rspamd[78579]: (rspamd_proxy) ; proxy; proxy_milter_finish_handler: finished milter connection
Jul 03 16:19:24 r3-pve.rocky9-pve3.org crowdsec1[79297]: time=“2025-07-03T14:19:24Z” level=info msg=“Ip 2 sources performed ‘custom/distributed-postfix-bruteforce’ (3 events over 3.749934569s) at 2025-07-03 14:19:24.515934211 +0000 UTC”
Jul 03 16:19:24 r3-pve.rocky9-pve3.org systemd[77398]: Starting Mark boot as successful…
Jul 03 16:19:24 r3-pve.rocky9-pve3.org systemd[77398]: Finished Mark boot as successful.
Jul 03 16:19:24 r3-pve.rocky9-pve3.org crowdsec1[79297]: time=“2025-07-03T14:19:24Z” level=info msg=“(localhost/crowdsec) custom/distributed-postfix-bruteforce by ip 192.168.12.15 : 4m ban on Ip 192.168.12.15”
Jul 03 16:19:24 r3-pve.rocky9-pve3.org crowdsec1[79297]: time=“2025-07-03T14:19:24Z” level=info msg=“(localhost/crowdsec) custom/distributed-postfix-bruteforce by ip 192.168.12.1 : 4m ban on Ip 192.168.12.1”

1 Like

I lowered capacity to 2, I am lazzy

1 Like

Great, maybe I missed some config…
It works, that’s the most important.

basically
lowered capacity to 2
enabled ban on the lan
trying from different 2 IP on the LAN

1 Like

Thanks for your great work :+1:

I can confirm that it works with capacity 2:

2025-07-03T15:59:30+02:00 [1:crowdsec1:crowdsec1] time="2025-07-03T13:59:30Z" level=info msg="Ip 5 sources performed 'custom/distributed-postfix-bruteforce' (12 events over 1m11.999757317s) at 2025-07-03 13:59:30.456053973 +0000 UTC"
2025-07-03T15:59:30+02:00 [1:crowdsec1:crowdsec1] time="2025-07-03T13:59:30Z" level=info msg="(localhost/crowdsec) custom/distributed-postfix-bruteforce by ip 192.168.3.144 : 12m ban on Ip 192.168.3.144"
2025-07-03T15:59:30+02:00 [1:crowdsec1:crowdsec1] time="2025-07-03T13:59:30Z" level=info msg="(localhost/crowdsec) custom/distributed-postfix-bruteforce by ip 192.168.3.133 : 4m ban on Ip 192.168.3.133"
2025-07-03T15:59:30+02:00 [1:crowdsec1:crowdsec1] time="2025-07-03T13:59:30Z" level=info msg="(localhost/crowdsec) custom/distributed-postfix-bruteforce by ip 192.168.3.46 : 8m ban on Ip 192.168.3.46"
2025-07-03T15:59:30+02:00 [1:crowdsec1:crowdsec1] time="2025-07-03T13:59:30Z" level=info msg="(localhost/crowdsec) custom/distributed-postfix-bruteforce by ip 192.168.3.41 : 12m ban on Ip 192.168.3.41"
2025-07-03T15:59:30+02:00 [1:crowdsec1:crowdsec1] time="2025-07-03T13:59:30Z" level=info msg="(localhost/crowdsec) custom/distributed-postfix-bruteforce by ip 192.168.3.40 : 12m ban on Ip 192.168.3.40"

But it doesn’t ban the network, just the IPs.

I’m going to test it from public…

1 Like

really not a fan to ban the network, I am not sure I want to go there :smiley:

1 Like