syslog-ng で sshd への辞書攻撃を遮断する

毎朝届くなっがい logwatch の出力。
辞書攻撃の痕跡10件ぐらいならまだしも、1000件2000件と食らうとスクロールさせるのもうんざりです。

うんざりするのにもうんざりしたので、syslog-ng のフィルタを使い、ssh に辞書攻撃を仕掛けてきた IP を iptables で遮断することにしたので、それの how to メモ。
環境は CentOS4 でやっています。他の環境ではログ形式の違いなどでスクリプト、フィルタ文字列の修正が必要かもしれません。

syslog-ng のインストール方法は別のところにお任せします。


指針は

  • ポート22への新規接続をフィルタ用チェーンに流すように変更する。
  • syslog-ng のフィルタに引っかかった IP アドレスからのパケットを DROP するようにルールをフィルタチェーンに追加していく。

といった感じ。

iptables の設定

inbound の処理が INPUT しかない状態で、全パケットを受け付けている(ACCEPT)状態を想定してます。
環境によって状態が変わるので、適宜調整してやってください。


FILTER チェーンを作成する。

# iptables -N FILTER

チェーンの最後まで到達したらACCEPTするようにする。

# iptables -A FILTER -j ACCEPT

TCP のポート 22 への syn パケットは FILTER に流すようにする。

# iptables -A INPUT -p tcp -m state --state NEW --dport 22 -j FILTER

変更した内容を保存する。iptables.conf は環境によって読み替えてくださいな。
Redhat系なら /etc/sysconfig/iptables になります。

# iptables-save > iptables.conf

遮断スクリプト

以下の2スクリプトを合体させた物です。
http://www.aconus.com/~oyaji/security/filter_cgi.htm
http://www.aconus.com/~oyaji/security/mail_cgi.htm

syslog-ng から渡された文字列から IP アドレスを取り出し、規程回数失敗していたら FILTER チェーンの先頭に DROP ルールを追加して、メールを投げるようになっています。

/root に以下の内容で syslog-ng-filter.cgi を作成します。owner は root:root。 chmod +x で実行権限を付与しておきます。

#!/usr/bin/perl
#

# メールのタイトル
$subject = 'Syslog-ng alert';

# 送信先メールアドレス
$mailto = '送信先メールアドレス';

# 送信元メールアドレス
$mailfrom = 'root';

# sendmailパス
$sendmail = '/usr/sbin/sendmail';

use Jcode;

# ログインミス回数の記録ディレクトリ
$dir = '/root/syslog-ng-filter/';

# エラー回数(2だと2回ミスすると遮断される)
$limit = 2;

# チェーンの名称
$chain="FILTER";

# iptablesのパス
$iptables="/sbin/iptables";

###############################################
while ($data = <STDIN>) {
    # マッチ処理してIPアドレスを抽出する
    # ログ形式が違う場合はここを修正
    if ($data =~ /sshd\[\d+\]\: Invalid user .+ from (::ffff:)?(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/) {
        $attacker_ip = $2;

        # 特定アドレスは除外
        if ($attacker_ip eq "127\.0\.0\.1") {exit;}
        # 除外アドレスを追加する場合はここに追加

        $attack_cnt = $dir . "$attacker_ip.cnt";
        $cnt = 1;
        if(-f "$attack_cnt"){
            open(TIN, "$attack_cnt");
            flock(TIN, 1);
            truncate(TIN, 0);
            while($cc = <TIN>){
                $cc =~ s/\s+$//;
                $reg = system("$iptables --list $chain -n | grep -q $attacker_ip");
                if($reg != 0){
                    if($cc >= $limit){
                        system("$iptables -I $chain 1 -p tcp --dport 22 -s $attacker_ip -j DROP");
                        close(TIN);

                        ### メール送信 ###
                        $message = "Add blocked IP : ".$attacker_ip;

                        Jcode::convert(\$subject,jis);
                        Jcode::convert(\$message,jis);

                        open(MAIL,"| $sendmail -t");
                        print MAIL "To: $mailto\n";
                        print MAIL "From: $mailfrom\n";
                        print MAIL "Subject: $subject\n";
                        print MAIL "MIME-Version: 1.0\n";
                        print MAIL "Content-type: text/plain; charset=ISO-2022-JP\n";
                        print MAIL "Content-Transfer-Encoding: 7bit\n";
                        print MAIL "$message\n";
                        print MAIL "$message\n";
                        close(MAIL);

                        exit;
                    }
                }
                $cc++;
                $cnt = $cc;
            }
            close(TIN);
        }
        open(TTCNT, "> $attack_cnt");
        print(TTCNT "$cnt\n");
        close(TTCNT);
    }
}
exit;

ログインミス回数の記録ファイルを置くディレクトリを作成する。

 # mkdir syslog-ng-filter


スクリプトの動作テスト。
まず、フィルタに引っかけるログを適当に1行とってきてファイルにしておきます。

 # vi testlog

内容はこんな感じ

 Sep 24 07:56:30 butterbeans sshd[53123]: Invalid user daniel from ::ffff:82.173.127.155

syslog-ng のフィルタ設定

syslog-ng に sshd への接続失敗を引っかけるフィルタを追加します。

/etc/syslog-ne/syslog-ng.conf に以下を追加します。
filter の行の match 内に引っかかったログが上のスクリプトに渡されることになるので、ここも環境に応じて変更が必要になると思います。

destination d_auth_filter { program("/root/syslog-ng-filter.cgi"); };
filter f_filter_failed_login { facility(authpriv) and match("Invalid user"); };
log { source(s_sys); filter(f_filter_failed_login); destination(d_auth_filter);};

最後に syslog-ng 再起動。

 # /etc/init.d/syslog-ng restart


動作確認には適当に接続に失敗してやればいいのですが、下手すると自分が遮断されてしまうので注意してください。
(別コネクションかコンソールを確保しておきましょう)