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