So-net無料ブログ作成

玄箱で 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

 

初めてのPerl

初めてのPerl

  • 作者: ランダル・L. シュワルツ, トム フェニックス
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2003/05
  • メディア: 単行本


実用Perlプログラミング

実用Perlプログラミング

  • 作者: スリラム スリニバサン
  • 出版社/メーカー: オライリー・ジャパン
  • 発売日: 1998/11
  • メディア: 単行本


nice!(0)  コメント(1)  トラックバック(1) 
共通テーマ:パソコン・インターネット

nice! 0

コメント 1

fullcover

9月に入ったら急にブロックに失敗しているのを発見。
ログの日付読み取り部にバグがあったので修正。

修正前) if ( /^(¥w{3}) (¥d¥d) (¥d¥d):(¥d¥d):(¥d¥d)/ )
修正後) if ( /^(¥w{3})\s+(¥d+)\s+(¥d¥d):(¥d¥d):(¥d¥d)/ )
by fullcover (2005-09-03 16:22) 

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 1

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。

×

この広告は1年以上新しい記事の更新がないブログに表示されております。