A couple of years ago I would have been shocked with this simple idea. To ban an entire country from ever using the service of one of my public hosted server. I would have never proposed or even agreed to an idea like this. This was a longtime ago and now the landscape of the Internet has changed so much that I’ve been resolved to use this simple, yet so effective, solution. I mean, this goes against the basic nature of the existence of the Internet; Information wants to be free and it should be available to anyone who wants to access it, anywhere on this world. And yet, here I am today, banning whole countries forever reaching the services of web and email services on some of the servers that I managed. Why? Because I’m tired of some organizations abusing servers. I am tired of constantly checking the status of the networks I must look after. I am tired of seeing that almost 99% of time, the attacks are always coming from the same countries. So after many years of consideration, in the autumn of 2013, I finally gave in and I’ve started banning entire countries with iptables. After many months of using it, the only conclusion that I have is, why I haven’t done that before?
The technical requirement and configuration are really easy, we will go trough it very soon, but before doing so, let’s take a step back to reflect on why we would like to do it and what are we going to achieve with that kind of setup. I’m managing and monitoring a lot of different kind of servers. Most of them are private and a few of them are public. They all have some different kind of security layers, but the public ones need some special care and more careful monitoring. Obvious reason; these are servers offering services to everyone on the internet with no special security access to reach it. Mostly web pages, emails, FTP and database access. Sometimes they have special services, but they are usually the exception, not the norm. Of course, some of the basic stuff for protecting the services are already configured, using keys for SSH login, password complexity; for making sure the users have good password (note to self ; make sure not to use the same password for my luggage and for the shield of planet Druidia), fail2ban and/or WAF to ban the IPs for the external attack, OSSEC for the internal attack, etc. But even with all of these different layers of protection, some problem might still arise. Like an old version of a WordPress being hosted and never updated or a plugin of whatever web platform that has a big security hole and was never patched. External/public servers are a very different kind of servers because for the most simple reason, security. You have to know exactly what you are getting into when you setup a public server. Why is that? Because there is always someone, somewhere, trying to break into the server and take control of it. The reasons for it are not that important, it might be for launching a DDOS attack, making it a FTP/Torrent server for Warez, hosting banners for pushing viruses or some new kind of phishing/spamming/scheming or simply just for the fun of it. 14 years ago it was pretty easy to setup a public server because there was rarely an attack on it. Now, today, it’s happening every second, if not 10 times per second. Brute force password are being constantly done against all public servers on the Internet. You might have changed the default port of your SSH service from 22 to whatever else, you are only avoiding the script kiddies and not the professional ones – who are the most dangerous/problematic. And even tough you are using many different kind of layers for protecting your server, you are truly never safe against some specific attack like DDOS, unless you have the money for buying an extra powerful firewall or are using the specialized service like Black Lotus.
Where am I getting at? It’s now officially a pain in the ass / full time job to monitor a public server for security. And you know what? Most of theses attacks are, mostly, always coming from a specific country. By simply using fail2ban and getting the emails reports when a brute force password is done, you can know exactly from which country the IP that has done it is coming from. Take this page for example. One of the old version of fail2ban had in it’s default configuration file, as an example, for reporting emails to fail2ban@mail.com. The original author probably thought that this didn’t existed, but alas, yes it does exist! Somebody had the extraordinary idea of registering that email address and he has been recording the emails that are sent to it for statistics analysis. On that web page you can check what countries are launching the most attack for brute force password and what countries are the most targeted by it. Another service you might want to check is www.blocklist.de – a fail2ban reporting service. A lot of fail2ban users have registered an account with this service and are sending them the information of whoever (IP) are currently attacking them. You can download their master list and add it to your firewall.
But after all of this being said, if you take the time to think about it, it’s a constant-never ending wheel of security. I’ve started to think about this idea of banning an entire country more than 3 years ago. I knew it could be “easily” done because of the simple feature that we can use today, geo IP location. All IPs are given in specific and, most of the time, contiguous block to countries and it’s registered to numerous database, available for free, on the web. So, I started thinking, who among my users are located in other countries? If the servers I’m looking for are all located in North America, I can easily check that all the users on it are located in America also. If that server has only less than 100 hundred users, it can be easily check with the logs of the different services. Of course, I also had to warn the users of the servers of my intention. Like banning one specific country because 97% of all the brute force attacks that were done against all the services on that server were all coming from that country. So after explaining that to all the users, I can also offer them some exception, if they where ever traveling to that country, they could send me an email from an email account not belonging to my server, like using their gmail or hotmail account and giving me the public address of where they could be located so that I can push an exception in the firewall. Thankfully, this kind of scenario has never happened so far.
So what do we need? a firewall for sure. Iptables in this case and Ipset with that. Why? Because of the way iptables works. When a connection is made to a linux server with iptables running on it, you have to know, that iptables take the IP and compare it to its internal list. line by line. Now imagine that you have an iptables list made up of thousand of lines. Do you think it will slow down the connection process? You bet. If your list has many entries, like in the range of hundreds of different IPs and some of them are block of IPs, using only Iptables is not efficient. That’s why you need Ipset. I highly recommend that you learn how Ipset work if you intend to ban an entire country. Ipset need iptables to work and is rarely installed by default on a Linux server. For this example, I’m using CentOS 6.5 and for installing ipset it’s a simple matter of typing :
# yum -y install ipset
There you go, it’s done. Since ipset is in the base repo, I don’t even need to connect to a third party repo to have it installed. libmnl should be installed automatically with that for dependency. For other distro the command may be different but the idea is the same. Now for the next part, if you have ever done a search on Google (or any other web search engine) about the term “iptables ban country” you will quickly realize that there are many ways on how to do it. Most of them all are good. The trick is to know where you get the “master list” for the IPs belonging to a specific country. Many websites offer that information but not all of them are up to date. There are 2 different main way to do this. First one is to connect to a service with some tools on Linux for getting a “live” list of Geo IP or the second way is to download a list from a webpage and putting in a text file on your server. You’ll quickly think that the first way should be better because the query is done on another server and you’ll always have an up to date list. And yet, this is where you will probably have the most problem. Most of these external servers are providing these service free of charge and are not always online, meaning that it will stop your iptables/ipset from working. Also, a lot of these server don’t even keep their list of Geo IP up to date. I prefer the second way. You get the list of Geo IP from a webpage, then you simply copy that list in a text file on your server. The drawback is that you need to keep that list updated by yourself (or you can even create a script with cron that will do this for you). Either way, this tutorial is trying to make it simple and I’ll use the second way. Suck it up! So one of the most popular website for Geo IP databse used to be the ipdeny.com website but it seems that they haven’t updated in a while. Other database offers some restricted free access to the list but most of them will charge you a subscription fee – this usually include the right for making a live query about the Geo IP connecting. You get what you pay for. For a free access, you can get the list of the countries that you want from either ip2location, Maxmind Geolite Free Database or Country IP Blocks. The one that I prefer, it’s free and up to date is the one from Simple Penguin (damn the site now seems to be down, I now recommend ipdeny.com). This list is optimized to be the shortest possible in CIDR format. Click on the country you want to ban and download the flat text file to your linux server. Now, you have ipset and a list of the country you want to ban. The next question you should ask before dropping the hammer of banning, do you want to ban that country from connecting to any ports on your server – like all the ports. Or do you want to ban a country for connecting to a specific port – like port 22 for the sshd service?
Before running the loop for creating the ban, let’s create a set in IPSet, we’ll call it “geoblock”, the name can be whatever you want, this set will be populated with the IP list of the countries you want to ban.
sudo ipset create geoblock hash:net,port
Here’s the loop to run for banning a list of countries from reaching the service of SSHD, if you want to ban another service, simply change the default port of 22 to the one you want, On the first line, between the {} input the country code you wish to ban with a , between each of them – I also recommend that you save this loop in a shell script file :
for IP in $(wget -O – http://www.ipdeny.com/ipblocks/data/countries/{cn,ru,kr,pk,tw,sg,hk}.zone)
do
# regular ban - block port 22 for countryXX
sudo ipset add geoblock $IP,22
done
If you want to use your hammer of banning for ever reaching any of the services on your server, we simply change the loop code for this one instead ;
for IP in $(wget -O – http://www.ipdeny.com/ipblocks/data/countries/{cn,ru,kr,pk,tw,sg,hk}.zone)
do
# ban everything - block countryX
sudo ipset add geoblock $IP
done
Now let’s see if your set has been populated correctly, simply run this command to show the content of the set called geoblock :
# sudo ipset list geoblock
You should get a list of IP addresses in the CIDR format. You can compare it from the original list where you got it to make sure that everything is alright. If you want to delete the complete set, simply use the following command ;
# sudo ipset del geoblock|"setname"
For all the details on ipset, check the manual of the command. Now that the set is created just like you want, we need to add this to the rules of IPtables. You can do it by running this command :
# sudo iptables -I INPUT -m set --set geoblock src -j DROP
If you restart iptables, this rule will not be persistent, don’t forget to run the command of “# service iptables save” in order to make it permanent. There is also another useful trick, it’s to make a reverse list. let’s say that you do not want to block specific countries but rather you would only like to allow only some countries from your list to communicate with your server and ban the rest? It’s rather simple and clever. You need to edit the loop script of creating your IPset, but instead of choosing the countries you want to ban, you will only choose the countries you want to allow and create your list from that. Afterward, you will use this command for iptables to drop all incoming connection from IPs that are not listed in your set of IPSet ;
# sudo iptables -A INPUT -m set --set !geoblock src -j DROP
Again, don’t forget to save your iptables rules if you want to make it permanent. That’s about it, you now have the power to block entire countries from communicating with your servers.
i’m not able to make it work on my debian 9 server
Hi,
your blog helps a lot. I had the same experience, I am tired of the permenanent attacks from specific countries. Although managing just a small personal but public site, there are many attacks per seconds, just flooding the logs. This creates lots of effort to secure this simple presentation.
Just a note: As you have published some blogs in French, I suppose you are from France. I am from Germany. My French is a bit rusty, so I do better to write in English, but let me tell you: Help building a common Europe! I am strongly convinced, that this is the only way to form a peaceful and wealthy zone.
Let us form the future together! I personally feel first as a European, than second as German. Maybe this is a bit too emotional, but all the bullshit nationalisms ruins our future, it just helps Trump damage to Europe.
Have a good time and all the best from Germany
JP
I too am getting: ipset v7.1: Syntax error: Second element is missing from 27.144.0.0/16. (etc)
hi
thanks for this 🙂
i’m getting this from (i think) every ip trying to be added from ipdeny.com non-agregated lists
ipset v6.30: Syntax error: Second element is missing from 223.118.0.0/15.
i can’t find anything to help me resolve this and wondered if you had any ideas?
As pointed out above, the version with the port number in the table just fails to match. That comment says it needs to be “src,dst”, which seems right. The total block without that does work, once the long dash is replaced by a single hyphen, and -match-set replaces the older -set.
For those struggling with the “!” version, consider going to two lines, the first of which accepts everything that matches for the ports desired, the second of which denies all else. Of course these would need to be in the proper place relative to other rules.
I can’t use the “reverse mode”, when I put the command
sudo iptables -A INPUT -m set –set !geoblock src -j DROP
it shows an error
-bash: !geoblock: event not found
also tried
sudo iptables -A INPUT -m set –set “!geoblock” src -j DROP
sudo iptables -A INPUT -m set –match-set “!geoblock” src -j DROP
and no success
I just realized that if you do a reverse block using a name like !587allow for the ipset hash name, it’s going to fail since !587 will call a command from your history. In my case it gave:
iptables -A INPUT -m set –match-set “!587allow” src -j DROP
iptables -A INPUT -m set –match-set “lsallow” src -j DROP
iptables v1.4.21: Set lsallow doesn’t exist.
I will choose a alpha hashname instead.
woops I missed the create part, I thought that was just an example of the command syntax. Problem solved, and thanks for helping me block my postfix submission port to half the world!
I think there is a copy-paste issue for me? I fixed the hyphen after wget -O but still I get :
–2018-02-21 13:09:44– http://www.ipdeny.com/ipblocks/data/countries/pk.zone
Resolving http://www.ipdeny.com (www.ipdeny.com)… 192.241.240.22
Connecting to http://www.ipdeny.com (www.ipdeny.com)|192.241.240.22|:80… connected.
2018-02-21 13:09:44 (792 MB/s) – written to stdout [6120/6120]
ipset v6.29: The set with the given name does not exist
ipset v6.29: The set with the given name does not exist
ipset v6.29: The set with the given name does not exist
ipset v6.29: The set with the given name does not exist
I just left the name as geoblock, like in the example.
Thanks
I really wish this would work for me. I get :
2018-02-21 01:58:25 (11.6 MB/s) – written to stdout [35090/35090]
FINISHED –2018-02-21 01:58:25–
Total wall clock time: 0.3s
Downloaded: 7 files, 336K in 0.1s (2.35 MB/s)
ipset v6.29: The set with the given name does not exist
ipset v6.29: The set with the given name does not exist
ipset v6.29: The set with the given name does not exist
ipset v6.29: The set with the given name does not exist
ipset v6.29: The set with the given name does not exist
Hey Mike.
First of all, you are right about http/s, it will not be affected by the other protocol that you are filtering.
I’ve done reverse IP banning for smtp and ssh on production server for about a hundred users only and I can say that we never notice any slowdow for the connections.
Having said that your setup will be affected by :
The power of your VPS.
The number of network connections your VPS is handling per second.
The quality of your network backbone.
If it’s an important production server, I would highly suggestions that you test it on a server in a lab and that you measure up the difference of the time it take for the connection to make the handshacke.
Also to speed up the process, make sure to use a very good service/list that is updated often.
Make sure that your list use IP blocks instead of individuals IP. This is the best thing you can do in this kind of setup.
Again test it before configuring that on a production server.
@DGHost, I just noticed that in your response to @Kamy you are saying that 6K IPs are too much, but when I look at IP ranges at ipdeny the list of IP ranges for China is 8K lines, for Canada is 7.6K and for USA is 51K! Does this mean that this ipset approach is only viable for banning small countries? As per my previous comment, my objective is to block email spam from countries other than Canada and USA…
Hi DGHost, thanks for this post. Lately, our business is getting bombarded by Chinese spam emails, so I am considering banning SMTP, SSH, FTP and SFTP ports for all countries except Canada and USA – our customers are only in these two countries. If I implement your IPSet solution for this, how much will it slow down the server? I assume it will not slow down HTTP/HTTPS, only the protocols listed above… correct? Our web server is running on VPS Cloud with Ubuntu 14 on Plesk Onyx.
Thanks,
Mike
@Kamy ; 6K IP’s are really too much, having thousands of IP’s take a lot of memory in your system. Instead try to make the list shorter. Are these individuals IP? If yes, you could ;
– try to use subnet block or ip range instead
– make reverse list maybe?
If you intend to use that many IP’s I highly suggest that you have a deep understanding of how ipset work.
Hi, first of all thank you very much for this fantastic post.
I did implement this and it works great with small number of IPs, but when I add around 6K IPs, SSH login gets really slow and it takes around 2 minutes to login. Is there anything I can do to identify where the problem is ?
Thank you in advance,
Regards,
Kamy
@Kevin : your ip deny list seems to be very big. Have you thought about doing a reverse list instead to make it shorter? You do get the error msg that the hash is full, cannot add more elements to it.
@Dan : You should get a result with the command of “ipset list geoblock” If not your process of creating the list didn’t worked and I would go back in the steps for making sure it’s good.
Been unable to determine if the list of countries loaded or not into my system using your command. Did not saw an error in my script run.
“let’s see if your set has been populated correctly, simply run this command to show the content of the set called geoblock :
sudo ipset list geoblock ”
You should get a list of IP addresses in the CIDR format.
My IP tables has the rule:
target prot opt source destination
DROP all — anywhere anywhere match-set geoblock src
How can I verify that the countries IP’s are loaded correctly?
I tried:
$ sudo ipset create geoblock hash:ip netmask 30 hashsize 64000
I tried:
$ ipset destroy geoblock
$ sudo ipset create geoblock hash:ip netmask 30
$ ./ipset.conf
Error below
ipset v6.19: Hash is full, cannot add more elements
Thanks for posting this script. I’m getting an error
command line > sudo ipset create geoblock hash:net,port
file name = ipset.conf
#!/bin/bash
# Add ips to the ip tables to block
# https://www.dghost.com/techno/internet/banning-an-entire-country-with-iptablesipset
for IP in $(wget -O – http://www.ipdeny.com/ipblocks/data/countries/{ad,ae,af,ag,ai,al,am,ao,ap,ar,as,at,au,aw,az,ba,bb,bd,be,bf,bg,bh,bi,bj,bl,bm,bn,bo,bq,br,bs,bt,bw,by,bz,ca,cd,cf,cg,ch,ci,ck,cl,cm,cn,co,cr,cu,cv,cw,cy,cz,de,dj,dk,dm,do,dz,ec,ee,eg,er,es,et,eu,fi,fj,fm,fo,fr,ga,gb,gd,ge,gf,gg,gh,gi,gl,gm,gn,gp,gq,gr,gt,gu,gw,gy,hk,hn,hr,ht,hu,id,ie,il,im,in,io,iq,ir,is,it,je,jm,jo,jp,ke,kg,kh,ki,km,kn,kp,kr,kw,ky,kz,la,lb,lc,li,lk,lr,ls,lt,lu,lv,ly,ma,mc,md,me,mf,mg,mh,mk,ml,mm,mn,mo,mp,mq,mr,ms,mt,mu,mv,mw,mx,my,mz,na,nc,ne,nf,ng,ni,nl,no,np,nr,nu,nz,om,pa,pe,pf,pg,ph,pk,pl,pm,pr,ps,pt,pw,py,qa,re,ro,rs,ru,rw,sa,sb,sc,sd,se,sg,si,sk,sl,sm,sn,so,sr,ss,st,sv,sx,sy,sz,tc,td,tg,th,tj,tk,tl,tm,tn,to,tr,tt,tv,tw,tz,ua,ug,uy,uz,va,vc,ve,vg,vi,vn,vu,wf,ws,ye,yt,za,zm,zw}.zone)
do
# ban everything – block countryX
sudo ipset add -exist geoblock $IP
done
command line > ./ipset.conf
error > ipset v6.19: Syntax error: Second element is missing from 167.249.192.0/22.
ipset v6.19: Syntax error: Second element is missing from 167.249.196.0/22.
Thanks for this, but I did battle a little bit.
Two things:
1. The second hyphen in “for IP in $(wget -O – ” is not a hyphen character, but it should be. This causes cut/paste issues.
2. # sudo iptables -I INPUT -m set –set geoblock src -j DROP
should be
# sudo iptables -I INPUT -m set –set geoblock src,dst -j DROP
The iptables-extensions man page:
Hence the command
iptables -A FORWARD -m set –match-set test src,dst
will match packets, for which (if the set type is ipportmap) the source address and destination port pair can be found in the specified set.
@Eldon ; Sorry I’m not able to reproduce the problem with the copy/paste of the wget command. But yes, it could be a problem with the formatting of WordPress/browser/text editor when copying/pasting the code. I’ve seen that in the past.
@dave : On your line, make sure that there is 2 — in front of –set !geoblock. I can only see one currently. Again, it could be related to a formatting option with copying/pasting.
is there an alternative to the reverse list:
“sudo iptables -A INPUT -m set –set !geoblock src -j DROP”
this did not work with iptables v1.4.21
and thank you for this article!
Came across this article, which is a good how-to by the way, and noticed that wordpress might be causing an issue with your wget command or perhaps it was a bad copy/paste.
in your code it looks like there is an em or en dash instead of a regular hyphen, this would cause anyone doing a copy and paste to copy the wrong character and result in the rules being written to a file name “–”. This does indeed break the script as you were instead wanting the output of wget to be dumped to STDOUT and stored into the variable IP.
Hey thanks for the ideas. I don’t know where or why the filename “-” was used, it appears to be a default of some sort. Will try manually and see if I can get some tractions. Thx!
You could try to save the file with a different filename instead of the “-“, also try to manually type the script instead of doing a copy/paste, sometimes you get weird characters inserted into the bash script.
I really like this idea, and I can add ip addresses to sets individually, and my shell script loops execute, but the “ipset add geoblock $IP” seems to fail. There is nothing in the ipset, and there is a file in the directory where the command was executed called “-” with all the ip addresses in it.
the output of the shell script has blocks like this:
–2016-10-11 22:45:05– http://www.ipdeny.com/ipblocks/data/countries/ru.zone
Reusing existing connection to http://www.ipdeny.com:80.
HTTP request sent, awaiting response… 200 OK
Length: 119531 (117K)
Saving to: ‘–’
I’m not that conversant in bash scripting specifics. There seems to be something wrong with the ipset add statement… ideas?
Frank K.
https://wiki.archlinux.org/index.php/Ipset
Making ipset persistent :
ipset you have created is stored in memory and will be gone after reboot. To make the ipset persistent you have to do the followings:
First save the ipset to /etc/ipset.conf:
# ipset save > /etc/ipset.conf
Then enable ipset.service,
I saved the iptables modifikation with “sudo iptables-restore /etc/network/iptable”. How can i save geoblock? At start von “sudo ipset geoblock” (after reboot) comes the error “The set with the given name does not exist” What can I do?
Did you saved your iptables modification? The rules are not permanent until you saved it.
After reboot the “geoblock” is missing and I must start again the script “ipset_create_geoblock.sh”. After that all works good.
The original URL are still working for me. Can you load the following URL in your browser and download it with wget? http://www.ipdeny.com/ipblocks/data/countries/hk.zone
Still the new aggregated list should also work. I’ve tested it just now and everything works fine.
I would suggest that you destroy your ipset with the command ; “# ipset destroy “setname”, then recreate it with ; # ipset create geoblock hash:net
Hello sir,
I’m trying to use your script but it seems the URL in the script has changed a bit. I’m also getting an error “ipset cannot parse file” while doing some testing by just downloading the zone file to the machine.
here is what i have modified from your original script to populate the geoblock.
ipsetfirewall.sh
#!/bin/bash
for IP in $(wget -O – http://www.ipdeny.com/ipblocks/data/aggregated/{cn-aggregated,ru-aggregated,kr-aggregated,us-aggregated,pk-aggregated,tw-aggregated,sg-aggregated,hk-aggreagated}.zone)
do
# ban everything – block countryX
sudo ipset add geoblock $IP
done