初級情報インフラ管理者の技術メモ

ほぼ自分用の技術メモです。

しつこくSMTP Authを試みてくるIP Addressをiptablesでブロックする

インターネットからアクセスできるSMTPサーバを立て、自ドメイン以外のメールリレーを全て拒否した上でSMTP Authを有効にして管理しているのですが、定期的に外部から何度もSMTP Authを試されてしまっています。

/var/log/maillogには次のようなログが繰り返し記録されています。

Sep 17 15:35:32 hoge postfix/smtpd[22321]: connect from fuga.example.com[xxx.xxx.xxx.xxx]
Sep 17 15:35:34 hoge postfix/smtpd[22321]: warning: SASL authentication failure: Password verification failed
Sep 17 15:35:34 hoge postfix/smtpd[22321]: warning: fuga.example.com[xxx.xxx.xxx.xxx]: SASL PLAIN authentic
ation failed: authentication failure
Sep 17 15:35:37 hoge postfix/smtpd[22321]: warning: fuga.example.com[xxx.xxx.xxx.xxx]: SASL LOGIN authentic
ation failed: authentication failure
Sep 17 15:35:37 hoge postfix/smtpd[22321]: disconnect from fuga.example.com[xxx.xxx.xxx.xxx]

自分しか使わないSMTPサーバなので、複数回SMTP Authに失敗しているイコール不正中継の踏み台にしようとしているのは明らかな状況です。

大変気持ちが悪いので、一定回数以上SMTP Authに失敗しているIP Addressを/var/log/maillogから抽出し、当該IP AddressをiptablesでDROPしてしまおうと考えました。

maillogから一定回数以上認証失敗しているIPを抽出するワンライナー

まずは/var/log/maillogから一定回数以上SMTP Authに失敗しているIP Addressを抽出できるか試してみます。

# cat /var/log/maillog | grep 'authentication failed' | egrep -o -e '\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\]' | sed -e 's/^\[\(.*\)\]$/\1/g' | sort | uniq -c | awk '$1 > 5 {print $2}'

上記ワンライナーで、/var/log/maillogから「authentication failed」の行の中からIPv4 Addressを取り出し、重複カウントした上で重複カウント数が5を超えるIPv4 Addressのみを抽出できました。

一応解説します。

egrep -o -e '\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\]' | sed -e 's/^\[\(.*\)\]$/\1/g'

egrepの-oオプションで、パターンにマッチした文字列のみを取り出します。-eオプションでパターンに正規表現を使ってIPv4 Addressっぽい文字列(数字1桁から3桁までの固まりがピリオド区切りで4つ連続している文字列)を取り出しています。

スマートではないのですが、maillogのauthentication failedログの行には[xxx.xxx.xxx.xxx]という形でIP Addressが書かれているため、まずこの部分を抽出してから更にsedにパイプして正規表現[]を取り除いています。ただ、万一[999.256.1.254].netのようなホスト名がログに記録されていればマッチしてしまうので、もう少しちゃんと書いた方がいいかもしれません。

| sort | uniq -c | awk '$1 > 5 {print $2}'

sortした上でuniqに-cオプションを付けて渡せば、重複している行をまとめて重複したカウント数も教えてくれます。次のような出力が返ります。

      2 xxx.xxx.xxx.xxx
     10 yyy.yyy.yyy.yyy
      6 zzz.zzz.zzz.zzz

一行がカウント数+半角スペース+IP Addressという形で返るため、更にこれをawkに渡してやれば、カウント数とIP Addressを分けて変数に格納できるため、最後はawkでカウント数が5以上であればIP Addressを表示する、というスクリプトになっています。

最終的に一行にカウント数5以上のIP Addressという形式の標準出力を得られました。

これを後述するiptablesから読み込むため、標準出力を任意の場所にテキストファイルとして出力しておきます。

# cat /var/log/maillog | grep 'authentication failed' | egrep -o -e '\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\]' | sed -e 's/^\[\(.*\)\]$/\1/g' | sort | uniq -c | awk '$1 > 5 {print $2}' >> /etc/postfix/sasl_failed_ip.txt

iptablesにファイルから読み込んだIP Addressをブロックする処理

以下は完全におまけです。シェルスクリプトに、上記手順で作成したテキストファイルを一行ずつ読み込んで、iptablesコマンドを生成する処理を作成します。

#!/bin/bash
while read LINE
  do
    iptables -A INPUT -s $LINE -j LOG --log-prefix "[Iptables DROP_SASL_FAIL]" || exit 1
    iptables -A INPUT -s $LINE -j DROP
  done < /etc/postfix/sasl_failed_ip.txt

一応ログプレフィクスを付けてログを出力するようにしています。

最終形のシェルスクリプト

一連の処理をシェルスクリプトにまとめるとこんな感じでしょうか。

#!/bin/bash
# Deny SASL Auth failed ip address
cat /var/log/maillog | grep 'authentication failed' | egrep -o -e '\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\]' | sed -e 's/^\[\(.*\)\]$/\1/g' | sort | uniq -c | awk '$1 > 5 {print $2}' >> /etc/postfix/sasl_failed_ip.txt || exit 1
while read LINE
  do
    iptables -A INPUT -s $LINE -j LOG --log-prefix "[Iptables DROP_SASL_FAIL]" || exit 1
    iptables -A INPUT -s $LINE -j DROP
  done < /etc/postfix/sasl_failed_ip.txt
exit $?

注意点は繰り返しになりますが、万一[999.256.1.254].netのようなホスト名だった場合、iptablesの処理で必ずエラーになるので、正規表現を練り直す必要があるかもしれません。

ちなみに私は自分のSMTPサーバにはIPv6でしかアクセスしないため、IPv4SMTP Authに失敗している時点で問答無用で弾いてしまってもいい状況です。

今のところIPv6でこの手の探索行為は受けている形跡はなさそうですが、もしIPv6の対策も考えなければならなくなったとき、正規表現がめんどくさそう……という気がします。