Avoiding DDOS: the PF way

I’ve run a FreeBSD server in my home for six years now. I love the capabilities home servers give you over your bog-standard wireless router – mine, for example, downloads all my POP3 email from various sources, runs it through a Bayesian-enhanced SpamAssassin and filters it through into various IMAP folders (on my boxes, usually Thunderbird or, on the laptop, Mail.app). But you’ve got to be very careful with this, and apart from a front-facing Postfix for email directed at my dynamic DNS domain I have had no regularly open ports. What if I want to access my email from work, for instance?

For this, I’d like to use SSH forwarding; putting the IMAP port through to a local port on the machine I’m using, with the actual data transferred securely over the Internet and where no-one can listen in, even if I’m on some crappy open wireless somewhere. SSH is configured to only accept public key authentication, and to refuse all password access – if you try connecting from a normal SSH client without a relevant key, you get dumped back to your command line with my snidely worded banner, and a “No password access” message. The only public key is in my possession and, of course, is passworded.

Despite this, having open SSH attracts scumbags like paparazzi to Amy Winehouse and the system I use for my firewall (a 733MHz Pentium-III with 256MB RAM) simply can’t cope with thousands of individual connections doing ineffectual dictionary attacks on usernames over Virgin’s 20Mbit connection; it locks up with a massive load average somewhere in the “c”‘s. As an added bonus, this of course eats my “unlimited” download cap during that particular point of the day.

How, therefore, can I balance my security with my convenience? The answer is the same thing I use to do my NAT forwarding, the pf packet filtering firewall.

pf originated with OpenBSD, and was introduced into FreeBSD somewhere around 5.3: I switched from FreeBSD’s own ipfw2 when I upgraded from 4.x to 6.x. As a bonus, pf allows dynamic lists to be built up of IPs that trigger specific rules, allowing for dynamic blocking of SSH offenders.

After my initial “block in” rule in my pf.conf, I define a table:

block in

table <abusive_hosts> persist
block quick from <abusive_hosts>

This defines a list of abusive hosts, traffic from which is blocked without any further discussion (with pf, applicable rules lower down the list take precidence over rules further up unless ‘quick’ is provided, which cuts off further parsing.) You can manually add to this table like so:

pfctl -t abusive_hosts -Tadd <IP address>

Or, more interestingly, you can add to it programatically. After my catch-all NAT rules, I make a rule to allow access to the local SSH port – with a catch.

pass in on $ext_if proto tcp to ($ext_if) port ssh flags S/SA keep state \
        (max-src-conn 10, max-src-conn-rate 6/30, overload <abusive_hosts> flush global)

This allows up to ten simultaneous connections from a particular SSH port, or up to six within thirty seconds. flush kills the states for previously OK connections when it over-runs; global kills all connections from the IP. And the overload rule causes all those things which fail the rule to be pushed into the abusive_hosts table, meaning anything that’s bad and repeatedly connects to my SSH port end up going straight to null.

And this works, too. Using the pfctl command, you can view the contents of the table. I’ll pass it first through awk to remove the spacing, aiding with xargs for further piping, and then through “wc -l” to get the line count:

orpheus# pfctl -t abusive_hosts -Tshow | awk '{print $1}' | wc -l

Removing ‘| wc -l’ gets you a list of IPs, and putting ‘xargs -n 1 host’ there instead gets you a list of the hostnames associated with each of the IPs which can give you an interesting picture: at least a couple of them right now are IPs on American cable modems who are almost certainly compromised home users.

That’s twenty-two abusive hosts who’ve met my SSH blackhole since I last rebooted my machine, who would otherwise have been a problem: pfctl -sr -v (which is sent to you in your nightly root emails) tells me that right now I’ve blocked 5.3MB of unwanted traffic from these hosts since I last rebooted 18 days ago, and I’m sure I’d have got much more if they hadn’t started getting nothing but silence from my machines since the point of blocking.

I’ve found this immeasurably useful for increasing my box’s uptime and overall reliability, which helps prove that a PIII type machine is still good enough for quite a lot of things. And if you click the link to read further, I’ve posted my complete (and only slightly altered) pf.conf for anyone’s interest.

Continue reading “Avoiding DDOS: the PF way”