Last active
September 21, 2023 07:34
-
-
Save valeriansaliou/9632580178e83da1dabce1bbe2cb6a76 to your computer and use it in GitHub Desktop.
HTTP/HTTPS DOS shield w/ IPTables
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Those rules protect HTTP/HTTPS services for both IPv4 and IPv6 sources as such: | |
# 1. Prevent a /32 IPv4 or /64 IPv6 to open more than 10 HTTPS?/TCP connections per second (the limit is high, but this still shield against some attacks) — DROP TCP packets in this case, to avoid generating egress traffic sending a RST | |
# 2. Limit ingress bandwidth to HTTPS? services to 32KB/sec (adjust to your needs, in my case it is used to shield a WebSocket backend against incoming WebSocket message floods) | |
# 3. Limit the number of simultaneous ongoing connections to HTTPS? to 40 (also, high limit, adjust to your needs) | |
# The protections those rules offer: | |
# 1. Prevent crypto-DOS (ie. a client that proceed too many key exchanges and thus exhaust server CPU) | |
# 2. Prevent WebSocket floodings (eg. I use this for Socket.IO, which has no efficient way to rate-limit received messages before they get parsed) | |
# 3. Prevent ephemeral TCP port exhaustion due to a client holding too many TCP connections | |
# 4. Prevent IPv6 rotation attacks where a client that possesses a /64 IPv6 block (most hold a /5x or /6x block) uses different ephemeral IPv6 on this /64 subnet to bypass any /128 rate-limiter. It is always safer to rate-limit on the allocated block, hence the /64 (for IPv4 the issue doesn't exist, as IPv4 are expensive and thus people are allocated /32 blocks in 99% cases). | |
# IPTables (IPv4) | |
:FIREWALL | |
-A INPUT -i eth0 -p tcp -m tcp -m multiport --dports 80,443 -m state --state NEW -m hashlimit --hashlimit-above 10/sec --hashlimit-burst 20 --hashlimit-mode srcip --hashlimit-name http_rate -j DROP | |
-A INPUT -i eth0 -p tcp -m tcp -m multiport --dports 80,443 -m hashlimit --hashlimit-above 32kb/s --hashlimit-mode srcip --hashlimit-name http_bandwidth -j DROP | |
-A INPUT -i eth0 -p tcp -m tcp -m multiport --dports 80,443 -m connlimit --connlimit-above 40 --connlimit-mask 32 --connlimit-saddr -j REJECT --reject-with tcp-reset | |
# IPTables (IPv6) | |
:FIREWALL | |
-A INPUT -i eth0 -p tcp -m tcp -m multiport --dports 80,443 -m state --state NEW -m hashlimit --hashlimit-above 10/sec --hashlimit-burst 20 --hashlimit-mode srcip --hashlimit-name http_rate --hashlimit-srcmask 64 -j DROP | |
-A INPUT -i eth0 -p tcp -m tcp -m multiport --dports 80,443 -m hashlimit --hashlimit-above 32kb/s --hashlimit-mode srcip --hashlimit-name http_bandwidth --hashlimit-srcmask 64 -j DROP | |
-A INPUT -i eth0 -p tcp -m tcp -m multiport --dports 80,443 -m connlimit --connlimit-above 40 --connlimit-mask 64 --connlimit-saddr -j REJECT --reject-with tcp-reset |
Thanks for the feedback. I'll make the change today.
@tszynalski done, fixed. Does it look good to your now?
Looks like -m hashlimit
comes after -m state
now, so it should be OK.
May I ask why did you not limit the bucket size?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Valerian,
The rule:
-A INPUT -i eth0 -p tcp -m tcp -m multiport --dports 80,443 -m hashlimit --hashlimit-above 10/sec --hashlimit-burst 20 --hashlimit-mode srcip --hashlimit-name http_rate -m state --state NEW -j DROP
is not doing what you think it's doing. Iptables modules are executed in the order they are given in the rule. Because in the above rule,-m hashlimit
comes before-m state
, hashlimit will process every TCP packet, not just new packets, and every TCP packet will count towards the 10/sec limit. At the beginning, there is no difference, because all packets are for NEW connections, but later on, if you have an open connection that keeps sending packets, those packets will keep depleting the hashlimit counters. So you may not be able to open a new TCP connection, even though the hashlimit rate says you should be able to.Instead, you want to first check if a packet is TCP, then check if it's for a NEW connection, and only then send it to hashlimit to be counted.
This is a really common mistake. You can see the wrong order in many places on the Web and I used it myself for years, wondering why my second or third SSH connection would sometimes fail to open. Then I looked at the internals of hashlimit (
cat /proc/net/ipt_hashlimit
) and realized my counters were being decremented all the time by my active SSH connection, which was of course sending some data now and again. Took me a long time to figure out what was going on. To my knowledge, this behavior is not explicitly documented anywhere, and I haven't been able to find any discussions about it. So I'm posting corrections in a few places on the Web to save people some frustration.I think a good rule of thumb is to always put
-m hashlimit
last in your iptables rule.