Blocking a whole country by IP addresses on a Linux server

With the Ukrainian war ongoing the risk of various attacks on our domestic IT infrastructure is high. In this blog post I will try to summarize methods on how to block the IP space of certain countries on different classical Linux systems. A warning beforehand: anyone can bypass IP ranges easily. This is (at best) useful for only a handful of very coarse attack scenarios by groups with low resources and motivation. My best hope is that this can be used to shield some more exposed services from the poor fucking infrantry, searching for easy prey.

At first will describe a very quick way of getting the job done via iptables (See “Just block it already”). This is intended as immediate damage control but is not persistent. I will also show you how to setup persistent rules using firewalld or ufw. Because those two methods scale very poorly, for firewalld the recommended way is to create and deploy a drop.xml. Raw iptables however might be the fastest way to deploy this and given how slow firewalld and ufw are, raw iptables are my recommendation for most setups.

We will establish a blocking filter in three steps: Obtaining a block list, activating it and check the system for the active rules. I have tested the procedure on openSUSE Leap 15.3, Ubuntu 20.04, CentOS 7 and Debian 10 Bullseye.

I expect a sharp rise of IT thread in the upcoming time because the ongoing war gives opportunity. Fuck demagogy and oppression, but not the Russian people. They are still cool peeps, trying to get over the rounds as everyone else.

This blog post acts as a quick start in case your IT defense mechanism needs to harden a system by e.g. blocking or block the whole IP space of the Bahamas, China, South Sudan, Madagascar, Russia or France.

A word of warning

Everybody can easily bypass ip range blocks, so this is only useful to mitigate very course attacks or scans. This is at best an additional protection but will definitely not safe your butt.

It might be helpful to block certain kind of scans or certain DOS attacks from script kiddies.

This will not block any real thread actor from doing nasty stuff to your systems

Build your defenses, setup your shields and be vigilant. And don’t trust a random dude from the internet for your IT security.


Just block it already! (iptables quickfix)

This is a quickfix and/or immediate damage control procedure for IPv4 and IPv6.

This is my recommended way for servers and that are maintained manually (e.g. manual reboots). The main downside is that the rules are not persistent, so you will need to re-apply them after each reboot, or after restarting firewalld. If that’s not problem for you, then use this method, as it is much faster than firewalld (see below). The same issue does not occur for ufw.

Godspeed!

IPv4

As an example, I have mirrored the Russian IPv4 iptables rules here.

IPv6

  • Go to https://www.countryipblocks.net/ipv6_acl.php
  • Download the CIDR list to a file blocklist.txt
  • Look at the produced list, check if nothing obvious is off
  • Run: for ip in `cat blocklist.txt`; do ip6tables -A INPUT -s $ip -j DROP; done

Also here, as an example, have the Russian IPv6 CIDR list.


Obtaining and activating the blocklist (firewalld and ufw)

Obtain the current country block lists from

Select the country you would like to block and use CIDR as Format.

See the section “List of mirrored blocklists” below in case the above stated link is not working.

WARNING: firewalld can take ages to boot with large lists

Adding large blocklists makes firewall-cmd --reload take a long time! On my test machine it took 20 minutes for the following example on each reboot. During the firewall setup, you can’t ssh into the host. So if the rule setup takes 20 minutes, you need to wait for firewalld to finish startup before you can even ssh into the machine.

This is the reason, why I recommend the “Just block it already” method for most servers, because it scales much better. ufw behaves much better, a reboot of the Ubuntu test system with the full IPv4 and IPv4 Russian test set performed reasonably well.

For fully automated servers that run automatic updates and reboots and can have a rather large latency after a reboot, the firewalld method should be fine though.

The process is single-threaded, so also on servers with many CPU cores this is gonna takes some time.

Predefined drop zone for firewalld

If you are using firewalld, you can use my predefined drop zone e.g. as xml file for Russia or for the Bahamas.

# Warning: This will overwrite all rules in your drop zone as well!
curl -o /etc/firewalld/zones/drop.xml ""

firewall-cmd --reload   # Will take some time! Up to 30 minutes

Reloading the firewall rules took up to 30 minutes on my test systems (openSUSE Leap and CentOS 7). My debian 10 bullseye crashes and I submitted a bug for that.

Check if the drop zone is active:

firewall-cmd --get-active-zones

If you see a large list of blocked IP addresses, you’re good to go.

Create your own drop.xml

Use my drop.py python script to convert a list of CIDR blocklists to a predefined drop.xml file. To create a file drop.xml from two CIDR blocklists block_ip4.txt and block_ip6.txt run:

./drop.py block_ip4.txt block_ip6.txt > drop.xml

The script works with an arbitrary number of blocklists and prints the resulting XML directly to standard output.

Activate blocklist using firewalld (openSUSE, CentOS, SUSE, RHEL)

This process takes multiple hours to complete. If somehow possible, use the predefined drop.xml as stated in the previous section.

Assuming your blocklist is named blocklist.txt, I will describe two ways of handling this.

The first and recommended one is to add the whole ip range to the preconfigred drop zone (see this), which simply drops all incoming packets without reply. I recommend this way because you have your “nasty-list” in one spot, which is better to keep the overview. To add all of our hosts of blocklist.txt to the drop zone do

for ip in `cat blocklist.txt`; do firewall-cmd --permanent --zone=drop --add-source=$ip; done
firewall-cmd --reload

You can process multiple files at once by just adding them into the cat ... section e.g. for Belarus IPv4 and Belarus IPv6 at the same time:

for ip in `cat belarus_ipv4.txt belarus_ipv6.txt`; do firewall-cmd --permanent --zone=drop --add-source=$ip; done

This method works for IPv4 and IPv6 blocklists. Don’t forget to block IPv6 as well! As stated before, this process takes multiple hours to finish.

If this isn’t applicable to you for some reason, you can fall back to the second method: adding rich-rules to the exposed zone (probably the public zone). If e.g. your webserver is running on the public zone, where you want to apply the filter on, you can add the blocklist there by running one of the following commands:

# IPv4
for ip in `cat blocklist.txt`; do firewall-cmd --permanent --zone=public --add-rich-rule="rule family='ipv4' source address='$ip' drop"; done
firewall-cmd --reload

# IPv6
for ip in `cat blocklist.txt`; do firewall-cmd --permanent --zone=public --add-rich-rule="rule family='ipv6' source address='$ip' drop"; done
firewall-cmd --reload

This is gonna take a while (up to hours).

Undo

for ip in `cat blocklist.txt`; do firewall-cmd --permanent --zone=drop --remove-source=$ip; done
firewall-cmd --reload

And for the alternative method (replace ipv4 with ipv6 if for non-legacy IP).

for ip in `cat blocklist.txt`; do firewall-cmd --permanent --zone=public --remove-rich-rule="rule family='ipv4' source address='$ip' drop"; done
firewall-cmd --reload

Activate blocklist using ufw (Ubuntu)

On Ubuntu servers ufw or uncomplicated firewall, is the default front-end for iptables. There the procedure is almost the same. This process is faster than firewalld, however it is still might take several hours to completion of the initial setup. Rebooting and re-activing the rules is no issue here.

Assuming the desired blocklist (See “Obtaining and activating the blocklist” section above) is named blocklist.txt we drop packets from every entry in the blocklist via

for ip in `cat blocklist.txt`; do ufw deny from $ip to any; done

This process will take some time (upt to several hours) to complete. Be patient.

Then check the rules via

ufw status

Undo

for ip in `cat blocklist.txt`; do ufw delete deny from $ip to any; done

List of mirrored blocklists

Please use fresh lists from https://www.countryipblocks.net is possible. Those lists might be outdated. However, given how slow country block are being moved and the probable impact of this measurement to block a state actor, those lists are probably just fine (for what they are worth)