Linux/静的IPフィルタの作成

アクセス失敗履歴をもとにIPアドレスフィルター(2017-03-05)


出入り禁止のIPフィルター、出禁(できん)リストを作るのが今回の趣旨です。*1
フィルターするにあたって、複数まとめて設定したいので
IPアドレスをサブネットに集約することも併せて検討します。

なお、動的なフィルター(アクセスに合わせてfirewallを設定)は目下実験中



掲示板cgi(既に閉鎖済み) へのアクセスが、少し煩わしいというか気持ち悪いのです。

負荷は大したことありません。
(ご参考まで、Apacheだと、インストールしたディレクトリのbinの下に
「ab」 という負荷試験に使えるツールがあるので
 秒間何リクエスト耐えるかの目安にできます。
 単純なサイトなら秒間100程度は問題ないかと)

ただ、負荷よりも、こう・・・気持ち悪いのです。 [worried]

IPで止めていいのか?

本来なら、同一IPアドレスに複数の端末が紐づいているので
(1つのルータに複数機器、IPv4~IPv6変換、そもそもIPアドレスが動的に変わる、など)
正規の通信も巻き添えにしかねないIPアドレスフィルタは強引な手でもあります。
IPとポートで、かつ短い期間でフィルターするべきでしょう。
・・・が、もう勘弁ならん ( ゚Д゚)ノ
みんな巻き添えじゃー!!

そういう製品買おうよ?

さらに、これをマメに確認し更新し続けなければならないので、
結構な工数がかかり続けます。(いわゆるランニングコストがかかります)
もし組織としてやるなら、ちゃんとした製品導入も視野に検討しましょう。
フィルタリングも企業独自のノウハウで、もっと安くて安全、効率的、なはずです。
(ファイアウォール、WAF、IPS、IDS・・・)
・・・が、そんな経済的余裕などない ( ゚Д゚)ノ
大雑把にフィルタしてしまえー!!



同意せざるを得ない立場のみなさん、以下、一緒にがんばりましょう。

フィルタする対象の確認

まずサーバの「入り口」を確認しておきます。

# netstat -a
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
(結果は抜粋しています、もっと多いはずです)
tcp        0      0 0.0.0.0:ssh             0.0.0.0:*               LISTEN
tcp6       0      0 [::]:http               [::]:*                  LISTEN
tcp6       0      0 [::]:ssh                [::]:*                  LISTEN

netstat でポートを探します、
「-a」で上記のポート一覧の表示、
「-na」にすれば、サービス名(ssh)でなくポート番号(22)で表示できます。
出力が長いので、前半だけ拾ってもいいです(「netstat -a | head -20」 とか、20は適当な数字。)

上の例だと ssh(=22)とhttp(=80)の2つがこのサーバへの入り口です。
他には、環境によってはメールやDB接続も受けていると思います。
さらに身に覚えがないものがあれば・・・大至急手を打ってください!バックドアかもしれませんよ!?*2

今回は、Webサイト、sshログイン、メールの3つをターゲットにします。

Webへの不正アクセスの集計と、IPフィルタの作成

特定のキーワードに対して、件数と日付を調べます。
先に、出力イメージから

# ./black_url.sh
0201 -----------------
    4xx   xxx.254.236.32 : 6
    4xx      xx.82.64.21 : 2
    4xx   xxx.25.166.199 : 1
    4xx      xx.82.65.21 : 1
    4xx    xxx.96.249.42 : 1
    spm  xxx.189.235.253 : 1
0202 -----------------
    4xx    xxx.56.47.199 : 1
    4xx   xxx.22.126.147 : 1
(中略)

  xxx.14.194.226 :06days
   xxx.96.249.42 :09days
 xxx.189.235.253 :12days

セキュリティホール狙いの404 NotFound や
特定のファイルへのスパムアクセスの件数を数えています。
こんな感じで、初日(02月01日)は大したことないですが、
後日とんでもない量のスパムがきており・・・今回の出禁リスト作成に思い至りました。
IPアドレスの国名変換 ツールで確認したら、
すべて海外でした。まぁ、国内だとしても拒否対象には変わりませんが。

# cat black_url.sh
#!/bin/sh

# アクセスログの場所を指定し、
# 今月の今日までのログを読み込む
ACLOG=/var/log/httpd/access_log*
mm=`export LANG=C && date +%b` # 日本語になっちゃったからLANG=Cにする
# mm=`date +%m`  # うちの場合は、月を文字(Feb)でなく数値(02)で表示している。
min=1
max=`date +%-d`

# 月日指定する場合は以下をいじる
#mm=Feb
#min=`date +%d --date "2 day ago"`
#max=$min

while [ $min -le $max ]; do
    ii=`printf '%02d' $min`

    echo "$mm$ii -----------------"

    # 一行目の mm ii の部分はアクセスログの書式(日付表示)に合わせて整形する
    #   -> "Feb 01" のような表示か、 "02/01" のような表示か、など
    # 二行目で、アクセス禁止のトリガにするキーワードを選ぶ
    #   -> 以下の例は2017/02 で比較的多かったもの。もっと沢山あるかも。
    egrep "$mm $ii" $ACLOG | \
    egrep -i "/mysql/admin|/phpmyadmin|/setup.php/|/wp-admin/|/upbbs.cgi" |\
    awk '
    {
        # アクセスログに合わせて$数値は変える。以下の場合は、
        #   $2 番目がIPアドレスで、
        #   $4 番目がレスポンスコード の場合

        if($0 ~ /upbbs.cgi/ && $4 ~ /[45][0-9][0-9]/){
            # ここだけ別カウント。
            # 今は存在しない掲示板。これは別カウントして確実に止める。
            host_list_spam[$2]++
        }
        else if($4 ~ /4[0-9][0-9]/){
            host_list_400[$2]++
        }
        else if($4 ~ /5[0-9][0-9]/){
            host_list_500[$2]++
        }
    }
    END{
        # 配列 host_list の
        # 添え字i は IPアドレス、中身はアクセス数 になる
        for (i in host_list_400) {
            printf("    4xx %16s : %d\n", i, host_list_400[i]);
        }
        for (i in host_list_500) {
            printf("    5xx %16s : %d\n", i, host_list_500[i]);
        }
        for (i in host_list_spam) {
            printf("    spm %16s : %d\n", i, host_list_spam[i]);
        }
    }' | tee $0.tmp

    awk '{print $2;}' $0.tmp | sort -u > ng_list$mm$ii.txt
    \rm $0.tmp
    min=`expr $min + 1 `
done

# 過去何回アクセスしてきているかを調べる
awk '{
     a[$1]++;
  } END{
     for (i in a){
         printf("%16s :%02ddays\n", i, a[i]);
     }
  }' ng_list$mm* |\
 sort -k2 |\
 tee ng_list_days.txt

作成された ng_list*.txt で、再犯率に応じてリストをピックアップしましょう。

なお、今回は、
個別にフィルタしても埒が明かないと思ったので、
複数IPをサブネット単位にまとめるツール をかけてリスト化しています。

こんな、black_hosts_1702.txt を作りました。
xxx.113.148.161
xxx.56.161.248
xxx.25.166.199/32
xxx.185.200.128/26
xxx.185.200.192/26
(以下省略)

以下では、これらのリストを firewalld に設定しています。
iptables の方は firewall-cmd の部分を読み替えてください。
しくじると接続できなくなるので慎重にやりましょう。

まず、以前入れたものと総入れ替えすることにしたので、
一旦クリアしました。

# for ff in `firewall-cmd --list-sources --zone=drop`; do
 echo $ff------
 firewall-cmd --zone=drop --permanent --remove-source=$ff
 done
# firewall-cmd --reload
# firewall-cmd --list-sources --zone=drop

今回のリストをすべて入れます。

# for ff in `cat black_hosts_1702.txt`; do
 echo $ff-------
 firewall-cmd --zone=drop --permanent --add-source=$ff
 done
# firewall-cmd --reload
# firewall-cmd --list-sources --zone=drop

効果のほどは、そうですね・・・
今後どうなるかは様子見ですが、bbs荒らしは一旦止まりました。すばらしい!
定番の WordPress や DB 脆弱性探しは毎回メンツが変わるので、効果薄。

以下は、
同じような手順で、ログイン失敗履歴とメール履歴も追ってみます。

ログイン失敗履歴(btmp/lastb)の集計

以下、ログインIDと、IPアドレスごとに失敗数を数えています。
(かなり長い出力になると思うので、ご注意を)
なお、2017年2月のみの所感ですが、うちの環境の場合、
大抵のホストは3日以内に撤退していたので、
アドレスフィルタの効果は薄そうです。

# cat black_login.sh
#!/bin/sh

# btmpの実行結果から
# 今月の今日までのログを読み込む
mm=`export LANG=C && date +%b`
min=1
max=`date +%-d`

# 月日指定する場合は以下をいじる。
#mm="Feb"
#min=1
#max=28

while [ $min -le $max ]; do
    ii=`printf '%2d' $min`

    echo "$mm$ii -----------------"

    # ローテ済みのログを見る場合は、 -f ファイル名で指定する。
    #lastb -i -f /var/log/btmp-* |\
    lastb -i |\
    egrep "$mm $ii" | \
    awk '
    {
        if($0 != "" && $1 !~ /btmp/){ # 空行とログの最後の記述は除外。
            host_list[$3]++
            name_list[$1]++
        }
    }
    END{
        # 配列 host_list, name_list の
        # 添え字i は IPアドレス/ログイン名、中身はアクセス数 になる
        for (i in host_list) {
            printf("   %16s : %d\n", i, host_list[i]);
        }
        for (i in name_list) {
            printf("   name  %10s : %d\n", i, name_list[i]);
        }
    }' | tee btmp_list$mm$min.txt

    min=`expr $min + 1 `
done

# 過去何回アクセスしてきているかを調べる
echo "----- total count [login]"
awk '{
     if($1 ~ /name/){
        a[$2] += $4;
     }
  } END{
     for (i in a){
         printf("%16s %4d\n", i, a[i]);
     }
  }' btmp_list$mm* |\
 sort -k2

echo "----- total count [host]"
# 統計の出力
awk '{
     if($1 ~ /name/){
        # nothing
     } else {
        a[$1]++;
     }
  } END{
     for (i in a){
         printf("%16s :%02ddays\n", i, a[i]);
     }
  }' btmp_list$mm* |\
 sort -k2 |\
 tee btmp_list_days.txt

メール履歴(postfixなど)の集計

ログがフリーフォーマットなので、手探りで拾っています。
認証失敗以外のエラーはとりあえず目視確認して、どうするか決める予定。
なお、2017年2月のみの所感ですが、うちの環境の場合、
こちらはログイン失敗と違って、複数日に渡る連続失敗があるので
一旦フィルタをかけて様子を見てみます。
(以下の bad_mail_days.txt からピックアップして、1列目をfirewallに設定する)

# cat black_mail.sh
#!/bin/sh

# アクセスログの場所を指定し、
# 今月の今日までのログを読み込む
MLLOG=/var/log/maillog*
mm=`export LANG=C && date +%b` # 日本語になっちゃったからLANG=Cにする
min=1
max=`date +%-d`

# 月日指定する場合は以下をいじる
#mm=Feb
#min=`date +%-d --date "2 day ago"`
min=1
#max=28

while [ $min -le $max ]; do
    ii=`printf '%2d' $min`

    echo "$mm$ii -----------------"

    # 一行目の mm ii の部分はアクセスログの書式に合わせて整形する
    # 二行目で、アクセス禁止のトリガにするキーワードを選ぶ
    egrep "^$mm $ii" $MLLOG | \
    egrep -i "fail|err" |\
    awk '
    {

        if($0 ~ /auth.+fail/ && match($0, /\[[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\]/) ){
            # 認証失敗のログから(auth.+fail)
            # matchでIPアドレスを抜き出す ([IPアドレス])
            host_list_auth[substr($0, RSTART+1, RLENGTH-2)]++
        }
        else if($0 ~ /fail/){
            # IPアドレスが拾えない場合は行番号NRで文面を拾う。
            msg_list_fail[NR]=$0
        }
        else {
            # まだ想定書式がないので、とりあえず行番号NRで文面を拾う。
            msg_list_err[NR]=$0
        }
    }
    END{
        
        for (i in host_list_auth) {
            printf("   badIP %16s : %d\n", i, host_list_auth[i]);
        }
        for (i in msg_list_fail) {
            printf("    fail %s\n", msg_list_fail[i]);
        }
        for (i in msg_list_err) {
            printf("    err  %s\n", msg_list_err[i]);
        }
    }' | tee $0.tmp

    grep badIP $0.tmp | awk '{print $2;}' | sort -u > bad_mail$mm$ii.txt
    \rm $0.tmp
    min=`expr $min + 1 `
done

# 過去何回アクセスしてきているかを調べる
awk '{
     a[$1]++;
  } END{
     for (i in a){
         printf("%16s :%02ddays\n", i, a[i]);
     }
  }' bad_mail$mm* |\
 sort -k2 |\
 tee bad_mail_days.txt

やってみた感想

以上のような履歴をもとにしたIPアドレスフィルターですが、
あからさまに連日来ているものを一旦黙らせるのは有効、
特定の標的狙い(うちだと、旧掲示板)のアクセスは止めたけど、
それ以外の攻撃は効果が薄い、といった印象です。

静的なフィルタ(今回)と動的なフィルタを併用すると良いでしょう。

あとは、やってみて思ったのですが、
フィルターの効果うんぬんよりも、攻撃の傾向が再確認できたな、と。
(rootログインOffは必須だな~、WordPressが大人気だな~、某国に偏りすぎか~?など)
また、そういった攻撃傾向については
インターネット定点観測
でググれば、そちらも参考になります。警察庁とか。


IoTだの、ネットワーク化が更に進めば、
調査や攻撃目的の通信も今以上に増えて
正規の通信がどんどん埋もれていくのでしょうね。
そうすると今のような世界にオープンな通信から、
限られたネットワークだけの通信
(国内でしか見れないWebサイト、メールサーバとか)
に近々逆戻りするんじゃないかとか・・・考えすぎか?

想像以上に不正アクセスがあったものなので、つい・・・


*1 出禁って一般用語なのか業界用語なのか。デキンになったとかデキンになりたい(=もう行きたくない)とか物騒なプロジェクトもありましたが・・・
*2 初めて見た場合は、想像以上の数のサービスが起動していて戸惑うと思います。とはいえ、少なくとも外(グローバル)にLISTENしているものは正体を確認しないとバックドア(侵入経路)が開いている場合も・・・実際に見てしまったことがあります。 [worried]

  最終更新のRSS