#! /usr/bin/perl -w # # ssh-brute-stop # # Unsophisticated script to block IPs with too many # illegal ssh attempts. # # http://brainsik.to/ssh-brute-force # Jeremy Avnet >brainsik-code(at)theory.org< # # How it works: # * Reads in your syslog auth log in real time (via fifo). # * Every bad SSH attempt gives the IP points. # (Caveat: only illegal attempts are counted, # i.e. attempts to login as non-existant users.) # * When a maximum point level is passed, # the IP is blocked using iptables. # # Required configuration: # 1) mkfifo /var/log/auth.fifo # 2) edit /etc/syslog.conf and add this line: # auth,authpriv.* |/var/log/auth.fifo # (i put mine below the auth.log line near the top) # 3) /etc/init.d/sysklogd restart # # Optional configuration: # * Whitelists: The script auto-whitelists local addresses. If you # want to add some more addresses to never be blocked look below for # "whitelist". # * Points: You can tweak the point values and max points. The # current setup blocks any IP that tries to login as 3 different # (non-existant) users or 9 times for a single (non-existant) user. I # wanted to be careful about valid users trying to login under a # single, wrong name (as happens sometimes). # # Changelog: # * 20050805: First public release. # * 20050822: Changed regex to match "Invalid" or "Illegal". # * 20060315: Detect failed passwords for root. # use strict; use Socket; use Sys::Syslog; my $DEBUG = 0; my $NAME = 'ssh-brute-stop'; my $MAXPOINTS = 9; my $PTS_NEWILLEGAL = 3; my $PTS_OLDILLEGAL = 1; my $PTS_ROOTFAIL = 3; my $AUTH_FIFO = "/var/log/auth.fifo"; # # bad password # sshd[5768]: (pam_unix) authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=localhost.localdomain user=brainsik # sshd[5768]: error: PAM: Authentication failure for brainsik from localhost.localdomain my( %log, %blocked, %points ); die "The required named pipe at $AUTH_FIFO does not exist! Exiting..\n" unless -p $AUTH_FIFO; # IP whitelist whitelistself(\%blocked); # add local addresses to whitelist # add any more like below #$blocked{'127.0.0.1'} = 'never'; while ( open(AUTH_FIFO, $AUTH_FIFO) ) { while () { my( $user, $ip ); # check for illegal (unknown) user attempts if ( /sshd\[\d+\]: (?:Illegal|Invalid) user (\w+) from (?:::ffff:)?([\d.]+)/ ) { ($user, $ip) = ($1, $2); $points{$ip} += (exists $log{$ip}{$user}) ? $PTS_OLDILLEGAL : $PTS_NEWILLEGAL; $log{$ip}{$user} = time; } elsif ( /sshd\[\d+\]: Failed password for root from (?:::ffff:)?([\d.]+)/ ) { $ip = $1; $points{$ip} += $PTS_ROOTFAIL; $log{$ip}{$user} = time; } else { next } blockip(\%points, \%blocked, $ip); } close AUTH_FIFO; print STDERR "closed $AUTH_FIFO at ", scalar localtime, "\n" if $DEBUG; sleep 1; } die "Could not open $AUTH_FIFO\n"; sub blockip { my $points = shift; my $blocked = shift; my $ip = shift; my $iptables = '/sbin/iptables -I INPUT -j DROP -s'; # block if past threshold if ( $$points{$ip} >= $MAXPOINTS ) { # avoid duplicate iptable block lines unless ( exists $$blocked{$ip} ) { system "$iptables $ip\n"; $$blocked{$ip} = 'iptables'; my $iaddr = inet_aton($ip); my $host = gethostbyaddr($iaddr, AF_INET); openlog($NAME, 'pid', 'auth'); syslog('notice', 'BLOCKED: %s', ( ($host) ? "$host ($ip)" : "$ip") . " [+$$points{$ip}]"); closelog(); } return 1; # ip is blocked } return 0; # ip not blocked this time } sub whitelistself { my $blocked = shift; my @ifconfig = `/sbin/ifconfig`; foreach (@ifconfig) { next unless /inet addr:([\d.]+)/; $$blocked{$1} = 'never'; } print "* added local addresses to whitelist:", map { " $_" } keys %blocked, "\n"; }