Using pf tables to drop packets from entire IP blocks

or, the skiddies will have to get crafty

Every so often, I read through my httpd access logs to see what people are up to. Most of the time it's normal users but occasionally it's automated scanners looking for vulnerabilities. I run something like :

$ cat /var/www/logs/access.log | grep -v 200\ 0 | sort | less

I then page through the file, looking at the types of things the scanners are trying to hit. Sometimes I run a whois on the IP addresses to see where the spam is coming from. The sources, in my case, are constantly, always, and almost exclusively IP addreses in Ukraine, Russia, China, and Hong Kong.

I've had enough, it's time to start blocking spam so that normal users can access my website without waiting for httpd to finish responding to some guy submitting hundreds of requests per second for resources that don't even exist. Sometimes the spam is so bad that even I see error pages from httpd when the server is overloaded. When I try to log in to see what's happening, there is severe ssh latency. Blocking entire countries doesn't solve the hacker issue (wow, state sponsored hackers know about tor!?!?!). The goal is to reduce the total amount of bullshit requests per second, thus reducing overall load and improving the load times for legitimate users.

I found a few tools that help admins find IP blocks by country. My server is listening on IPv4 and IPv6 so I decided to make a single megalist with both types of addresses. This handy site will help you generate a list of IPv4 blocks by country, And this is their IPv6 counterpart. I just grabbed the apache format and used vim to remove the parts I don't need. The table file is a newline delimited a of ip addresses/ranges

The easiest way to create a table using pf is to make a file with a list of ip addresses or ranges. Taking the generated lists from the links above, I made a new file called /etc/statesponsoredhackers.table. It looks something like:

0.0.0.0/24
0.0.0.0/16
0.0.0.0/8
0000:0000::/24

# and so on and so forth for another 60 thousand lines

In order to use this table in pf, we need to add a few rules to /etc/pf.conf. I added something like this in the section where I to tables.

# tables  
table <whitelist> persist file "/etc/whitelist.table"
pass in all from <whitelist> to any

table <statesponsoredhackers> persist file "/etc/statesponsoredhackers.table"
block in quick from <statesponsoredhackers> to any

Refreshing the ruleset and checking that the rules actually installed:

$ pfctl -e /etc/pf.conf
$ pfctl -t statesponsoredhackers -T show

# a list of the 60 thousand ip addresses now prints

OpenBSD's pf uses a "drop" rather than "return" block policy by default. This means my server won't even response to pings from any of the blocked IP addresses.

The last bit of advice: put the block in quick section before the rest of your firewall rules. The quick keyword stops processing additional rules as soon as there is a match. Obviously, you would need to put the whitelist section above any of the quick sections so that it's not ignored.