Using WAF and GeoIP data to block specific countries

One of the features that many WAFs have, is blocking traffic using GeoIP data. First, why would you need to do something like this? Because, maybe your company does not do any business in that country for example, or you have been attacked by multiple IPs from a specific country/city and you want to temporarily block them. One other case might be that you want to block countries that have high e-commerce fraud rates (https://www.builderfly.com/what-countries-have-the-highest-fraud-cases-for-ecommerce/). There are also other use cases.
Beside your WAF (in my case: ModSecurity), you will need a database with country/city to subnet mapping. One of these is the MaxMind GeoIP database. Usually, this information costs money as it is used by many products but there is a free tier also: https://dev.maxmind.com/geoip/geolite2-free-geolocation-data?lang=en
This free tier gives you a database for countries and one for cities.

-rw-r--r--   1 root root  68M Nov 00 02:00 GeoLite2-City.mmdb
-rw-r--r--   1 root root 5.4M Nov 00 02:00 GeoLite2-Country.mmdb

You will also need to update it from time to time. Here is a tutorial on how to do it: https://dev.maxmind.com/geoip/updating-databases

For my ModSecurity WAF to be able to work with this database, I’ll need to make sure it was compiled with the right libraries. I had some problems with this and I opened an issue on ModSecurity Github, but I ended up fixing it myself, as I was having the wrong libraries.
Here is the link: https://github.com/SpiderLabs/ModSecurity/issues/2829
But the idea is that you need to have these libraries:

libmaxminddb-dev/focal,now 1.7.1-0+maxmind1
libmaxminddb0/focal,now 1.7.1-0+maxmind1

And ModSecurity needs to be compiled with them:

+ GeoIP/MaxMind ....found
* (MaxMind) v
/usr/lib/x86_64-linux-gnu//libmaxminddb.so, /usr/include/x86_64-linux-gnu/, -DWITH_MAXMIND -I/usr/include/x86_64-linux-gnu/

Now that we have the WAF and GeoIP database installed, we need to configure the WAF to get the info from the database. In the crs-setup.conf file, I’ve put the location of the database file and a rule to search for a mapping for a client IP address :

#GeoIP
SecGeoLookupDb /usr/share/GeoIP/GeoLite2-Country.mmdb
SecRule REMOTE_ADDR "@geoLookup" "id:15,phase:1,t:none,pass,nolog"

Next, I’ve created a policy in my custom policies file:

# block China,Hong Kong,Russia
SecRule GEO:COUNTRY_CODE "@pm CN HK RU" \
    "id:111,\
    phase:2,\
    t:none,\
    log,\
    logdata:'%{MATCHED_VAR}',\
    deny,\
    status:403,\
    msg:'Deny Client IP from %{MATCHED_VAR}',\
    ver:'OWASP_CRS/3.3.4'"

This policy will block and log any IP coming from locations like China, Hong Kong, and Russia.
This is how it looks in the logs:

21 10:59:25 server1 modsec_audit.log ModSecurity: Access denied with code 403 (phase 2). Matched "Operator `Pm' with parameter `CN HK RU' against variable `GEO:COUNTRY_CODE' (Value: `RU' ) [file "/usr/local/modsecurity-crs/rules/REQUEST-100-CUSTOM-RULES.conf"] [line "25"] [id "111"] [rev ""] [msg "Deny Client IP from RU"] [data "RU"] [severity "0"] [ver "OWASP_CRS/3.3.4"] [maturity "0"] [accuracy "0"] [hostname "10.156.0.11"] [uri "/"] [unique_id "166902116459.206475"] [ref "o0,2v0,2"]

And here is my monitoring dashboard:

Here you can see that the most blocked country, in my case, is Russia.

This is just a simple example, but you can create other types of policies too, where you don’t block the IP from a specific country, but increase the anomaly score for example.

Here are some useful links:
https://www.trustwave.com/en-us/resources/blogs/spiderlabs-blog/detecting-malice-with-modsecurity-geolocation-data/
https://malware.expert/tutorial/writing-modsecurity-rules/

About the author

Mihai is a Senior Network Engineer with more than 10 years of experience