前回、個人サイトが1か月で33万回攻撃されていた話を書きました。ありがたいことに週間トレンド5位まで載せていただきましたが、記事の最後に「fail2ban を入れた」と書いた手前、入れっぱなしで終わらせるのは片手落ちだと思っていました。
1週間ちょっと経ったので、効果を実測しました。そして案の定というか、また自分の置き忘れが1つ出てきました。今回はその2本立てです。
1. 先に数字
| 6月 | 7月(確定) | 8/3時点 | |
|---|---|---|---|
| SSH ログイン失敗 | 335,466 | 337,282 | — |
| 攻撃元 IP 数 | 2,081 | 2,012 | — |
| fail2ban 遮断(累計) | 2 | 23 | 66 |
fail2ban を稼働させたのは 7/27 の 11:46。7月末までの4日間で 2→23(+21件)、そこから8/3までの3日間で 23→66(+43件)。導入直後より、むしろ加速して遮断件数が伸びています。
一方で、攻撃の試行回数そのものは減っていません。335,466 → 337,282 で、誤差の範囲です。
2. 「遮断は効くのに試行回数は減らない」の意味
これは矛盾ではありません。攻撃側のボットは、こちらの応答を見て学習したりしません。遮断されたことにすら気づかず、同じリストを最初から順に撃ち続けます。 遮断される IP が増えるのは、こちらが機械的に弾いている数が増えているだけで、相手が諦めた結果ではないということです。
効果として実際に効いているのは、
- 同一 IP からの連続試行が短時間で止まる(1時間、繰り返せば最大1週間)
- ログの「本当に見るべき異常」が埋もれにくくなる
- パスワード認証自体を切っているので、そもそも遮断以前に侵入経路がない
の3つです。**「攻撃件数を減らす対策」ではなく「1件あたりの被害と、こちらの疲弊を減らす対策」**だと捉えるのが実態に近いと思います。侵入成功は導入前後とも一貫して 0 でした(そもそも公開鍵認証のみなのでパスワードが存在しません)。
fail2ban-client status sshd
Status for the jail: sshd
|- Filter
| |- Currently failed: 3
| `- Total failed: 3411
`- Actions
|- Currently banned: 1
`- Total banned: 66
Total banned は fail2ban が起動してからの累計です。月ごとの増減を見たいときは、前月末の値との差分を取ってください。私はここで一瞬「あれ、月次でリセットされないのか」と戸惑いました。
3. 月次集計を自動化しておいた理由
前回の記事を書いた時点で、/etc/cron.d/ に月次集計スクリプトを仕込んでおきました。理由は単純で、「あとで実測しよう」は高確率でやらないからです。毎月1日の05:00に、前月分の SSH失敗・攻撃元IP・Web探索・fail2ban累計を1行のテキストに固めて永久保存するだけの、ごく簡単なものです。
# 月ラベルは実行日ではなく、lastb の最古レコードから判定する
# (月の途中で手動実行すると前月のデータに当月ラベルが付く事故を防ぐ)
MONTH=$(lastb -F 2>/dev/null | tail -1 | awk '{print $(NF-1)" "$NF}' | ...)
生ログは30日で消しても構わない設計にして、数百バイトの要約だけを残す方針にしています。今回、8/1の自動実行で7月分の「確定値」が初めて手に入りました。
Web探索の「成功」件数が7月は4件と出ましたが、中身を見るとすべて /?url=.env や /?phpinfo=1 のようなクエリ文字列だけの探索でした。nginx の try_files $uri はパス部分しか見ないので、これは単にトップページが 200 を返しているだけです。実際に .env の中身が読まれたわけではありません。 カウントの仕組み上どうしても混ざるので、中身を見ずに「成功4件!」と騒がないよう自分への注意書きです。
4. そしてまた出てきた、自分の置き忘れ
前回の記事の9章で「去年の移行作業で置き忘れた nginx 設定ファイルが公開ディレクトリに残っていた」という話を書きました。今回、fail2ban の効果測定のついでに find の範囲を広げてもう一度確認したところ、同じ移行作業(2025-09-07)の残骸がまだ他にもありました。
find /var/www -maxdepth 6 \( -name '.env' -o -name '.git' -o -name '*.sql' \
-o -name '*.conf' -o -name '*.bak' -o -name '*~' -o -name '*.orig' \)
出てきたのは大きく2種類です。
| 内容 | 中身 |
|---|---|
| 旧サーバーの nginx 設定ファイル 2件 | パスワード等はなし。内部パスが漏れる程度 |
| 移行時の作業ログ一式 | 旧サーバーのグローバルIP、作業機のLAN内部IP、旧ページのバックアップHTML |
特に後者の中に、こんなファイルがありました。
$ cat report/ssh_22.txt
[2025-09-03 16:26:04] Test-NetConnection
ComputerName : 45.xx.xx.xx
RemoteAddress : 45.xx.xx.xx
...
SourceAddress : 192.168.0.8
移行作業時に「サーバーに疎通するか」を確認したコマンドの出力が、そのまま公開ディレクトリに置きっぱなしになっていました。 旧サーバーのIPと、自分の作業機のプライベートIPが平文で読める状態です。実害の大きさで言えば前回の nginx 設定ファイルよりこちらのほうが嫌な部類だと思います。
5. なぜ前回の find で見つからなかったか
ここが今回いちばん書きたかったところです。前回の記事の最後に載せたコマンドは -maxdepth 4 でした。
# 前回(7/27)使ったコマンド
find /var/www -maxdepth 4 ...
今回見つかった .htm.bak のひとつが、こういうパスにありました。
/var/www/html/akio/exhibition/nanyodo/index.htm.bak
# 1 2 3 4 5
/var/www を起点に数えると 深さ5です。-maxdepth 4 の網は、ちょうどこれの1つ手前で止まっていました。同じチェックコマンドを疑わずにコピペし続けていたら、一生見つからなかったということです。
犯人はディレクトリ構造そのものでした。今回見つかった移行残骸は akio/exhibition/ のように、コンテンツによって階層が1〜2段深いところに置かれていました。セキュリティチェックのコマンドも、対象が変われば射程を見直す必要がある、という当たり前だけれど見落としがちな話です。今は -maxdepth 6 に直しています。
6. 消す前にやったこと
前回と同じく、消す前に「誰かに読まれていないか」を先に確認しました。
# 保存されているログの範囲で、該当パスへの外部アクセスがあるか
zgrep -h -E '(server/nginx|report/|\.htm\.bak)' /var/log/nginx/access.log* \
| awk '{print $1, $4, $7}'
結果は、確認のために自分がその場でアクセスした1回だけでした。2025-09-07 に置かれてから約11か月、外部からのアクセスはログに残っている範囲でゼロです。中身を確認し、リンクされていないことも確認したうえで、/root/removed_YYYYMMDD/ へ退避してから公開領域を消しました。
mkdir -p /root/removed_20260803/
mv /var/www/html/server /root/removed_20260803/
mv /var/www/html/report /root/removed_20260803/
nginx -t && systemctl is-active nginx
消してすぐ **「消しただけで壊していないか」**を確認するのも欠かせません。
# 消した側 → 404 になっているか
curl -o /dev/null -w '%{http_code}\n' https://example/report/site-report.json
# => 404
# 既存の正規ページ → 200 のままか
curl -o /dev/null -w '%{http_code}\n' https://example/akio/index.htm
# => 200
7. まとめ
- fail2ban 導入1週間で遮断 66件。攻撃の試行回数自体は減らないが、被害と疲弊は確実に減る
- 月次集計を仕込んでおいたおかげで、「あとで実測する」を実際にやれた
- 「成功件数」の中身は必ず見る。クエリ文字列だけの探索を「侵入成功」と誤読しない
-
前回チェックして安心していたのに、また置き忘れが出てきた。 原因はコマンドの
-maxdepthが浅すぎたこと - セキュリティのチェックコマンドも一度書いたら終わりではなく、対象の構造が変わったら見直す
33万回のログイン試行より、正直こちらのほうが堪えました。攻撃は他人事として数えられますが、自分の置き忘れは何度確認しても出てくるものだと思い知らされました。次にやるのは、この手のチェックを月次の集計スクリプトに組み込んで、毎月自動で回すことです。
前回・今回とも実測データは、一般向けにコマンド抜きでまとめた記事にも反映しています。
📊 個人サイトが1か月で33万回攻撃されていた — 実測データと対策
普段は照明・舞台の制御装置を個人で設計しています。調光卓、DMX512 / Art-Net / DALI、LEDシャンデリアの深絞り調光、カレンダー連動の演出制御など、**「既製品では届かないところ」**を主にやっています。
過去の記事や実績はこちらにまとめてあります。
🔗 tkurume.work
- 🛡️ 1日数人の個人サイトが1か月で33万回攻撃されていた — ログを数えて塞ぐまで — 前回記事
- 💡 LED照明がちらつく・暗くできない — 症状から引ける形にまとめたもの
- 🇫🇮 Helvar の DALI システムと DMX512 をつなぐ — 254段階・1200bps という壁
- 📈 LED時代の調光カーブ設計 — 消え際をどう作るか
方式の選定や特殊制御でお困りのことがあれば、サイトからご相談いただけます。