Blocking connections to nefarious IP addresses in your firewall is usually a good idea. On OpenBSD, the pf-badhost script updates PF to block traffic to and from such addresses. This post walks through my installation and verification of pf-badhost. Make sure to check out the well-written installation instructions, too.

Configure

Download the pf-badhost.sh script.

ftp https://www.geoghegan.ca/scripts/pf-badhost.sh

Install the script with the appropriate permissions.

install -g bin -m 644 -o root pf-badhost.sh /usr/local/bin/ (1)
1 The script will be owned by root, belong to the bin group, and be readable by everyone and writeable by the owner.

Create a new user, _pfbadhost, who will not be allowed to login.

useradd -s /sbin/nologin _pfbadhost

Install an empty pf-badhost.txt file owned by the _pfbadhost user which is readable and writeable by the owner only.

install -m 600 -o _pfbadhost /dev/null /etc/pf-badhost.txt

Give the necessary permissions to the _pfbadhost user.

/etc/doas.conf
permit nopass _pfbadhost cmd pfctl args -nf /etc/pf.conf (1)
permit nopass _pfbadhost cmd pfctl args -t pfbadhost -T replace -f /etc/pf-badhost.txt (2)
1 Allow _pfbadhost to reload the PF configuration file without a password.
2 Allow _pfbadhost to update the pfbadhost PF table from the file /etc/pf-badhost.txt.

Edit the crontab file for _pfbadhost.

crontab -u _pfbadhost -e

Add a rule to run the pf-badhost.sh script every morning at 6:45.

/var/cron/tabs/_pfbadhost
# use /bin/sh to run commands, no matter what /etc/passwd says
SHELL=/bin/sh

# Update pf-badhost at 6:45 every morning.
45	6	*	*	*	/usr/local/bin/pf-badhost.sh

Run the pf-badhost.sh script.

doas -u _pfbadhost sh /usr/local/bin/pf-badhost.sh

Configure PF to block all traffic to or from the blacklisted addresses.

/etc/pf.conf
# pf-badhost configuration
table <pfbadhost> persist file “/etc/pf-badhost.txt” (1)
block in quick on egress from <pfbadhost> (2)
block out quick on egress to <pfbadhost> (3)
1 Populate the pfbadhost table from the file /etc/pf-badhost.txt and keep the table even if no rules refer to it.
2 Block any traffic coming in to the gateway from any address in the pfbadhost table.
3 Block any traffic coming out of the gateway to any address in the pfbadhost table.
Be careful not to block your internal network’s traffic here.

Reload the PF ruleset.

pfctl -f /etc/pf.conf

Following the original tutorial, run the pf-badhost.sh script once more for good measure.

doas -u _pfbadhost sh /usr/local/bin/pf-badhost.sh

Verify

Now…​ did that actually do anything? I usually ask myself this question, and I like to be sure sometimes, especially when it comes to network security.

Outbound

First, test the outbound traffic destined for a blocked IP address. For the outbound test, you could use any publicly available IP address which is not blacklisted. I use the address of one of Google’s DNS servers, 8.8.8.8 in this test.

You could just ping a blacklisted server with and without the pfbadhost rules in place. I definitely did this on my first go. 😊 However, I figured that probably wasn’t the best idea and so devised a safer test.

Make sure that the server is reachable before adding it to the blacklist.

ping -c 3 8.8.8.8

There should be zero percent packet loss if everything went well, like in the following output.

PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: icmp_seq=0 ttl=52 time=9.906 ms
64 bytes from 8.8.8.8: icmp_seq=1 ttl=52 time=9.736 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=52 time=10.039 ms

--- 8.8.8.8 ping statistics ---
3 packets transmitted, 3 packets received, 0.0% packet loss
round-trip min/avg/max/std-dev = 9.736/9.894/10.039/0.124 ms

If the packets don’t reach the server successfully, you’ll have to troubleshoot. A PF rule could be blocking outgoing traffic to the server. Hopefully the server your testing against isn’t already on the blacklist.

Now, add the server`s IP address to the blacklist.

/etc/pf-badhost.txt
# User Defined Rules:
8.8.8.8

Update the table by reloading PF.

pfctl -f /etc/pf.conf

Now, verify that the server is no longer reachable.

ping -c 50 8.8.8.8

This should take a minute and not have any successful pings. Your output should match the following, accounting for the IP address you selected.

PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.

--- 8.8.8.8 ping statistics ---
50 packets transmitted, 0 received, 100% packet loss, time 50168ms

This means pf-badhost is successfully blocking traffic outbound to this blacklisted IP address. Remember to remove 8.8.8.8 from the list and reload the PF ruleset once again.

Inbound

Verifying the inbound traffic is not as straightforward. My AWS server allows me to test the ability of pf-badhost to block incoming traffic from blacklisted IP addresses. The IP address 1.2.3.4 represents the server.

First, ensure you can ping the server before it is blacklisted. I had to add a temporary rule to my PF configuration to allow the server to ping the router.

/etc/pf.conf
pass in on egress from 1.2.3.4 to any (1)

# pf-badhost configuration
table <pfbadhost> persist file “/etc/pf-badhost.txt”
block in quick on egress from <pfbadhost>
block out quick on egress to <pfbadhost>
1 Allow traffic to the router from the server.

To account for the new rule, reload the PF ruleset.

pfctl -f /etc/pf.conf

The IP address 2.2.2.2 will represent the router’s gateway address. From the server, ping the router.

ping -c 3 2.2.2.2

It should be able to ping the server, assuming the server is not one of the blacklisted IPs or impeded by a firewall rule. Successful output should look like the following.

PING 2.2.2.2 (2.2.2.2): 56 data bytes
64 bytes from 2.2.2.2: icmp_seq=0 ttl=52 time=9.906 ms
64 bytes from 2.2.2.2: icmp_seq=1 ttl=52 time=9.736 ms
64 bytes from 2.2.2.2: icmp_seq=2 ttl=52 time=10.039 ms

--- 2.2.2.2 ping statistics ---
3 packets transmitted, 3 packets received, 0.0% packet loss
round-trip min/avg/max/std-dev = 9.736/9.894/10.039/0.124 ms

Now, Add the IP address of the server to /etc/pf-badhost.txt

/etc/pf-badhost.txt
# User Defined Rules:
1.2.3.4

Update the table by reloading PF.

pfctl -f /etc/pf.conf

From the server, ping the router again.

ping -c 50 2.2.2.2

The packets should all be dropped, printing the output below.

PING 2.2.2.2 (2.2.2.2) 56(84) bytes of data.

--- 2.2.2.2 ping statistics ---
50 packets transmitted, 0 received, 100% packet loss, time 50168ms

To clean up, remove the server’s IP from the blacklist and reload PF. If applicable, remember to delete the temporary rule in /etc/pf.conf if you added it.