玄箱で SSH 辞書攻撃対策 [Linux]
最近 SSH の辞書攻撃 (brute force attack) を頻繁に受けるとの話を見かけ、我が家の玄箱 (Vine 2.6) のログファイル /var/log/secure を調べてみたらやはり来ていた。(IPアドレスは伏せ字)
Aug 28 05:16:08 kurobox sshd[27763]: Illegal user optic from xxx.xxx.xxx.xxx Aug 28 05:16:10 kurobox sshd[27765]: Illegal user service from xxx.xxx.xxx.xxx Aug 28 05:16:11 kurobox sshd[27767]: Illegal user admin from xxx.xxx.xxx.xxx Aug 28 05:16:12 kurobox sshd[27769]: Illegal user danielle from xxx.xxx.xxx.xxx Aug 28 05:16:14 kurobox sshd[27771]: Illegal user nexus from xxx.xxx.xxx.xxx Aug 28 05:16:15 kurobox sshd[27773]: Illegal user arthur from xxx.xxx.xxx.xxx Aug 28 05:16:16 kurobox sshd[27775]: Illegal user fred from xxx.xxx.xxx.xxx Aug 28 05:16:19 kurobox sshd[27777]: Illegal user greg from xxx.xxx.xxx.xxx (更に続く)
一応 /etc/ssh/sshd_config で接続許可ユーザを限定し、更に認証方法を鍵交換方式だけにしてあるので侵入される可能性はほとんどゼロなのだが、攻撃のせいでログファイルがどんどん膨れ上がるのはちと気に入らない。
対策法を検索してみると iptables の recent モジュールというのを使えば良いらしいことが分かった[1]。しかし recent に対応している kernel は 2.4.22 以降で、玄箱の kernel-2.4.17 では未対応。残念。
もう少し検索してみると、sshd への接続要求が辞書攻撃しているアドレスかどうかをログファイルから調べ、該当すれば iptables の REJECT ルールを生成するスクリプトを tcpwrapper から起動する方法[2]が紹介されていた。ブロックと同時に at で規定時間後に解除コマンドが実行されるようにもしている。お〜、賢い。これは使えそうかも。
上の例での sh スクリプトを参考に、次のような perl スクリプトを書いてみる。攻撃アドレスの判別方法としては、perl の文字列処理能力を生かして recent モジュールのように設定時間内での不正アクセス回数を見るよう変更。
#!/usr/bin/perl use Time::Local; use Sys::Syslog qw(:DEFAULT setlogsock); setlogsock('unix'); ($IPADDR,$INTERVAL,$MAXHITS) = @ARGV; die if($IPADDR !~ /\d+\.\d+\.\d+\.\d+/); # Site specific settings $DEBUG = 0; $LOGFILE = "/var/log/secure"; $IPTABLES = "/sbin/iptables"; $SSHPORT = 22; $SYSLOGKEY = 'ssh-block'; $AT = "/usr/bin/at"; # Default values $INTERVAL = 5 if ($INTERVAL <= 0); # minutes $MAXHITS = 5 if ($MAXHITS <= 0); # Initialization %MNAME = ('Jan',0,'Feb',1,'Mar',2,'Apr',3,'May',4,'Jun',5, 'Jul',6,'Aug',7,'Sep',8,'Oct',9,'Nov',10,'Dec',11); $YEAR = (localtime())[5] + 1900; $MONTH = (localtime())[4]; # Read sshd output from logfile. open(FILE,"$LOGFILE") or die("$LOGFILE:$!"); while(<FILE>) { if(/sshd¥[¥d+¥]/) { chomp; push(@LOGS,$_); } } close FILE; # Check if our ipaddress is an attacker or not. $HITS=0; for ($i=$#LOGS;0<=$i;$i--) { $_ = $LOGS[$i]; my $logtime; # Support for new log expression (Illegal->Invalid) # in later versions of OpenSSH [2007/05/24] if ( /(Illegal|Invalid) user ¥w+ from $IPADDR/ or /Did not receive identification string from $IPADDR/ ) { if ( /^(¥w{3})\s+(¥d+)\s+(¥d¥d):(¥d¥d):(¥d¥d)/ ) { local($mname,$day,$hour,$min,$sec) = ($1,$2,$3,$4,$5); my $mon = $MNAME{$mname}; my $year = $YEAR; if ($MONTH < $mon) { $year -= 1; } $logtime = timelocal($sec,$min,$hour,$day,$mon,$year); } if ( $INTERVAL < (time-$logtime)/60 ) { last; } else { $HITS++; printf STDERR ("[%02d] %s¥n",$HITS,substr($_,0,86)) if $DEBUG; last if $MAXHITS <= $HITS; } } } # Process the actions. if ($MAXHITS <= $HITS) { print STDERR "$IPADDR is an attacker.¥n" if $DEBUG; my $blocked; open(IPTABLES_L,"$IPTABLES -L -n |") or die; while(<IPTABLES_L>) { $blocked = 1 if (/^REJECT¥s+tcp¥s+[^¥s]+¥s+$IPADDR.*dpt:$SSHPORT/); } close IPTABLES_L; if ( $blocked == 1 ) { print STDERR "$IPADDR is already blocked.¥n" if $DEBUG; } else { print STDERR "Blocking $IPADDR by iptables.¥n" if $DEBUG; my $ipt_opts = "INPUT -s $IPADDR -p tcp --dport $SSHPORT -j REJECT"; # Add blocking table system("$IPTABLES -A $ipt_opts"); # Set releasing commad via "at" system("(echo $IPTABLES -D $ipt_opts | $AT now+${INTERVAL}min 2>&1)>/dev/null"); # Log openlog("$SYSLOGKEY",'cons,pid','authpriv'); syslog('info',"Blocked $IPADDR for ${INTERVAL}min"); closelog(); } } else { print STDERR "$IPADDR is clean.¥n" if $DEBUG; }
このスクリプトを /usr/local/sbin/ssh_block_attack.pl として実行権限付きで保存。tcpwrapper によって ssh 接続要求時に実行されるようにするには、[2]の例のように /etc/hosts.allow に次のように記述する。
sshd : ALL : spawn(/usr/local/sbin/ssh_block_attack.pl %c 5) : allow
ローカルから攻撃をしかけて期待通りに機能することを確認、しばらく待つと早速外から攻撃者がやって来た。(IPアドレスは伏せ字)
Aug 29 00:25:08 kurobox sshd[30819]: Did not receive identification string from xxx.xxx.xxx.xxx Aug 29 00:28:03 kurobox sshd[30822]: User root not allowed because not listed in AllowUsers Aug 29 00:28:08 kurobox sshd[30826]: Illegal user fluffy from xxx.xxx.xxx.xxx Aug 29 00:28:09 kurobox sshd[30830]: Illegal user admin from xxx.xxx.xxx.xxx Aug 29 00:28:11 kurobox sshd[30834]: Illegal user test from xxx.xxx.xxx.xxx Aug 29 00:28:13 kurobox sshd[30838]: Illegal user guest from xxx.xxx.xxx.xxx Aug 29 00:28:14 kurobox ssh-block[30844]: Blocked xxx.xxx.xxx.xxx for 5min Aug 29 00:30:14 kurobox sshd[30842]: fatal: Timeout before authentication for xxx.xxx.xxx.xxx
この後はアクセスが途絶え、攻撃者は退散した模様。
う〜む、すっきり。
[1] http://www.musicae.ath.cx/diary/?200506c&to=200506272#200506272
[2] http://search.luky.org/linux-users.a/msg04927.html
冬休みの宿題2:玄箱ディスク換装 [Linux]
玄箱[1]という小さなマシンで運営している自宅メールサーバーのHDDを
換装してみた。
このマシンはとある方[2]が配布されているキットにより Vine Linux 2.6 PPC
で動いており、これに自前の perl script を入れて ADSL モデムの接続状態を
常時監視しながら外部の Dynamic DNS 情報を更新し、SMTP で独自ドメイン
のメールを受け取るようになっている。メールサーバーを自前で運営している
と、様々な会員登録やキャンペーン応募に使用するアドレスを自由に発行・停止
出来るので何かと便利。とは言えあまり安定しない ADSL 接続での運営なので、
大事なメールの受信には向いていない。
それはともかく HDD の話に戻ると、これまでのディスクが IBM の古い
3.5" HDD (DTTA-350640) だったのだが、この音が結構うるさいので、
音の小さい 2.5" HDD にしたいなあと思いつつはや数カ月。やっと時間
が取れたので取り組む事に。
材料:
1) 2.5" HDD (IBM DMCA-21440 1.4GB, じゃんぱら新宿店で¥1,980で購入)
2) 2.5"/3.5" 変換アダプタ (コネクタ・ブラケット一体のもの)
3) Norton Ghost 2002 (ext2, ext3 にも対応のWin用HDDクローニングソフト)
4) 作業用 PC (PC/AT)
まず玄箱を止め、3.5" HDD を取り出して作業用 PC に接続。同じく 2.5" HDD
も接続し、Ghost のブート FD で起動。DIsk to Disk コピーで 3.5" のクローン
を 2.5" に作成。ディスク容量が違うので、はじめにパーティションの大きさを
設定する画面で若干の調整を入れる。クローンニングは10分ほどで完了。
2.5" HDD を玄箱に接続し、まずはカバーを閉めずに動作確認。問題無く起動し、
メインマシンの PowerMac G3 から ssh でログインしてみる。OKそうなので
電源を切り、ふたを閉めて元の設置場所(棚の裏)に戻して稼働再開。
やはり 2.5" にすると格段に静か。これで夜中でも気分良く読書出来そう。
ちなみに玄箱は CPU に PowerPC を使ったマシンだけど、Intel マシンで
ディスクを複製しても特に問題はない模様。
という事で、本日も一件落着。
明日からは家族を連れてお泊まりの為、
冬休みの宿題は本日にてひとまず終了。
[1] http://www.kuroutoshikou.com/products/kuro-box/kuro-boxfset.html
[2] http://www.yamasita.jp/