FreeBSD SSH port security 2

From JonDonym Wiki
Revision as of 15:29, 22 March 2010 by Kn (Talk | contribs)
Jump to: navigation, search

navigation: Main Page | FreeBSD and Jails | Setting up the PF packet filter

Setting up the PF packet filter

PF is „on board“ with each FreeBSD system and already installed as a standard when you set up a host. But it needs to get activated and configured before you can use it.

Make sure, you're on your host system please. All PF stuff only will get installed on the host system. No work needs to be done within any of the jails. Become root then by


First thing to do is to load the PF kernel modules. To load those modules while the host system is already up just enter:

kldload pf
kldload pflog

Then check, whether the modules are loaded indeed by entering:


Both modules should get listed (among others).

You by the way do not need the pflog module in any case as it's needed only for logging of PF activities. But loading the „pf“ module is a must have for this setup.

As you almost certainly want the modules get automatically loaded at boot time you also should open the kernel modules file and add entries for both modules.

vi /boot/loader.conf

Then add two lines there:


Loading the kernel modules (no matter whether manually or during bootup) does not mean that the PF packet filter was already active. There's also a daemon needed to get started.

You can make PF starting automatically at boot time by additional entries in the /etc/rc.conf file.

vi /etc/rc.conf

Add two lines there (no matter where, just two new lines):


Next, you need two new files in the /etc directory. pf.conf is the file which will contain the firewall ruleset. pf.table.fail2ban is the file collecting the banned IP addresses from PF and fail2ban.

touch /etc/pf.conf
touch /etc/pf.table.fail2ban

Ensure, both files will be writeable only by root.

chmod 600 /etc/pf.conf 
chmod 600 /etc/pf.table.fail2ban

There by the way is another pf file located in /etc – the pf.os file. You do not need to do anything there. pf.os is for SYN signatures (we will not work with these). Just leave it as it is.

Setting up a firewall on a remote host is always a bit risky as you could block yourself by accident. So, better to prevent those sort of accidents by doing a little trick. Add a line to your /etc/crontab file which will switch off the PF firewall every 5 minutes.

vi /etc/crontab

Add a line containing the following entry:

0/5	*	*	*	*	root	/sbin/pfctl -d 2>/dev/null

So, in case you blocked yourself just look at your watch to see when you will be able to SSH again into your host system (assuming your watch and the hosts system clock are synchronized).

Short introduction on how to control PF

The control program for the packet filter is /sbin/pfctl (PF control).

You can activate (enable) PF by

pfctl -e

or alternatively by

/etc/rc.d/pf start

You can de-activate (disable) PF by

pfctl -d

or alternatively by

/etc/rc.d/pf stop

Leaving path/filename away when using pfctl means, the default path/filename (/etc/pf.conf) will be used. Or in other words – giving a path/filename with the -f option allows you to chose a different file than /etc/pf.conf. Of course, it otherwise doesn't hurt to use the -f option with the default path/filename.

To test the ruleset in pf.conf for syntax errors without actually loading the ruleset:

pfctl -nf /etc/pf.conf

Same as before but with verbose output:

pfctl -nvf /etc/pf.conf

To actually load a ruleset (not yet with enabling PF):

pfctl -f /etc/pf.conf

Using PF packet filter

We now can enable PF. It of course will not show any effect as the PF ruleset isn't present this time (the /etc/pf.conf file is still empty) but we nonetheless can test things as done so far.


pfctl -e

The output will be:

No ALTQ support in kernel
ALTQ related functions disabled
pf enabled

Don't worry about the two error message lines on ALTQ. This isn't something we need for our purposes.

Then switch it off again by entering:

pfctl -d
No ALTQ support in kernel
ALTQ related functions disabled
pf disabled

Everything seems okay so far.

We of course now need to insert the ruleset to the /etc/pf.conf file. You may checkout a standard ruleset file for PF from the JonDos svn repository. This file is a lean but fully functional skeleton. You only need to adapt it to your hosts individual environment.

Download the file from the repository, save it as pf.conf in the /etc directory (overwriting the old and empty pf.conf there). Then open it with your editor as you will need to adapt some of the lines.

Adaptions in the MACROS section of the file:

Find the line


Replace the string „em0“ by the device name of the ethernet adapter on your host. If the device name was „myk0“ then change the line in


If you are not sure about the device name use „ifconfig“ to check the device name. If there's more than one adapter, use the one (and the IP address) assigned to the SSH server daemon.

Find now the line


Replace the example IP by the generic IP of your host (the one, SSHD will listen to).

In case your hosts SSHD will listen on port 22, nothing needs to be changed in the line

bfports=“{ 22 }“

Otherwise replace „22“ by the port number your SSHD listens to.

Adaptions in the TABLES section:

Find the line

table <myhosts> …

Replace the IP addresses between the brackets by those IPs from which you want to access your host without the PF ruleset beeing applied.

Done. No further adaptions are needed. Doublecheck your changes, then save the file and exit.

Now we'll test the firewall again, but this time with the real ruleset in effect. As you almost probably by time will adapt the ruleset, here's a method of careful testing (avoiding full risk of blocking yourself by accident):

First, let PF check the ruleset without actually loading the ruleset:

pfctl -nvf /etc/pf.conf

If your pf.conf is free of syntax errors, pfctl will output the ruleset, resolving the macros. If there are syntax errors, it otherwise will complain about them and you first have to fix them.

Assuming there are no errors, we'll go ahead – this time loading the ruleset:

pfctl -vf /etc/pf.conf

The output will now be the same as before but the two lines with error messages about ALTQ will get output in advance. But despite of the loaded ruleset, PF still does not work.

To start PF, using the already loaded ruleset, enter:

pfctl -e

PF is active now. You might experience, that your SSH connection gets stuck for a short moment (through enabling PF) but usually it will recover at once and you can go ahead with your SSH session. In worst case, you get thrown out and need to re-ssh onto your host (which will be possible without further problems as long as the ruleset was adapted right).

In case you cannot login via SSH you almost probably blocked yourself by accident (through some sort of misconfiguration). You then have to look at your watch and wait until the cronjob will switch off the firewall and let you logon again. You then had to find and correct the failure, enable PF again and see what happens then.

In case your original SSH session was not interrupted, nonetheless try to logon via a second SSH session to see whether it was still possible. Best was, to try that two times – one from an IP included in your exception list and another one not included.

Back in your (first) SSH session, you now may do some further investigations and testing on PF.

The next command will inform you on whether or not PF is running (enabled).

pfctl -s info

Something like „Status: Enabled for 0 days 00:01:08“ will tell you that PF is working since one minute and 8 seconds.

To see the currently active ruleset:

pfctl -sr

To list the current content of the fail2ban table in memory:

pfctl -t fail2ban -T show

To write out the table in memory to a disk file:

pfctl -t fail2ban -T show > /etc/pf.table.fail2ban

Same, with eliminating error messages:

pfctl -t fail2ban -T show > /etc/pf.table.fail2ban  2>/dev/null

To manually add a certain IP address to the table:

pfctl -t fail2ban -T add

You might use the latter, to test whether you get blocked when trying to login from a certain machine.

To manually add a complete subnet (written in CIDR) to the table:

pfctl -t fail2ban -T add

To delete a certain IP address from the table:

pfctl -t fail2ban -T delete

To load the IP list from the disk file into the table in memory:

pfctl -t fail2ban -T replace -f /etc/pf.table.fail2ban

As soon as you are sure your ruleset works fine and you feel comfortable with PF you can do the last steps.

We will now ensure that all caught IP addresses will not only be persistent (in memory) while the host system runs. We also want PF to remember them after rebooting. To ensure the latter, we will write the table containing all the banned IPs to the /etc/pf.table.fail2ban file. CRON can do that for us every minute so almost probably none of the banned IPs will get lost.

Open again /etc/crontab

vi /etc/crontab

and add another line there: 0/1 * * * * root pfctl -t fail2ban -T show >/etc/pf.table.fail2ban 2>/dev/null

This will overwrite the then existent version of /etc/pf.table.fail2ban each minute by the then content of PFs table of banned IPs in memory (which we named „fail2ban“ as it will be served by the fail2ban program too lateron). Additionally banned IPs only can get lost if an additional IP gets caught with your host crashing before the table was written to disk by CRON one more. But if this IP is a stubborn attacker, he/she will try again after the host recovered and then gets caught once more very quick.

Finally, after you're done playing around, ensure PF goes on working with the right ruleset:

Stop PF by

pfctl -d

Edit the cronfile:

vi /etc/crontab

and uncomment or change the line we inserted earlier to switch off PF every 5 minutes.

Load the „production version“ of the ruleset again from /etc/pf.conf:

pfctl -vf /etc/pf.conf

Doublecheck the loaded ruleset:

pfctl -sr

Enable PF again:

pfctl -e

Check whether there are any IPs in the fail2ban table in memory:

pfctl -t fail2ban -T show

In case there are any IPs in the table left, you might have added earlier for testing, delete them by:

pfctl -t fail2ban -T delete

Check also the file version of the table by:

cat /etc/pf.table.fail2ban

Done. PF is now working with the appropriate ruleset. The table in memory is empty as well as the file version /etc/pf.table.fail2ban. It's now awaiting the first IP to block because of an brute force trial against your SSH server.

We next will add the fail2ban python script, collaborating with PF.

Personal tools