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

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


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

そして現在は、あきらめてログを精査して出禁リストを作る実験中です



掲示板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をサブネット単位にまとめるツール をかけてリスト化したりしましたが、
現在は、腹をくくってアクセスログを全部見て、不正アクセスっぽいIPアドレスの一覧を作成することで対応しています。

参考まで、
作成したIPアドレスのリストを 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

効果のほどは・・・
(随分昔に削除した掲示板へのアクセスをようやく止めることに成功はしたのですが)
結局、これをやるとIPアドレスは延々に増え続けるので、
不正アクセスの最終アクセス日とか、情報の蓄積と更新が必要なんですよね...

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

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

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

ただ、間違いなく、 rootログインは禁止しないといけないことは、分かりました。

# 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など)の集計

ログがフリーフォーマットなので、手探りで拾っています。
とりあえず、これで暫く様子を見た結果
毎日不正アクセスがきている、ログを精査しないとダメだ
ということになって、ようやく、もう少し真面目にログを追うことに成ります。

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

攻撃自体は公開しているサーバなら必ず来るものなので、
脆弱性を塞いだ上で放置というのも対処の1つかもしれませんが...

sshについては、lastbコマンドとか見れば分かるように
だいたい狙われるIDは決まっているし、
そもそもポート番号を22以外にすれば良いと思います。
攻撃元はほぼ日替わりですね。

継続的な不正アクセスが多いのは、むしろメールサーバの方でした。
これは放っておくと、30日間連続で(止めない限り永遠に?)同じIPから来たりします。
しょーだんとか、せんさすとか、常連が居ます。
第三者不正中継ができそうなサーバとか見つけたら、
その情報を売ったりするんですかね、たぶん...
このアクセスを止めるとなると、1つ止めても次のIPアドレスで来るので、
1つ2つではなく数十以上のアドレスを止めるつもりで、
すこし真面目にログを精査していかないと きびしいかもしれません(その2へ続く

Webサイトは、スパムリファラーは、ほぼ毎日、固定客です。
...それだけしつこいということは、利益が出ているのかな、あれって?
あと、当時は一時的なのものかと思っていたら、今なお多い、WordPressへの攻撃。
たぶん人気が有る分、脆弱性を塞がないサイトも多いということなんでしょうね。


Webサイトへの攻撃の亜種として、調査目的?のクローラー。
以前海外の大学から、あからさまな攻撃が、
「これは調査です」ってURLだかUser-Agentだかに書いて上で来たことがありますが、
調査だろうが何だろうが、
あれでサーバがダウンとかして企業とかが損害を被ったら、...いや、被らなくてもか?
とにかく、裁判で争ったりするんですよね...大丈夫なのだろうか?

今は別のクローラーが、
通常アクセスに混じって編集やら削除やらを試みてきて驚いています。
一応、故意の攻撃ではないと仮定した上で、推測すると、
原理自体はふつうにSNSにブラウザクラッシャー埋め込むのと同じで、
クローラーでリンクを辿る過程で
どこかのサイトなり、WikiやSNSなりで、不正なURLリンクが書かれたコンテンツへ辿り着いて、
それをクローラーが無造作に取り込んで、そのURLへアクセスする、
結果、クローラーが攻撃に加担する、という流れなのではないでしょうか?

ちなみに、その国内の某クローラー、
Webで検索すると、何かあればご連絡下さい的なことが書いてありましたが、
情報が古いのか、そこで説明されている内容と実際に来るIPアドレスが一致していませんでした。
(でもIPアドレス自体は、逆引きするとその大学のドメインです)
おそらく既に管理されていないのか、本当に攻撃してきているか... [worried]
他にも、何故か大文字を小文字に変換してアクセスしてくるクローラーとか、色々あって、
クローラーって、 実装するには難易度が高いシステムなのかもしれませんね。*3
もちろん、迷惑とか一切顧みないなら、簡単に作れそうですけど...
...私もそこへ、「調査」し返せば良いんですかね?
攻げ調査自体は簡単だけど、
みんなそれを分かっていて自重しているのだと思っていますが...



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

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


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

  最終更新のRSS