SSH Log Action Script


Jump to: navigation, search


SSH Brute force password guessing is a common occurrence for any Internet-exposed machine running ssh. The point of this script is to monitor your auth log for such password failures and block them via iptables, as well as notify syslog and optionally a machine on the local network via Growl (for Mac OS X).


Modify the variables in the script as per the comments. Then run the script via cron.

I run the script on my Ubuntu Linux machine every 5 minutes with an /etc/cron.d entry:

*/5 * * * * root /usr/sbin/logact >/dev/null 2>/dev/null

If there is a network block you don't want to ever block, please look for the "DON'T BLOCK" section and modify the regex.


# logact - block/notify on ssh login abuse 
# 07/07/05 - 
# 11/06/06 - growl support added 
# to remove block
# /sbin/iptables -t filter -D INPUT 3 to remove (where 3 is the 3rd entry)
use Sys::Syslog qw(:DEFAULT setlogsock);  
use Email::Find;
use MIME::Lite;
use Net::Growl;  # if you want to use Growl you need this and the $GROWL_* vars
use Net::hostent;
use Socket;
# threshold before block
# set to 1 if you want to send an email to hosts abuse contact
# threshold before notification
# logtail binary  
# log file where ssh auth failures are located
# offset file (you'll need to create this dir)
$DEV = "eth0";
$MYIP= "external_IP_here";
# set these if you want to support growl alerts (OS X) of blocked hosts
$ip_regex = qw '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}';
while(<TAIL>) {
	# Failed password for invalid user java from 
	if (/Failed password for invalid user \S+ from ($ip_regex)/) {
		$ip_data{$1} = $ip_data{$1} . $_;
		$subject{$1} = "[ABUSE] Illegal login attempts from $1";
	} elsif (/no such user found from ($ip_regex)/) {
		$ip_data{$1} = $ip_data{$1} . $_;
		$subject{$1} = "[ABUSE] Illegal login attempts from $1";
foreach $ip (keys %ip_count) {
    # DON'T BLOCK regex... 
    if ($ip !~ /^192\.168\.1/) {
	if ($ip_count{$ip} >= $THRESHOLD) {
		#print "Blocking $ip:\n$ip_data{$ip}\n\n";
	if ($ip_count{$ip} >= $NOTIFY_THRESHOLD) {
	    if ($NOTIFY_ADMIN) {
sub block {
	my ($ip,$attempts) = @_;
	my $did_notify ="";
	$did_notify = " and notifying" if ($attempts >= $NOTIFY_THRESHOLD);
	syslog('notice',"logact: Blocking" . $did_notify . " $ip for $ip_count{$ip} failed login attempts");
	system("/sbin/iptables -t filter -A INPUT -j DROP -s $ip -i $DEV");
	if ($h = gethost($ip)) {
		$ip = "$host ($ip)";
      if ($GROWL_APP && $GROWL_PASS && $GROWL_HOST) {
	Net::Growl::register(host => $GROWL_HOST, application=>$GROWL_APP, password=>$GROWL_PASS) if !$ALREADY_REGISTERED;
	Net::Growl::notify(host=> $GROWL_HOST, application=>$GROWL_APP,
        title=>'Deny Rule Added',
        description=>"for address $ip",
sub notify_email {
	my ($ip) = @_;
	my $to;
	my %addresses;
	my $data;
	open(WHOIS, "whois -h $ip|");
	while(<WHOIS>) {
		my $finder = Email::Find->new(sub { 
	foreach $address (keys %addresses) {
		next if ($address =~ /apnic/);
		next if ($address =~ /arin/);
		$to = $address . ", " . $to;
	chop($to); chop($to);
	#print "To: $to\n";
	$data = "\nThis is an automated abuse message being generated to" .
	        " alert you that a host within your jurisdiction has been" .
		" attempting to brute force login to one of my machines." .
		" While this message is automated, I will respond to all" .
		" replies.\n\n" .
		"The below logs show abuse activity from $ip to $MYIP (all times are US/Eastern (GMT-5):\n";
	$data = $data . $ip_data{$ip};
	$data = $data ."\n\nPlease address this situation in accordance to".
	        " your domains abuse policy.  Feel free to contact me directly".
		" by replying to this message.";
	if ($to !~ /\S+/) {  # where whois returns no admin emails
		$to = $ADMIN_EMAIL;
	$msg = MIME::Lite->new(
		From => "$ADMIN_EMAIL",
		To   => "$to",
		Cc   => "$ADMIN_EMAIL",
		Subject => $subject{$ip},
		Data => $data