network/sshblock: Added (an SSH dictionary-attack blocker).

Signed-off-by: Matteo Bernardini <ponce@slackbuilds.org>
This commit is contained in:
Willy Sudiarto Raharjo 2012-12-16 22:16:20 +01:00 committed by Matteo Bernardini
parent de6041627c
commit 8d0150f686
9 changed files with 618 additions and 0 deletions

7
network/sshblock/README Normal file
View File

@ -0,0 +1,7 @@
SSHblock is intended to dynamically and automatically stop SSH-based
dictionary attacks by blocking any IP address that fails an SSH
login too many times too quickly, and automatically unblocks it
after a while.
You may change the pre-defined configuration of SSHblock in
sshblock.pl.tpl (whitelist IP, email, and hostname).

View File

@ -0,0 +1,14 @@
config() {
NEW="$1"
OLD="$(dirname $NEW)/$(basename $NEW .new)"
# If there's no config file by that name, mv it over:
if [ ! -r $OLD ]; then
mv $NEW $OLD
elif [ "$(cat $OLD | md5sum)" = "$(cat $NEW | md5sum)" ]; then
# toss the redundant copy
rm $NEW
fi
# Otherwise, we leave the .new copy for the admin to consider...
}
config etc/rc.d/rc.sshblock.new

View File

@ -0,0 +1,56 @@
#!/bin/bash
if [ ! $UID ]; then
echo "You must be root to use SSHblock."
exit 1;
fi
case "$1" in
'start')
swatch -c /etc/swatch/sshblock -t /var/log/messages &> /dev/null &
if [ ! `ls /etc/cron.hourly | grep sshunblock` ]; then
ln -s /usr/sbin/sshunblock.pl /etc/cron.hourly
fi
;;
'stop')
pid=`ps auxwww | grep swatch | grep -v grep | grep sshblock | awk '{print $2}'`
kill $pid
;;
'clear')
for ip in `iptables -nL INPUT | tail +3 | grep DROP | grep dpt:22 | awk '{print $4}'`; do
iptables -D INPUT -p tcp -s $ip --dport 22 --syn -j DROP
done
;;
'list')
echo "Blocked IP addresses:"
iptables -nL INPUT | tail +3 | grep DROP | grep dpt:22 | awk '{print $4}'
;;
'status')
blocking=`ps auxwww | grep swatch | grep -v grep | grep sshblock | wc -l`
blocked=`iptables -nL INPUT | tail +3 | grep DROP | grep dpt:22 | wc -l`
unblocking=`ls -l /etc/cron.hourly | grep sshunblock | wc -l`
if [ $blocked -eq 1 ]; then
pl=''
verb='is'
else
pl='es'
verb='are'
fi
if [ $blocking -gt 0 ]; then
echo "SSHblock is active"
else
echo "SSHblock is not running"
fi
echo "There $verb currently $blocked address$pl blocked."
;;
*)
echo "Usage: $0 [start|stop|clear|status|list]"
echo " "
echo "start: Start SSHblock system"
echo "stop: Stop blocking new IPs; old ones will still expire at the usual rate"
echo "clear: Clear all blocked addresses"
echo "status: Report whether SSHblock is running, how many IPs are blocked"
echo "list: List all blocked IP addresses"
exit
;;
esac

View File

@ -0,0 +1,19 @@
# HOW TO EDIT THIS FILE:
# The "handy ruler" below makes it easier to edit a package description.
# Line up the first '|' above the ':' following the base package name, and
# the '|' on the right side marks the last column you can put a character in.
# You must make exactly 11 lines for the formatting to be correct. It's also
# customary to leave one space after the ':' except on otherwise blank lines.
|-----handy-ruler------------------------------------------------------|
sshblock: sshblock (an SSH Dictionary-Attack Blocker)
sshblock:
sshblock: SSHblock is intended to dynamically and automatically stop SSH-based
sshblock: dictionary attacks by blocking any IP address that fails an SSH
sshblock: login too many times too quickly, and automatically unblocks it
sshblock: after a while.
sshblock:
sshblock: Project Website: http://kagan.mactane.org/software/sshblock/
sshblock:
sshblock:
sshblock:

View File

@ -0,0 +1,95 @@
#!/bin/sh
# Slackware build script for sshblock
# Copyright 2012 Willy Sudiarto Raharjo <willysr@slackware-id.org>
# All rights reserved.
#
# Redistribution and use of this script, with or without modification, is
# permitted provided that the following conditions are met:
#
# 1. Redistributions of this script must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
PRGNAM=sshblock
VERSION=${VERSION:-0.5}
BUILD=${BUILD:-1}
TAG=${TAG:-_SBo}
if [ -z "$ARCH" ]; then
case "$( uname -m )" in
i?86) ARCH=i486 ;;
arm*) ARCH=arm ;;
*) ARCH=$( uname -m ) ;;
esac
fi
CWD=$(pwd)
TMP=${TMP:-/tmp/SBo}
PKG=$TMP/package-$PRGNAM
OUTPUT=${OUTPUT:-/tmp}
if [ "$ARCH" = "i486" ]; then
SLKCFLAGS="-O2 -march=i486 -mtune=i686"
LIBDIRSUFFIX=""
elif [ "$ARCH" = "i686" ]; then
SLKCFLAGS="-O2 -march=i686 -mtune=i686"
LIBDIRSUFFIX=""
elif [ "$ARCH" = "x86_64" ]; then
SLKCFLAGS="-O2 -fPIC"
LIBDIRSUFFIX="64"
else
SLKCFLAGS="-O2"
LIBDIRSUFFIX=""
fi
set -e
rm -rf $PKG
mkdir -p $TMP $PKG $OUTPUT
cd $TMP
rm -rf $PRGNAM-$VERSION
tar xvf $CWD/$PRGNAM-$VERSION.tar.bz2
cd $PRGNAM-$VERSION
chown -R root:root .
find . \
\( -perm 777 -o -perm 775 -o -perm 711 -o -perm 555 -o -perm 511 \) \
-exec chmod 755 {} \; -o \
\( -perm 666 -o -perm 664 -o -perm 600 -o -perm 444 -o -perm 440 -o -perm 400 \) \
-exec chmod 644 {} \;
mkdir -p $PKG/usr/sbin $PKG/etc/ssh $PKG/etc/swatch $PKG/etc/rc.d $PKG/etc/cron.hourly
touch $PKG/etc/ssh/block-history
install -m 0644 $CWD/rc.sshblock $PKG/etc/rc.d/rc.sshblock.new
install -m 0755 $CWD/sshblock.pl.tpl $PKG/usr/sbin/sshblock.pl
install -m 0755 $CWD/sshunblock.pl.tpl $PKG/usr/sbin/sshunblock.pl
install -m 0755 $CWD/sshblock.tpl $PKG/etc/swatch/sshblock
install -m 0755 $CWD/sshunblock.pl.tpl $PKG/etc/cron.hourly/sshunblock
find $PKG -print0 | xargs -0 file | grep -e "executable" -e "shared object" | grep ELF \
| cut -f 1 -d : | xargs strip --strip-unneeded 2> /dev/null || true
mkdir -p $PKG/usr/doc/$PRGNAM-$VERSION
cp -a \
README \
$PKG/usr/doc/$PRGNAM-$VERSION
cat $CWD/$PRGNAM.SlackBuild > $PKG/usr/doc/$PRGNAM-$VERSION/$PRGNAM.SlackBuild
mkdir -p $PKG/install
cat $CWD/slack-desc > $PKG/install/slack-desc
cat $CWD/doinst.sh > $PKG/install/doinst.sh
cd $PKG
/sbin/makepkg -l y -c n $OUTPUT/$PRGNAM-$VERSION-$ARCH-$BUILD$TAG.${PKGTYPE:-tgz}

View File

@ -0,0 +1,10 @@
PRGNAM="sshblock"
VERSION="0.5"
HOMEPAGE="http://kagan.mactane.org/software/sshblock/"
DOWNLOAD="http://kagan.mactane.org/software/libraries/download/sshblock-0.5.tar.bz2"
MD5SUM="dbfaee5f45296de2f9a22d5fe79e7332"
DOWNLOAD_x86_64=""
MD5SUM_x86_64=""
REQUIRES="swatch"
MAINTAINER="Willy Sudiarto Raharjo"
EMAIL="willysr@slackware-id.org"

View File

@ -0,0 +1,230 @@
#!/usr/bin/perl -wT
# Copyright 2009 Kagan D. MacTane
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require 5.004;
use strict;
use Sys::Syslog;
# -------------------------------------------------------------------
# This is the whitelist. Any IP address that's listed in this array will
# never be blocked.
my @never_block_these = qw(127.0.0.1 192.168.1.1);
# Where should SSHblock track which addresses it's blocked? This will be
# a text file with tab-delimited fields:
# IP address - # of times blocked # timestamp of last block
my $history_file = '/etc/ssh/block-history';
# If email notifications
# Set to an empty string to suppress email notifications.
my $send_email = '';
# Only send email if IP has been blocked at least this many times.
# E.g., at $email_level = 3, email will only be sent if an IP is
# blocked for the 3rd (or greater) time.
my $email_level = 2;
# This is just to keep SSHblock from having to run `hostname` every time
# it wants to notify you that it's blocked something. This text is only
# used in the notification email, and can be safely altered.
my $myhostname = 'localhost';
my $Syslog_Level = 'info';
my $Syslog_Facility = 'user';
my $Syslog_Options = '';
my $VERSION = 0.5;
# -------------------------------------------------------------------
$ENV{PATH} = '/sbin:/usr/sbin:/usr/bin:/bin';
unless (scalar @ARGV) {
LogMessage("Called with no arguments; aborting.");
print "Usage: $0 ip_addr\n\"perldoc $0\" for full man page\n";
exit 1;
}
my $ip_addr = shift;
# Ensure we were passed a valid IP address as first argument
if ($ip_addr =~ /^((\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3}))$/) {
$ip_addr = $1;
} else {
LogMessage("first arg not IP address: $ip_addr");
exit 1;
}
if ($2 > 255 || $3 > 255 || $4 > 255 || $5 > 255) {
exit 1;
}
# Abort if you're not running as root
if ($>) {
print "Only root can run this program.\n";
exit 1;
}
# And don't waste processor cycles if the IP's already blocked
exit 0 if (is_blocked($ip_addr));
if (grep { $_ eq $ip_addr } @never_block_these) {
LogMessage("Not bothering to block whitelisted IP $ip_addr");
exit 3;
}
# block command:
`iptables -A INPUT -p tcp -s $ip_addr --dport 22 --syn -j DROP`;
if ($?) {
LogMessage("Failed to block IP $ip_addr; error $?: $!");
exit $?;
}
# Assuming that succeeded, add a record to the history file.
my @history;
open FH, "$history_file" || exit 7;
@history = <FH>;
close FH;
my ($prev) = grep /^$ip_addr\s/, @history;
my $how_many;
if ($prev) {
my @prev = split /\s+/, $prev;
$prev[1]++;
splice(@prev, 2, 1, time());
map { if ($_ =~ /^$ip_addr\s/) { $_ = join("\t", @prev)."\n"; } } @history;
$how_many = ordinal($prev[1]);
} else {
push(@history, join("\t", $ip_addr, 1, time()), "\n");
$how_many = ordinal(1);
}
open FH, ">$history_file" || exit 8;
print FH @history;
close FH;
LogMessage("Blocked IP $ip_addr for $how_many time");
my $num = $how_many;
$num =~ s/\D//g;
if ($send_email && $num >= $email_level) {
open MAIL, "|[[SENDMAIL_PATH]] -t";
print MAIL "From: sshblock <noreply\@$myhostname>\nTo: $send_email\nSubject: SSH Block: $ip_addr\n\nBlocked IP address $ip_addr for $how_many time.\n\n";
close MAIL;
}
exit 0;
sub is_blocked {
my $ip_addr = shift;
return grep /^$ip_addr/, split /\n/, `iptables -nL | grep DROP | grep 'dpt:22' | grep '0x17/0x02' | awk '{print \$4}'`;
}
sub ordinal {
my $num = shift;
if (length($num) > 1 && substr($num, -2, 1) == 1) {
return $num . 'th';
}
if (substr($num, -1) == 1) {
return $num . 'st';
} elsif (substr($num, -1) == 2) {
return $num . 'nd';
} elsif (substr($num, -1) == 3) {
return $num . 'rd';
} else {
return $num . 'th';
}
}
sub LogMessage {
my $format = shift;
openlog('sshblock', $Syslog_Options, $Syslog_Facility);
syslog($Syslog_Level, $format, @_);
closelog();
}
__END__
=head1 NAME
sshblock.pl - SSH dictionary attack blocker
=head1 SYNOPSIS
B<sshblock.pl> I<ip_address>
=head1 DESCRIPTION
This is part of the SSHblock system; the B<sshblock.pl> executable is responsible for blocking IP addresses from access to port 22. B<sshblock.pl> does this by adding a firewall rule to B<iptables>, which must be present on the system. Because of this, SSHblock must be run as root.
B<sshblock.pl> only blocks addresses; unblocking them is the responsibility of B<sshunblock.pl>, which should be run as an hourly cron(8) job.
=head1 INVOCATION
B<sshblock.pl> is intended to be called by swatch(1) or a similar automated process. You I<can> call it from the command line, passing it a single IP address to block, and this won't actualyl cause any problems, but it will only take one argument per invocation.
By default, SSHblock logs its activity to syslog(8), using the "user" facility at level "info".
=head1 CONFIGURATION
SSHblock can be configured by changing the following options in the program's source code:
=over
=item B<@never_block_these>
This array holds SSHblock's whitelist. Any IP address found in this array will never be blocked.
=item B<$history_file>
Where SSHblock should store its history file. This file keeps a record of all IP addresses SSHblock has ever blocked, one per line. Each line consists of three tab-delimited fields: the IP address; the total number of times it's been blocked; and the timestamp it was last blocked at.
=item B<$email_level>
If this number is nonzero, then SSHblock will send an email to the address specified in B<$send_email> whenever an address is blocked for the Nth or greater time. For example, if $email_level is 3, SSHblock will remain silent when it blocks an address for the 1st or 2nd time, but send email on the 3rd time.
=back
=head1 FILES
=over
=item F</etc/ssh/block-history>
=back
=head1 BUGS
Please let me know if you find any.
=head1 AUTHOR
Kagan D. MacTane (kai@mactane.org)
=head1 SEE ALSO
sshunblock(8), iptables(8), L<http://sourceforge.net/projects/swatch/>
=cut

View File

@ -0,0 +1,8 @@
watchfor /Failed password for invalid user \w+ from ([\d\.]+) port/
exec "/usr/sbin/sshblock.pl $1"
threshold track_by=$1, type=threshold, count=3, seconds=90
watchfor /Failed password for root from ([\d\.]+) port/
exec "/usr/sbin/sshblock.pl $1"
threshold track_by=$1, type=threshold, count=3, seconds=30

View File

@ -0,0 +1,179 @@
#!/usr/bin/perl -wT
# Copyright 2009 Kagan D. MacTane
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require 5.004;
use strict;
use Sys::Syslog;
# -------------------------------------------------------------------
# This option *MUST* match the corresponding value in sshblock.pl!
my $history_file = '/etc/ssh/block-history';
# Blocking duration formula: If blocking for the Nth time, and
# b = $base and m = $mult, then block for:
#
# T = m * ( b ^ (N-1) )
#
# Time T is expressed in hours.
my $base = 4;
my $mult = 3;
my $Syslog_Level = 'info';
my $Syslog_Facility = 'user';
my $Syslog_Options = '';
my $VERSION = 0.5;
$ENV{'PATH'} = '/bin:/usr/bin:/usr/sbin:/sbin';
# -------------------------------------------------------------------
# Abort if you're not running as root
if ($>) {
print "Only root can run this program.\n";
exit 1;
}
my $now = time();
my @history;
open FH, "$history_file" || exit 7;
@history = <FH>;
close FH;
my @input_chain = `iptables -nL INPUT | tail +3`;
# Your iptables output needs to look like:
# DROP tcp -- 1.2.3.4 0.0.0.0/0 tcp dpt:22 flags:0x17/0x02
if (scalar @input_chain > 0) {
LogMessage("Checking ".scalar @input_chain." blocked IPs against ".scalar @history." block-history entries.");
}
foreach my $item (@input_chain) {
my @stats = split(/\s+/, $item);
next unless ($stats[0] eq 'DROP');
next unless ($stats[1] eq 'tcp');
next unless ($stats[6] eq 'dpt:22');
next unless ($stats[7] eq 'flags:0x17/0x02');
my $curr_ip = $stats[3];
if ($curr_ip =~ /^(\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b)$/) {
$curr_ip = $1;
} else {
LogMessage("Invalid IP address in output from iptables?!?");
next;
}
for ( my $i = 0; $i < scalar @history; $i++ ) {
my $hist_ip = (split(/\t/, $history[$i]))[0];
if ((split(/\t/, $history[$i]))[0] eq $curr_ip) {
my ($times_blocked, $blocked_since) = (split(/\t/, $history[$i]))[1,2];
my $duration = $now - $blocked_since;
$duration /= 3600;
$duration = sprintf("%.2f", $duration);
my $block_for = $mult * ($base ** ($times_blocked - 1));
if ($duration > $block_for) {
`iptables -D INPUT -p tcp -s $curr_ip --dport 22 --syn -j DROP`;
if ($?) {
LogMessage("Couldn't unblock IP $curr_ip (now blocked for $duration hours)! Error $?: $!");
} else {
LogMessage("Unblocked IP $curr_ip after $duration hours.");
}
}
}
}
}
exit 0;
sub is_blocked {
my $ip_addr = shift;
return grep /^$ip_addr/, split /\n/, `iptables -nL | grep DROP | grep 'dpt:22' | grep '0x17/0x02' | awk '{print \$4}'`;
}
sub LogMessage {
my $format = shift;
openlog('sshblock', $Syslog_Options, $Syslog_Facility);
syslog($Syslog_Level, $format, @_);
closelog();
}
__END__
=head1 NAME
sshunblock.pl - SSH dictionary attack (un)blocker
=head1 SYNOPSIS
B<sshblock.pl>
=head1 DESCRIPTION
This is part of the SSHblock system; the B<sshunblock.pl> executable is responsible for unblocking blocked IP addresses after a suitable length of time has passed. It does this by removing the B<iptables> firewall rules created by B<sshblock.pl>. In order to use iptables, sshunblock.pl must be run as root.
=head1 INVOCATION
B<sshunblock.pl> is intended to be called as an hourly cron(8) job. Calling it more or less frequently will not interfere with its operation.
By default, SSHblock logs its activity to syslog(8), using the "user" facility at level "info".
=head1 CONFIGURATION
F<sshunblock.pl> can be configured by changing the following options in the program's source code.
=over
=item B<$history_file>
Note that B<this option MUST match the value in sshblock.pl!> This is where SSHblock should store its history file. This file keeps a record of all IP addresses SSHblock has ever blocked, one per line. Each line consists of three tab-delimited fields: the IP address; the total number of times it's been blocked; and the timestamp it was last blocked at.
=item B<$base, $mult>
These control the behavior of SSHblock's exponential increase algorithm. By tweaking these, you can make SSHblock block attacking IP addresses for longer or shorter periods of time.
=back
=head1 FILES
=over
=item F</etc/ssh/block-history>
=back
=head1 BUGS
Please let me know if you find any.
=head1 AUTHOR
Kagan D. MacTane (kai@mactane.org)
=head1 SEE ALSO
sshblock(8), iptables(8), L<http://sourceforge.net/projects/swatch/>
=cut