#!/usr/bin/perl # SSH probe blocker for FreeBSD # # Requires ipfw # # Allows you 10 failed attempts before blocking the IP. If it does not receive # an attempt for 5 minutes, the counter for that IP is reset. Addresses get # unblocked after 24 hours. # # We need a pool of IPFW rule numbers, here we use 1100 to 1199, the worst # that'll happen if we overflow that 100 is that some addresses will get # unblocked early. Also could do with a whitelist # # tom@marmot.org.uk use strict; use File::Tail; my %ipdb; # must be a nicer way of doing this... for (1100..1199) { system "/sbin/ipfw delete $_"; } # this bit could probably be done better with IO::KQueue my $file=File::Tail->new("/var/log/auth.log"); my $rulenum = 1100; while (defined(my $line=$file->read)) { if ($line =~ m/sshd.*Failed password for.*from (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/) { warn "failed login from $1"; $ipdb{$1}{counter}++; $ipdb{$1}{expire} = (time() + 300); for (keys %ipdb) { if ($ipdb{$_}{counter} > 10) { # you're going down warn "blocking $_"; $ipdb{$_}{expire} += 86400; $ipdb{$_}{counter} = -1000; $ipdb{$_}{rulenum} = $rulenum; system "/sbin/ipfw add $rulenum deny tcp from $_ to 212.13.198.165 dst-port 22"; $rulenum = 1100 if ++$rulenum > 1199; next; } if ($ipdb{$_}{expire} < time()) { if ($ipdb{$_}{rulenum}) { system "/sbin/ipfw delete $ipdb{$_}{rulenum}"; warn "deleting rule $ipdb{$_}{rulenum}"; } warn "expiring $_"; delete $ipdb{$_}; } } } } exit 0