Nice. I use something similar on Mikrotik; perhaps there are ideas here to broaden your approach.
It uses a more probabilistic detection, and doesn’t otherwise interfere with later packet processing and whether the port is allowed or blocked. Thus, it covers exposed ports which must be allowed but repeated connections are still unwanted: ssh password guessing (regardless of whether that has any chance of success, the attackers don’t know). The advantage is I don’t have to pick specific ports that are otherwise blocked.
It uses several address-lists as a series of sinbin buckets:
- an allow list that is not checked further
- a couple of intermediate lists
- a sinbin list, which is blocked
Each connection, the source is checked against the address lists. If it’s on one of the intermediate lists, it’s added to the next-higher one (or the first, if it’s not on any of them), until it gets added to the sinbin list.
As tunables, each list has an expiry time, and each promotion happens on a percentage probability. I give ssh higher probability.
In routeros syntax, with some added tabulation spaces to make the rule progression clearer:
Near the top of the main input chain, a sin check:
3 chain=input action=jump jump-target=sincheck src-address-list=!whitelist
Then the sincheck chain:
0 chain=sincheck action=drop src-address-list=sinbin 1 chain=sincheck action=add-src-to-address-list connection-state=new protocol=tcp src-address-list=sin_2 address-list=sinbin address-list-timeout=4h dst-port=22 random=80 log=yes log-prefix="sinbinned" 2 chain=sincheck action=add-src-to-address-list connection-state=new src-address-list=sin_2 address-list=sinbin address-list-timeout=4h random=40 log=yes log-prefix="sinbinned" 3 chain=sincheck action=add-src-to-address-list connection-state=new protocol=tcp src-address-list=sin_1 address-list=sin_2 address-list-timeout=1m dst-port=22 random=80 4 chain=sincheck action=add-src-to-address-list connection-state=new src-address-list=sin_1 address-list=sin_2 address-list-timeout=1m random=30 5 chain=sincheck action=add-src-to-address-list connection-state=new protocol=tcp address-list=sin_1 address-list-timeout=1m dst-port=22 random=80 6 chain=sincheck action=add-src-to-address-list connection-state=new address-list=sin_1 address-list-timeout=1m random=20
Aside: It’s been a while (over a decade) since I set this up, and looking at it now, there are some changes I should get around to:
- rename “whitelist”
- consider the tarpit action once they’re in the sinbin
- use the built-in
psdproperty for statistical port-scanning detection, removing the non-special-cased ports like 22.
We are blocking different things, from what I understand from your explanations
I use fail2ban for banning people “abusing” the open services, and I proactively ban people scanning the server for common services that I’m not running.
You seem to ban people depending on their lack of “fair use” of your services?
Yes and no. I didn’t see mention of fail2ban in the article, so I was only addressing the proactive port-scanning detection anyway. For that, I’m just sampling all ports rather than a matching list of unused ones, as well as using ssh password-guessing as a good indicator of bad intent.
And honestly, the primary benefit is just that it shuts up further log spam because sinbin drops are silent.
For the particular device in this example, there are few other services accessible outside of vpn. If adapting these kinds of ideas to something with busier services, they need to be excluded from the detection ladder. Perhaps at that point it matters less which way you go, as you’re matching a list of ports anyway, whether to exclude or include in detection.
The psd module uses a decaying score, and points are added each time a connection is made on a port that’s different to the previous, so a busy web service wouldn’t get counted more than once. I don’t think it existed when I created this setup.