2

I have set up rate-limiting to stop brute-force attacks on my ssh server. I am using the following iptables rules:

iptables -A INPUT -p tcp -m tcp --dport 22 -m hashlimit --hashlimit-upto 4/min
--hashlimit-burst 6 --hashlimit-mode srcip --hashlimit-name ssh
--hashlimit-htable-expire 60000 -m state --state NEW -j ACCEPT

iptables -A INPUT -m tcp -p tcp --dport 22 -m state --state NEW -j REJECT

When I open one SSH connection (e.g. via PuTTY), and then, a minute later, try to open another one (for example, to transfer a file), the second connection is sometimes rejected with no response from the server. If I manage to open a second SSH connection and then try to open a third connection, it becomes even harder (high probability of no response).

I have verified that when I disable the above rules, everything works fine. Increasing --hashlimit-upto and/or --hashlimit-burst helps, but doesn't resolve the problem completely -- it only decreases the likelihood of it happening. Rejection still happens sometimes, whereas if I disable the iptables rule, it never happens.

What is going on? The above iptables rules say that TCP packets related to new connections should be limited to 4 per minute. So I should easily be able to open up to 4 connections per minute.

1 Answer 1

7

(Answering my own question to leave a record for others.)

Your first rule:

iptables -A INPUT -p tcp -m tcp --dport 22 -m hashlimit --hashlimit-upto 4/min
--hashlimit-burst 6 --hashlimit-mode srcip --hashlimit-name ssh 
--hashlimit-htable-expire 60000 -m state --state NEW -j ACCEPT

is not doing what you think it's doing. When you say -m tcp, -m hashlimit and -m state, you invoke three iptables modules. You think the hashlimit will apply only to packets which satisfy all the other conditions in the rule. But that's not what's happening. Instead, modules are executed in the order they are given in the rule. (This fact is not intuitively obvious and not explicitly documented anywhere, to my knowledge.)

In the above rule, -m hashlimit comes before -m state. That means every TCP packet (not just a NEW packet) will first be sent to the hashlimit module. Hashlimit will count this packet and if it is within the 4/min limit, it will be passed on to -m state. Only now does the state module get to look at the packet and check if it represents a new connection.

So what happens in your case is that you open your first SSH connection, which transmits some packets from time to time. These packets are not "NEW" packets, so they are not dropped (the --state NEW condition in your second rule is not met). But they are processed by hashlimit and will count against your 4/min limit. If they come fast enough, they will use up the quota and if you then try to open another connection, the first packet for that connection will hit against the quota and your first rule will not match.

The obvious solution is to change the order:

iptables -A INPUT -p tcp -m tcp --dport 22 -m state --state NEW -m hashlimit
--hashlimit-upto 4/min --hashlimit-burst 6 --hashlimit-mode srcip --hashlimit-name ssh 
--hashlimit-htable-expire 60000 -j ACCEPT

In general, I think a good rule of thumb is to always place -m hashlimit last in an iptables rule.

1
  • 2
    In general, the better rule of thumb is to put -m conntrack --ctstate ESTABLISHED -j ACCEPT in the first place. This has the additional benefit that packets from existing connections traverse fewer rules, thus speeding up processing and taking less CPU.
    – iBug
    Mar 7, 2022 at 13:40

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .