sshd や popd に対する brute force attack (総当たり攻撃) が原因で、サーバ側の負荷が急増し、ホームページ参照やメール受信に支障を来すケースが散見されています。
sshd は数年前からすでに対策ツールを作成して運用済みでしたが、ここ 1 年程は popd に対する攻撃が急増、しかも容赦のないセッション数を張って load averages が 500 台まで上がることがあります。
攻撃の特徴として、ある IP アドレスから複数のサーバに総当たり攻撃を行うので、負荷の急増に伴いゲートウェイとなる L3 スイッチ自体で遮断していましたが、数があまりにも増えて来ました。
sshd では、認証失敗時に /var/log/auth.log に Falied password … と出力されます。一定回数出力した場合は、ipfw で該当する IP アドレスをすべてのプロトコルで遮断し、一定時間経過後解放しています。
この仕組みを popd (/usr/ports/mail/popd/) にも適用しました。まずは、チェックツール全体を添付、概要は 次回 に説明しましょう。
#
# bfcheck.pl
#
#// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
#// use Module
#// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
use strict;
use vars qw( $opt_p );
use Getopt::Long;
#// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
#// Controller
#// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
#// ----------------------------------------------------------
#// Option Parse
#// ----------------------------------------------------------
$opt_p = 0;
my @basename = split(/\//, $0);
my $basename = pop(@basename);
my $result = GetOptions('p');
#// ----------------------------------------------------------
#// Item Set
#// ----------------------------------------------------------
my $item = {
mkdir => '/bin/mkdir',
cat => '/bin/cat',
chmod => '/bin/chmod',
ps => '/bin/ps',
hostname => '/bin/hostname',
mail => '/usr/bin/mail',
awk => '/usr/bin/awk',
grep => '/usr/bin/grep',
chown => '/usr/sbin/chown',
cp => '/bin/cp',
wc => '/usr/bin/wc',
touch => '/usr/bin/touch',
head => '/usr/bin/head',
tail => '/usr/bin/tail',
host => '/usr/bin/host',
ipfw => '/sbin/ipfw',
target0 => '/var/log/auth.log',
target1 => '/var/log/poplog',
dir0 => '/home/tools/bfcheck.s/',
dir1 => '/home/tools/bfcheck.p/',
mistime => 10,
expire => 1800,
delim => 'last_line',
good => 'pass',
stat0 => '-',
stat1 => 'add',
stat2 => 'expire',
maddr => 'trouble@example.jp',
report => 0,
base => $basename,
file => '/tmp/.' . $basename
};
if ($opt_p == 0) {
$item->{target} = $item->{target0};
$item->{dir} = $item->{dir0};
$item->{file} .= '.s';
}
if ($opt_p == 1) {
$item->{target} = $item->{target1};
$item->{dir} = $item->{dir1};
$item->{file} .= '.p';
}
if ( !-d $item->{dir} ) {
`$item->{mkdir} $item->{dir}`;
`$item->{chmod} 750 $item->{dir}`;
}
#// ----------------------------------------------------------
#// Start
#// ----------------------------------------------------------
#// list
my $check = {};
($item, $check) = mklog($item);
my $last0 = $item->{last_line};
my $last = count($item);
my $tnum = $last;
#// read log
if ($last >= $last0) { $tnum = $last - $last0; }
# $last < $last0 --> log rotated
my $diff = `$item->{tail} -n $tnum $item->{target};`;
foreach (split(/\n/, $diff)) {
my $ipaddr = replace($item, check($_));
$check->{$ipaddr}->{num} += 1;
}
#// data check
my @line = ();
my $deny = '';
my $expire = {};
my $log = '';
foreach (%$check) {
next unless ($check->{$_});
next if ($_ eq $item->{good});
if ($check->{$_}->{add} && $check->{$_}->{add} == 1) {
my $time = date2utime($check->{$_}->{time});
if (($item->{utime} - $time) >= $item->{expire}) {
$expire->{$_} = 1;
$log .= $item->{time} . "\t" . $_ . "\t" .
0 . "\t" . $item->{stat2} . "\n";
}
if ($check->{$_}->{mode}) {
$log .= $item->{time} . "\t" . $_ . "\t" .
0 . "\t" . $item->{stat1} . "\n";
}
} else {
next if ($check->{$_}->{num} == 0);
if ($check->{$_}->{num0} &&
$check->{$_}->{num0} == $check->{$_}->{num}) {
} elsif ($check->{$_}->{num} >= $item->{mistime}) {
my $line = "00101 deny ip from $_ to any";
push(@line, $line);
$deny .= "$line\n";
$log .= $item->{time} . "\t" . $_ . "\t" .
$check->{$_}->{num} . "\t" . $item->{stat1} . "\n";
} else {
$log .= $item->{time} . "\t" . $_ . "\t" .
$check->{$_}->{num} . "\t" . $item->{stat0} . "\n";
}
}
}
if ($deny ne '' && $item->{report} == 1) {
report($item, $deny);
}
#// ipfw list 101
my @list = `$item->{ipfw} list | $item->{grep} 00101`;
foreach (@list) {
chomp;
my @line0 = split(/ /);
my $check = $line0[4];
next if ($expire->{$check} && $expire->{$check} == 1);
push(@line, $_);
}
if (@list) {
`$item->{ipfw} delete 101`;
}
foreach (@line) {
my $line = "$item->{ipfw} add " . $_;
`$line`;
}
#// write log
open READ, "<$item->{log}";
my @log = <READ>;
close READ;
open WRITE, "+>$item->{log}";
unless (@log) {
print WRITE $item->{delim} . "\t\t" . $last . "\n";
} else {
foreach (@log) {
if ($_ =~ /^$item->{delim}/) {
print WRITE $item->{delim} . "\t\t" . $last . "\n";
} else {
print WRITE $_;
}
}
}
print WRITE $log;
close WRITE;
#// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
#// Model
#// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
sub date2utime {
my $date = shift;
$date =~ s/[\/:]/ /g;
$date =~ s/^(\s+)//g; $date =~ s/(\s+)$//g;
$date =~ s/(\s+)/ /g;
my ($year, $mon, $mday, $hour, $min, $sec) = split(/ /, $date);
if (!defined $sec) { $sec = 0; }
$mon -= 1;
eval { timelocal($sec, $min, $hour, $mday, $mon, $year); };
if ($@) { return 0; }
my $utime = timelocal($sec, $min, $hour, $mday, $mon, $year);
return $utime;
}
sub utime2date {
my ($sec, $min, $hours, $mday, $mon, $year, $wday, $yday, $isdst) = localtime($_[0]);
my $years = $year + 1900;
my $this_mon = sprintf("%02d",$mon + 1);
$mday = sprintf("%02d", "$mday");
$hours = sprintf("%02d", "$hours");
$min = sprintf("%02d", "$min");
$sec = sprintf("%02d", "$sec");
my $date = "$years/$this_mon/$mday $hours:$min:$sec";
return $date;
}
sub mklog {
my ($item) = @_;
my $check = {};
my $utime = time();
my $time = utime2date($utime);
my $time0 = substr(utime2date($utime), 0, 10);
my $file = $time0; $file =~ s/\///g;
$item->{log} = $item->{dir} . $file;
$item->{utime} = $utime;
$item->{time} = $time;
$item->{time0} = $time0;
($item, $check) = last_line($item);
return ($item, $check);
}
sub last_line {
my ($item) = @_;
my $check = {};
my $num = 0;
if ( -e $item->{log} ) {
my $str = `$item->{head} -1 $item->{log}`;
chomp $str;
$str =~ s/([\s\t]+)/ /g;
if ($str =~ /$item->{delim} (.+)/) {
$num = $1;
}
($item, $check) = fetch_log($item, $item->{log});
} else {
`$item->{touch} $item->{log}`;
my $time = date2utime("$item->{time0} 00:00:00");
my $time0 = substr(utime2date($time - 1), 0, 10);
my $file0 = $time0; $file0 =~ s/\///g;
my $log = $item->{dir} . $file0;
if ( -e $log ) {
my $str = `$item->{head} -1 $log`;
chomp $str;
$str =~ s/([\s\t]+)/ /g;
if ($str =~ /$item->{delim} (.+)/) {
$num = $1;
}
($item, $check) = fetch_log($item, $log, 1);
}
}
$item->{last_line} = $num;
return ($item, $check);
}
sub fetch_log {
my ($item, $log, $mode) = @_;
unless ($mode) { $mode = 0; }
open READ, "<$log";
my @log = <READ>;
close READ;
my $check = {};
foreach (@log) {
chomp;
next if ($_ =~ /^$item->{delim}/);
my ($time, $ipaddr, $num, $stat) = split(/\t/);
if ($stat eq $item->{stat0}) { #// '-'
$check->{$ipaddr}->{num} = $num;
$check->{$ipaddr}->{num0} = $num;
$check->{$ipaddr}->{add} = 0;
}
if ($stat eq $item->{stat1}) { #// 'add'
$check->{$ipaddr}->{num} = $num;
$check->{$ipaddr}->{add} = 1;
$check->{$ipaddr}->{time} = $time;
$check->{$ipaddr}->{mode} = $mode;
}
if ($stat eq $item->{stat2}) { #// 'expire'
$check->{$ipaddr}->{num} = $num;
$check->{$ipaddr}->{num0} = 0;
$check->{$ipaddr}->{add} = 0;
}
}
return ($item, $check)
}
sub count {
my ($item) = @_;
my $num = `$item->{wc} -l $item->{target} | $item->{awk} '{print \$1}'`;
chomp $num;
return $num;
}
sub check {
my ($line) = @_;
my $mode = 0;
# sample
# Oct 2 12:34:00 hsXX sshd[17625]: Failed password for root from 202.152.209.136
if ($line =~ /Failed /) {
if ($line =~ /(.+)Failed password for invalid user (.+)/) {
$mode = 1;
} elsif ($line =~ /(.+)Failed publickey for (.+)/) {
$mode = 0;
} elsif ($line =~ /(.+)Failed password for (.+)/) {
$mode = 3;
# Oct 2 12:34:00 hsXX sshd[17625]: root from 202.152.209.136
}
if ($mode != 0) { $line = $1 . $2; }
}
return ($mode, $line);
}
sub replace {
my ($item, $mode, $line) = @_;
chomp $line;
return $item->{good} if ($mode == 0);
$line =~ s/(\s+)/ /g;
my @str = split(/ /, $line);
my $ipaddr = $str[7];
return $ipaddr;
}
sub report {
my ($item, $msg) = @_;
my $host = `$item->{hostname}`; chomp $host;
my $time = utime2date(time());
my $file = $item->{file};
my $subj = "Info: ipfw ($host)";
my $body = <<"BODY";
$time => ipfw add
$msg
BODY
open FILE, "+>$file";
print FILE $body;
close FILE;
`$item->{mail} -s "$subj" $item->{maddr} < $file`;
unlink($file);
}
- FreeBSD Disk Array Check Tool
- FreeBSD File System Check Tool
- FreeBSD Brute Force Attack Counter Tool (No. 1)
- FreeBSD Brute Force Attack Counter Tool (No. 2)
- FreeBSD ACPI Thermal Check Tool








