Blocking script kiddies with PF

OpenBSD’s PF firewall is brilliant. Not only is it easy to configure with a straightforward syntax, but it’s easy to control on-the-fly.

Supposing we had a script that scanned through log files and picked up the IP address of someone trying random passwords to log in. It’s easy enough to write one. Or we noticed someone trying it while logged in. How can we block them quickly and easily without changing /etc/pf.conf? The answer is a pf table.

You will need to edit pf.conf to declare the table, thus:

# Table to hold abusive IPs
table <abuse> persist

“abuse” is the name of the table, and the <> are important! persist tells pf you want to keep the table even if it’s empty. It DOES NOT persist the table through reboots, or even restarts of the pf service. You can dump and reload the table if you want to, but you probably don’t in this use case.

Next you need to add a line to pf.conf to blacklist anything in this table:

# Block traffic from any IP in the abuse table
block in quick from <abuse> to any

Make sure you add this in the appropriate place in the file (near or at the end).

And that’s it.

To add an IP address (example 1.2.3.4) to the abuse table you need the following:

pfctl -t abuse -T add 1.2.3.4

To list the table use:

pfctl -t abuse -T show

To delete entries or the whole table use one of the following (flush deletes all):

pfctl -t abuse -T delete 1.2.3.4
pfctl -t abuse -T flush

Now I prefer to use a clean interface, and on all systems I implement a “blackhole” command, that takes any number of miscreant IP addresses and blocks them using whatever firewall is available. It’s designed to be used by other scripts as well as on the command line, and allows for a whitelist so you don’t accidentally block yourself! It also logs additions.

#!/bin/sh

/sbin/pfctl -sTables | /usr/bin/grep '^abuse$' >/dev/null || { echo "pf.conf must define an abuse table" >&2 ; exit 1 ; }

whitelistip="44.0 88.12 66.6" # Class B networks that shouldn't be blacklisted

for nasty in "$@"
do
        echo "$nasty" | /usr/bin/grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' >/dev/null || { echo "$nasty is not valid IPv4 address" >&2 ; continue ; }

        classb=$(echo "$nasty" | cut -d . -f 1-2)

        case " $whitelistip " in
                *" $classb "*)
                echo "Whitelisted Class B $nasty"
                continue
                ;;
        esac

        if /sbin/pfctl -t abuse -T add "$nasty"
        then
                echo Added new entry $nasty
                echo "$(date "+%b %e %H:%M:%S") Added $nasty" >>/var/log/blackhole
        fi
done

That’s all there is two it. Obviously my made-up whitelist should be set to something relevant to you.

So how do you feed this blackhole script automatically? It’s up to you, but here are a few examples:

/usr/bin/grep "checkpass failed" /var/log/maillog | /usr/bin/cut -d [ -f3 | /usr/bin/cut -f1 -d ] | /usr/bin/sort -u

This goes through mail log and produces a list of IP addresses where people have used the wrong password to sendmail

/usr/bin/grep "auth failed" /var/log/maillog | /usr/bin/cut -d , -f 4 | /usr/bin/cut -c 6- | /usr/bin/sort -u

The above does the same for dovecot. Beware, these are brutal! In reality I have an additional grep in the chain that detects invalid usernames, as most of the script kiddies are guessing at these and are sure to hit on an invalid one quickly.

Both of these examples produce a list of IP addresses, one per line. You can pipe this output using xargs like this.

findbadlogins | xargs -r blackhole

The -r simply deals with the case where there’s no output, and will therefore not run blackhole – a slight efficiency saving.

If you don’t have pf, the following also works (replace the /sbin/pfctl in the script with it):

/sbin/route -q add $nasty 127.0.0.1 -blackhole 2>/dev/null

This adds the nasty IP address to the routing table and directs packets from it to somewhere the sun don’t shine. pf is probably more efficient that the routing table, but only if you’re using it. This is a quick and dirty way of blocking a single address out-of-the-box.

Sophos UTM sets ambitions goals; and fails to score

Okay, I’m being a bit unfair on singling out Sophos here, but they’re a current source of irritation. Like all security vendors they’re selling products that don’t work. Actually, Sophos is one of the few larger players that will talk about this honestly, which is why they have been my first choice recommendation for a long time.

The problem is that if you have companies selling “total security” products, which are nothing of the sort, the public are likely to believe such a thing is possible. If you describe your product realistically the idiots will look elsewhere, purchasing based on the most outrageous claims. A look at the Sophos customer base suggests they’re not selling to idiots.

So what’s my problem with Sophos at the moment. Well I’m falling foul of their UTM Web Defender at an educational establishments. Some of my information sites are unclassified on their list of web sites, and so they’re blocked. They contain educational material that I use when teaching. Not helpful.

Okay, this isn’t default behaviour and the establishments in question have made a decision to block anything that Sophos hasn’t classified yet. Some of these sites have been there since 1992, so presumably there’s a long backlog. And this illustrates the problem very nicely; there are over 300,000,000 domain names registered, with 1,000,000 being added every month. Web filtering companies have to look at all these web sites, and sub domain web sites, and classify them all. It’s an impossible task. I know Sophos does this manually, heroic but doom to failure.

The World Wide Web was created to allow the sharing of knowledge; particularly academic and research information. Unfortunately this is just the kind of web site that’s likely to remain unclassified by content filters; obscure links to non-commercial servers giving the information needed for research.

There is a solution. A few years ago I decided to write my own web search engine for a laugh. I then modified it to try and figure out what the web sites were about. Google has built an empire on doing this extremely well, but my quick heuristic solution did a pretty good job.

So here’s what Sophos et all should do. When their web defender appliance hits an unclassified site it should automatically submit it to them for evaluation. An automated system using heuristics can then figure out the likely classification, with a probability threshold for human checking.

This doesn’t have to be instant to be a hell of a lot better than their current system. To get past a Sophos filter (for example) you have to manually submit every site to them by filling in a form, and then they’ll go and classify it within a week. Possibly. And in reality, who’s going to submit such a request to access a web site they can’t actually view because it’s blocked as “unclassified”. There’s a hole in their bucket!