Geoblocking when you can't Geoblock, (Tue, Mar 1st)

Given recent events, I’ve gotten a flood of calls from clients who want to start blocking egress traffic to specific countries, or block ingress traffic from specific countries (or both).  This seems like something the more “aware” organizations have tried quite a while back, and in many cases have tried it and given it up as not so effective.  But just this last week we’ve been seeing a flood of folks who are thinking about it as something they need to do NOW.  In many cases, depending on your hardware and licensing it’s as simple as a few tickboxes or lines in an ACL.  Even freely available firewalls such as pfSense do a good job of this, using MaxMind (look at pfBlockerNG for pfSense)

However, if your hardware doesn’t support using a feed or an API interface for a tool like MaxMind, what can you do?  The tricky part in geo-blocking is that it’s an ever-shifting landscape, your list of subnets that are “assigned” to any given country will change daily.  Also, saying “block Russia” is not terribly effective.  If you want to block any given country, you should consider that any target country will have a list of allies that might host attacks or “phone home” servers.  More importantly, if an attacker is any good, they simply won’t source any of their attacks from their own personal or corporate addresses, or any IP’s that are in their country.  Really you can host most attacks for pennies on most cloud platforms.

All that being said, we still need to deal with these requests from Sr Managment to “block Russia”.  Understand going in that you likely won’t be able to convince them it’s a bad idea.  So to save time, let’s script this so you can get it off your list quick!  We’ll do this in Windows / PowerShell since that’s a bit more accessible than Linux and/or Python – – sorry, I didn’t mean to bring religion into this 🙂  , but you can run the PowerShell script in your Linux desktop too if you want.

With everything discussed, let’s say you’re going to proceed with blocking country X.  MaxMind still has free lists of subnets-per-country that you can download as CSVs (their GeoLite2 list).  The files are dated so you can easily tell how fresh your data is – in this example I’m working with GeoLite2-Country-CSV_20220215.csv

The file header is:


Each line in the file looks like:,2077456,2077456,,0,0


How do we tell which line is which country?  Look at the companion file GeoLite2-Country-Locations-en.csv –  For instance, we can get Russia’s ID from this:

type GeoLite2-Country-Locations-en.csv | findstr “RU”

Going back to the first file, the subnet mask is in binary (bitmask format) – for instance “/16”. For an ACL you’ll likely want to work that back to decimal values such as (dotted-netmask representation) or (dotted-wildcard representation), depending on your platform.
Let’s look at a bitmask of /17 and convert it to a netmask format (in PowerShell):

$MaskLength = 17

[ipaddress] $mask = ([Math]::Pow(2, $MaskLength) – 1) * [Math]::Pow(2, (32 – $MaskLength))


Address            : 131071

AddressFamily      : InterNetwork

ScopeId            :

IsIPv6Multicast    : False

IsIPv6LinkLocal    : False

IsIPv6SiteLocal    : False

IsIPv6Teredo       : False

IsIPv4MappedToIPv6 : False

IPAddressToString  :

you can see that our answer is in $mask.IpAddressToString

If you need the inverse (wildcard representation) for deploying to an IOS device, take your $mask and invert it:

$wildcard = [ipaddress] (-bnot([uint32] $mask.address))



Address            : 4279173120

AddressFamily      : InterNetwork

ScopeId            :

IsIPv6Multicast    : False

IsIPv6LinkLocal    : False

IsIPv6SiteLocal    : False

IsIPv6Teredo       : False

IsIPv4MappedToIPv6 : False

IPAddressToString  :

$wildcard.ipaddresstostring gives you a wildcard of “”

Let’s process the GeoLite2 database, looking for country ID of 2017370.  First, let’s import the file and look at one entry in the resulting list:

$GL2 = Import-Csv .GeoLite2-Country-Blocks-IPv4.csv


network                        :

geoname_id                     : 2077456

registered_country_geoname_id  : 2077456

represented_country_geoname_id :

is_anonymous_proxy             : 0

is_satellite_provider          : 0

Let’s pull our target entries:

$target = “2017370”

$target_list = $gl2 | where {($_.geoname_id -eq $target) -or ($_.registered_country_geoname_id -eq $target) -or ($_.represented_country_geoname_id -eq $target)}

This gives us enough to create our final script:

$GL2 = Import-Csv .GeoLite2-Country-Blocks-IPv4.csv

# target country is Russian Federation

$target = “2017370”

$target_list = $gl2 | where {($_.geoname_id -eq $target) -or ($_.registered_country_geoname_id -eq $target) -or ($_.represented_country_geoname_id -eq $target)}

# start by declaring the two lists

$aclinbound = @()

$acloutbound = @()

# start the list by deleting the existing list so we can start over with current values

$aclinbound += “no access-list ingressfilter-geo”

$acloutbound += “no access-list egressfilter-geo”

# compute the line items

foreach ($t in $target_list) {

    # get the network and bitmask

    $n = ($“/”)[0]

    $MaskLength = ($“/”)[1]

    # compute the netmask:

    $mask = [ipaddress]  $mask = ([Math]::Pow(2, $MaskBits) – 1) * [Math]::Pow(2, (32 – $MaskBits))

    # create the ACL entry – inbound

    $aclinbound += “access-list ingressfilter-geo extended deny ip “+$n+” “+$mask.IPAddressToString+” any”

    # ditto for the Egress filter list

    $acloutbound += “access-list egresssfilter-geo extended deny ip any “+$n+” “+$mask.IPAddressToString


# Output the ACLs to text files

$aclinbound | out-file “./ingressfilter.txt”

$acloutbound | out-file “./egressfilter.txt”

Taking a quick look, those are pretty hefty ACLs.  You can certainly apply this on most reasonable gear, but it’s going to make your config files a bit unweildy, and while it will run fine, it’ll certainly affect your memory and cpu.  Especially given the caveats we discussed earlier – this isn’t going to be terribly effective!

#subtract one to account for the header line

$aclinbound.length -1



Where to go from here?  You can cut/paste the ACLs as-is into your ASA, then apply it to the appropriate inbound/outbound interface(s).  To streamline it, you could easily script the download using MaxMind’s API (, and while you’re at it you could update to the more accurate GeoIP2 list.

At the other end, you can apply the ACL using common automation tools like (among other tools) Solarwinds’ CATTOOLs, in PowerShell using Posh-SSH or in Python using netmiko or paramiko.  EXPECT is another tried-and-true option.  Frameworks like Ansible, SALT, Puppet, Chef or Terraform can allow you to expand your automation to more complex functions – these will also tend to protect your firewall credentials better than a plain text script.

If you’re looking for a more useful way to build your egress list, we discussed this all the way back in 2014:
In short, letting your internal workstations and people trust all hosts and protocols on the internet is a really bad idea!  Trust what you need to, then wrap up your egress filter with a deny any/any/log line.

If you’re looking for lists of malicious hosts to build a block list, that’s more easy to come by
ISC Block list:
Tor Project Exit nodes:

More on working with IP addresses in powershell:

If you’ve got an active / regularly updated “block list” that you use to protect your infrastructure, please share in our comment form!

Rob VandenBrink

(c) SANS Internet Storm Center. Creative Commons Attribution-Noncommercial 3.0 United States License.

Reposted from SANS. View original.