How to protect against DDoS with UFW

How to protect against DDoS with UFW

Ever need to protect your server using UFW from DDoS attacks?

What is a DDoS attack?

Simply put, a DDoS, or Distributed Denial oService, is when many devices from many separate IP addresses send large amounts of traffic to your server in an attempt to take it down. These are slightly different from DoS attacks, in which only one device is attacking. DoS attacks can be mitigated rather easily because of the fact that they’re only coming from one IP address. But, how do you defend against DDoS attacks?

The answer? iptables

The majority of tutorials on DDoS protection give you a bunch of iptables rules. You’re probably wondering, why not just use those rules along with UFW. While you technically can,  UFW rules generally run before iptables rules, so if a port is open with UFW, then no other iptables rule will affect that traffic. I will be using JavaPipe’s rules. They work amazing(I’ve tested them with a small DoS attack, and my system practically didn’t even notice), but don’t work if UFW is installed.

Enter before.rules

More or less, the only iptables rules that affect UFW are what’s in /etc/ufw/before*.rules. These rules are added before the standard UFW rules(generated with the UFW command). So, add the following to /etc/ufw/before.rules and /etc/ufw/before6.rules:

#ANTI DDOS
*mangle
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A PREROUTING -m conntrack --ctstate INVALID -j DROP
-A PREROUTING -p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN -m conntrack --ctstate NEW -j DROP
-A PREROUTING -p tcp -m conntrack --ctstate NEW -m tcpmss ! --mss 536:65535 -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG NONE -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN FIN,SYN -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags SYN,RST SYN,RST -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,RST FIN,RST -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,ACK FIN -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags ACK,URG URG -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,ACK FIN -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags PSH,ACK PSH -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,SYN,RST,PSH,ACK,URG -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG NONE -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,PSH,URG -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,SYN,PSH,URG -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,SYN,RST,ACK,URG -j DROP
-A PREROUTING -m conntrack --ctstate INVALID -j DROP
-A PREROUTING -p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN -m conntrack --ctstate NEW -j DROP
-A PREROUTING -p tcp -m conntrack --ctstate NEW -m tcpmss ! --mss 536:65535 -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG NONE -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN FIN,SYN -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags SYN,RST SYN,RST -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,RST FIN,RST -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,ACK FIN -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags ACK,URG URG -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,ACK FIN -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags PSH,ACK PSH -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,SYN,RST,PSH,ACK,URG -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG NONE -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,PSH,URG -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,SYN,PSH,URG -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,SYN,RST,ACK,URG -j DROP
-A PREROUTING -p tcp -m state --state NEW -m recent --set --name DEFAULT --rsource
-A PREROUTING -p tcp -m state --state NEW -m recent --update --seconds 10 --hitcount 25 --name DEFAULT --rsource -j DROP
-A PREROUTING -p icmp -m limit --limit 2/sec -j ACCEPT
-A PREROUTING -p icmp -j DROP
COMMIT
# Completed on Sun Jan 14 21:47:45 2018
# Generated by ip6tables-save v1.6.0 on Sun Jan 14 21:47:45 2018
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -p tcp -m connlimit --connlimit-above 25 --connlimit-mask 128 --connlimit-saddr -j REJECT --reject-with tcp-reset
-A INPUT -p tcp -m tcp --tcp-flags RST RST -j DROP
-A INPUT -p tcp -m conntrack --ctstate NEW -j DROP
COMMIT

In case you’re wondering, these are essentially an iptables-save dump from a server after I added all the relevant rules.

A bit more…

Now just run

 sudo ufw reload

and the settings should take affect. Not everything on JavaPipe is on here, because I use a VPN, so the internal IPs must work, so I didn’t use the anti-false IP rules. I also left ICMP unblocked, because I use ping every now and then to see if my server has an SSH problem, or a network problem. Also, they recommend you add the following to /etc/sysctl.conf:

kernel.printk = 4 4 1 7 
kernel.panic = 10 
kernel.sysrq = 0 
kernel.shmmax = 4294967296 
kernel.shmall = 4194304 
kernel.core_uses_pid = 1 
kernel.msgmnb = 65536 
kernel.msgmax = 65536 
vm.dirty_ratio = 80 
vm.dirty_background_ratio = 5 
fs.file-max = 2097152 
net.core.netdev_max_backlog = 262144 
net.core.rmem_default = 31457280 
net.core.rmem_max = 67108864 
net.core.wmem_default = 31457280 
net.core.wmem_max = 67108864 
net.core.somaxconn = 65535 
net.core.optmem_max = 25165824 
net.ipv4.neigh.default.gc_thresh1 = 4096 
net.ipv4.neigh.default.gc_thresh2 = 8192 
net.ipv4.neigh.default.gc_thresh3 = 16384 
net.ipv4.neigh.default.gc_interval = 5 
net.ipv4.neigh.default.gc_stale_time = 120 
net.netfilter.nf_conntrack_max = 10000000 
net.netfilter.nf_conntrack_tcp_loose = 0 
net.netfilter.nf_conntrack_tcp_timeout_established = 1800 
net.netfilter.nf_conntrack_tcp_timeout_close = 10 
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 10 
net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 20 
net.netfilter.nf_conntrack_tcp_timeout_last_ack = 20 
net.netfilter.nf_conntrack_tcp_timeout_syn_recv = 20 
net.netfilter.nf_conntrack_tcp_timeout_syn_sent = 20 
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 10 
net.ipv4.tcp_slow_start_after_idle = 0 
net.ipv4.ip_local_port_range = 1024 65000 
net.ipv4.ip_no_pmtu_disc = 1 
net.ipv4.route.flush = 1 
net.ipv4.route.max_size = 8048576 
net.ipv4.icmp_echo_ignore_broadcasts = 1 
net.ipv4.icmp_ignore_bogus_error_responses = 1 
net.ipv4.tcp_congestion_control = htcp 
net.ipv4.tcp_mem = 65536 131072 262144 
net.ipv4.udp_mem = 65536 131072 262144 
net.ipv4.tcp_rmem = 4096 87380 33554432 
net.ipv4.udp_rmem_min = 16384 
net.ipv4.tcp_wmem = 4096 87380 33554432 
net.ipv4.udp_wmem_min = 16384 
net.ipv4.tcp_max_tw_buckets = 1440000 
net.ipv4.tcp_tw_recycle = 0 
net.ipv4.tcp_tw_reuse = 1 
net.ipv4.tcp_max_orphans = 400000 
net.ipv4.tcp_window_scaling = 1 
net.ipv4.tcp_rfc1337 = 1 
net.ipv4.tcp_syncookies = 1 
net.ipv4.tcp_synack_retries = 1 
net.ipv4.tcp_syn_retries = 2 
net.ipv4.tcp_max_syn_backlog = 16384 
net.ipv4.tcp_timestamps = 1 
net.ipv4.tcp_sack = 1 
net.ipv4.tcp_fack = 1 
net.ipv4.tcp_ecn = 2 
net.ipv4.tcp_fin_timeout = 10 
net.ipv4.tcp_keepalive_time = 600 
net.ipv4.tcp_keepalive_intvl = 60 
net.ipv4.tcp_keepalive_probes = 10 
net.ipv4.tcp_no_metrics_save = 1 
net.ipv4.conf.all.accept_redirects = 0 
net.ipv4.conf.all.send_redirects = 0 
net.ipv4.conf.all.accept_source_route = 0 
net.ipv4.conf.all.rp_filter = 1

I just put what I use, which is like three lines less than the original. To be honest, I have no idea what most of them do(I understand many of the networking ones), but from my testing with ab on a more powerful server, these rules are effective. Now reboot, or because you’re on Linux 🙂 , just run

sudo sysctl -a

Additional tips

If you’re using UFW, there are a few more steps you can take to prevent your server from going down. You’re probably familiar with

ufw allow 22/tcp

This allows connections to TCP port 22, which is most commonly used for SSH. For extra protection, however, use “limit” like so:

ufw limit 22/tcp

This will overwrite “allow” rules if they exist for the same port. What this does is rate limit that port to 6 new connection per ip per 30 seconds. This works on many other services as well. However, remember that sometimes receiving more than 6 connections in 30 seconds is normal. For example, refreshing the page, or clicking on a link more than once within 30 seconds isn’t exactly rare. Rate limiting on TCP 80 and/or 443 could result in browsers not being able to access your website. To resolve this, simply don’t use rate limiting on those ports, or allow certain IP addresses(i.e. Cloudflare’s) unmetered access(i.e. ufw insert 1 allow from IP_ADDR to any port 443), and limit everyone else.

ATTRIBUTION:

99% of this post is from the amazing JavaPipe blog, I just converted the rules into iptables-restore compatible format to work with UFW and ip6tables. Some UFW examples are from DigitalOcean. I also got some UFW help from cyberciti.

Note: Some of the rules can’t be put in before.rules or else they would just let all ports through, even ones blocked with UFW. The rules in this post (should) respect UFW port blocking.

Leave any other tips in the comments!!!

Leave a Reply(Markdown is On)

%d bloggers like this: