攻撃マップで一つの国の上に濃い染みができていると、「その国をまるごと禁止する」というルールがおのずと浮かびます。しかしそれではほとんど何も得られません。出口の国を変えるのに攻撃者が費やすのは数分で、禁止に引っかかるのは自分の訪問者と、出張中の自分自身です。さらに悪いことに、単一のクラウドから来ていて目に見えていたクローラーは、遮断されたあと家庭用の回線のアドレスに移り、人間と見分けがつかなくなります。
以下は、同じサーバー上で実際に効果の出る五つの手順です。スキャナーを断ち、速度を抑え、必要なものを切らず、それでも必要なら正しく遮断する。すべて一度の作業で確認して設定できます。
手順 1. 染みを実際に作っているのが誰かを見る
まずリクエストのログを探します。複数のサイトが載ったサーバーでは access.log が空であることが多く、記録はそれぞれのバーチャルホストのログに入ります。
sudo ls -lS /var/log/apache2/*access*.log /var/log/apache2/*requests*.log 2>/dev/null
以降のコマンドでは、この一覧の先頭に来たファイルに置き換えてください。まず一日の上位アドレス、次に最上位のアドレスが何を求めているのかを見ます。
sudo awk '{print $1}' /var/log/apache2/access.log | sort | uniq -c | sort -rn | head -20
sudo grep '^203\.0\.113\.' /var/log/apache2/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -20
二つめのコマンドが肝心の問いに答えます。出力に自分のサイトのさまざまなページが何十も並ぶなら、それは内容の収集役です。無害なので、訪問者として数えなければそれで足ります。/wp-login.php、/.env、/phpmyadmin など、そもそも自分が持っていないものが並ぶなら、それはスキャナーで、手順 2 が受け持ちます。nginx ならログは /var/log/nginx/ にあり、項目は同じです。
手順 2. 持っていないものへのリクエストに対する jail
要求されたパスは偽装できません。アドレスや User-Agent とは違い、それ自体が意図だからです。フィルター /etc/fail2ban/filter.d/web-probes.local:
[Definition]
failregex = ^<HOST> .* "(GET|POST|HEAD) /(wp-login\.php|wp-admin|xmlrpc\.php|\.env|\.git/|phpmyadmin|administrator/)
ignoreregex =
jail /etc/fail2ban/jail.d/web-probes.local:
[web-probes]
enabled = true
port = http,https
filter = web-probes
logpath = /var/log/apache2/*access.log
maxretry = 1
findtime = 1h
bantime = 1d
ここでの maxretry = 1 には根拠があります。他人の管理画面にたまたま行き着くことはないからです。適用したら、フィルターが自分のログの行を実際に捕らえることを確かめます。
sudo fail2ban-regex /var/log/apache2/access.log /etc/fail2ban/filter.d/web-probes.local
sudo systemctl reload fail2ban && sudo fail2ban-client status web-probes
手順 3. 速度を抑える
スキャナーもクローラーも、どの国から来ているかに関係なく速度で正体が割れます。一つのアドレスから毎分三十ページという線引きは、カタログをめくっている人には当たりません。
[http-flood]
enabled = true
port = http,https
filter = apache-overflows
logpath = /var/log/apache2/site_access.log
maxretry = 30
findtime = 60
bantime = 1h
決め手になるのは logpath にどのログを書くかです。そのパターンに管理パネル自体や管理画面のログまで入ってしまうと、自分を締め出すことになります。パネルの一ページはスタイルやスクリプトへのリクエストを十数件連れてくるので、三十件は二度のクリックで溜まります。ですからパネルには jail のパターンに一致しない専用のログファイルを与え、静的ファイルには長めのキャッシュ期間を与えて、ページを移るたびにブラウザーが取り直さないようにします。それでも起きてしまったときの症状は、HTML は届くのに見た目が来ず、ページが固まる状態です。遮断の解除:
sudo fail2ban-client set http-flood unbanip 203.0.113.10
手順 4. 切ってはいけないものを守る
遮断を入れる前に例外を書き出します。そうしないと、最初に引っかかるのがまさに必要なものになります。
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 203.0.113.0/24 66.249.64.0/19
この行に必ず入れるべきものと、その理由:
- 自分のアドレス、あるいは事務所のサブネット。サーバーへのアクセスを失う最も多い原因は攻撃ではなく、自分で書いたルールです。
- 範囲
66.249.64.0/19。Google の検索ロボットは User-Agent で判別できますが、JavaScript を実行する部分はごく普通のChrome/Linuxとして訪れ、名乗りません。「データセンターからの通信を遮断する」式のルールはまさにそこに当たり、その後ページは静かに検索結果から外れます。怪しいアドレスは逆引きで確かめられます。dig -x 66.249.66.1 +shortはgooglebot.comゾーンの名前を返すはずです。 - 支払い通知の送信元アドレス。決済サービスの webhook は AWS や Azure のサーバーから届きます。これらのクラウドを丸ごと閉じると支払いの確認が届かなくなり、それを知るのは、入金されたのに権限がないと利用者から連絡が来たときです。範囲は利用している決済事業者の資料から取ります。
手順 5. それでも遮断が必要なら、国ではなくサブネットを遮断する
単一のネットワークから来て何日も続き、似通ったリクエストを伴う密度の高まりは、一覧をなぞっている一つの実行者です。この場合は範囲を絞り、期限を決めて遮断します。
sudo ufw deny from 198.51.100.0/22 to any
sudo ufw status numbered
二つめのコマンドでルールの番号を控えてください。後で遮断を解くのはまさにその番号です(sudo ufw delete <番号>)。自分との約束として期限を決め——たとえば一か月——解除を予定表に書き込みます。「永久に」と入れたルールは一年もすると誰も手を触れられない一覧に変わります。それらのアドレスがなぜそこに入ったのか、誰も覚えていないからです。
どのサブネットを遮断するかは、手順 1 で得たアドレスから調べられます:whois 198.51.100.17 | grep -iE 'route|cidr|netname'。
この五つの手順のあとで変わること
スキャナーは出自ではなく意図によって断たれ、最初のリクエストで捕まります。クローラーは速度の上限に突き当たります。検索ロボットと支払い通知はこれまでどおり通ります。広いまま残る遮断は一つだけで、それも範囲が絞られ、期限も理由もはっきりしています。
マップ自体がなくなるわけではありませんが、役割は変わります。国を閉じるためではなく、密度の高まりそのものに気づき、一週間前と比べるために使うものになります。そのために要るのは履歴であって、その日一日の切り取りではありません。そうしたマップが遮断の記録や開いているポートの一覧と並んでどう見えるかは、下のデモページで確認できます。