Skip navigation.
Home

Security on This Server

This is the way this server's packet filtering is configured.

Many people would claim that in doing this, I reduce some of the security of the system, but I don't see it that way. I don't believe obscurity adds any significant level of security, thus I feel no need to obscure my setup.

The flip side is that I believe in the concepts of open source. What that means is that I believe that by making this information public, I will get more people helping to make the setup better than who will try to break it, and the result will be even better security than if I kept it secret.

Since this server runs a recent version of FreeBSD I have pf available, which make things nice.

Two features of pf make this very easy.

The first is pf tables, which allow other programs to easily alter the behavior of the packet filter without rewriting rules. The second is the max-src-conn-rate parameter, which allows me to alter firewall rules according to how fast connections are happening.

Let's start by looking at my /etc/pf.conf. It's annotated, so read the comments as I'm not going to explain it separately.

ext_if="bge0"
whitelist="[iprange]"

table <denyssh>
# No need to do any filtering on the loopback
set skip on lo0

# To protect myself from getting locked out
pass quick on $ext_if proto tcp from $whitelist to any port 22 flags S/SA keep state

# People who have violated any number of rules to become part
# of the denyssh table are blocked from accessing the server in
# any way
block return quick on $ext_if from <denyssh> to any

# Allow folks to ssh in from anywhere.  Bots attempt a lot of connections
# in rapid succession, so block them when we see them
pass in quick on $ext_if proto tcp from any to any port 22 flags S/SA keep state \
 (max-src-conn-rate 3/1 overload <denyssh> flush global)

# This is stateless filtering, which avoids any problems with state
# table sizes.  This first rule allows any previously established
# connection to continue by allowing packets that aren't SYN
pass quick on $ext_if proto tcp from any to any flags /S
# These next two rules allow connections to become established by allowing
# the initial SYN packets
pass in quick on $ext_if proto tcp from any to any port {80,443,25,587,993} flags S/S
pass out quick on $ext_if proto tcp from any port {80,443,25,587,993} to any flags S/S
# Allow this system to establish any outgoing connection.  These
# connections aren't terribly common, so allow them to use state
pass out quick on $ext_if proto {udp,tcp,icmp} from any to any keep state

# Block everything not previously allowed
block return log all 

A few points on policy:

  • The <denyssh> table is called that because I'd originally set up denyssh.
  • This policy is extremely draconian. Once you've been added to the block list, you're there "for ever and for everything." Effectively, you're on the list until the next system reboot, which typically occurs every 3 to 6 months. Yes, you're blocked from everything -- if you attacked my server, I see no reason to allow you to continue to attempt to break in via any means, whether it be HTTP or SMTP. I could save the list of IPs to a file so they persist across reboots, but I'm giving everyone the benefit of the doubt -- you get another chance when I reboot the server.

Many folks may find my rules too harsh for their own liking. There are two alternatives (you can do one or both):

  1. Introduce a timeout/aging system to the denyssh table. This adds complexity, but allows addresses to be removed as time goes on without manual intervention. In my case, I'm perfectly happy to keep people on the block list for months on end. Maybe if enough people complain, ISPs will start taking security seriously. I can't justify the complexity of an aging system. I want to make clear that the lack of an aging system is intentional, not laziness.
  2. Only block ssh. This is what many other people do. In my case, if they've attacked me at all, they've forfeited the privilege of accessing my server in any way. If I were trying to sell ringtones or something, I might be less draconian. Also, by using a single blacklist that blocks everything, other components of the system are simpler. Read on.

Now, the beauty of pf is that you can easily tweak the denyssh table from outside the firewall itself. This allows me to write spiffy little monitoring scripts. Let's take a simple example:

#!/usr/local/bin/php
<?php
$fh = popen('tail -F /var/log/httpd-access.log', 'r');
openlog('watchhttp', LOG_PID, LOG_AUTH);
while (true) {
        $line = fgets($fh);
        $line = trim($line);
        if (preg_match('/\"(GET|POST) [^"]+_vti[^"]+\.dll/', $line) ) {
                $fields = explode(' ', $line);
                $ip = $fields[0];
                if (!empty($ip)) {
                        blockThis($ip);
                }
        }
}

function blockThis($ip) {
        syslog(LOG_WARNING, "blocking $ip from HTTP");
        `/sbin/pfctl -t denyssh -T add $ip`;
}

?>

If you don't favor PHP, you could write this in just about any other language you want. The key is that you can grep a log file in real time as data comes in and use the pfctl command to add entries to the denyssh table.

With a little more work, you could create multiple pf tables that block different things and have different scripts monitoring them, so this method can scale up to considerably more complex requirements.

I've learned that you have to be careful about blocking failed HTTP access. At first, I figured that any 404 error should be assumed to be a failed attack vector. In reality, the vast majority of pages not found are legitimate mistakes. Even more common are attempts by automated systems hooked in to browsers to see if features are enabled on your web server. For example, there are certain Microsoft products that attempt to access dll files in an attempt to ascertain whether Microsoft-style services are available.

I'll keep this page updated as I make improvements to the system. If you feel that you've found a flaw, or have a suggestion to improve it, feel free to drop me a line at wmoran@potentialtech.com

To finish out the details, here is a second script I run to do additional checks on ssh traffic:

#!/usr/local/bin/php
<?php
$fh = popen('tail -F /var/log/auth.log', 'r');
openlog('watchssh', LOG_PID, LOG_AUTH);
while (true) {
        $line = fgets($fh);
        $line = trim($line);
        if (preg_match('/Invalid user/', $line) ) {
                $fields = explode(' ', $line);
                $ip = trim(array_pop($fields));
                if (!empty($ip)) {
                        blockThis($ip);
                }
        }
}

function blockThis($ip) {
        syslog(LOG_WARNING, "blocking $ip from SSH");
        `/sbin/pfctl -t denyssh -T add $ip`;
}

?>

Document History

  • 2007-04.16: Corrected html problems and clarified aging strategy
  • 2007-04-15: Initial publication