VPSは立てた瞬間から戦場である - SSH攻撃ログの現実と対策

VPSは立てた瞬間から戦場である - SSH攻撃ログの現実と対策

以前、VPSをマルウェアに感染させてしまった話を書いた。その後Xserver VPSで環境を再構築し、SSHをハードニングした直後、ふと気になった。

「自分のサーバーは、今どれくらい攻撃されているのか?」

調べてみたら、想像の10倍は殴られていた。この記録を残しておく。


ハードニングの内容

再構築にあたり、まず基本の5点を実施。

※ 作業中は既存セッションを閉じず、別ターミナルで新ユーザーのログインを確認してから次に進む。順番を間違えると自分が締め出される。


攻撃ログの集計

/var/log/auth.log を集計するだけで、攻撃の「気配」ではなく「実数」が見える。

試行されたユーザー名 TOP 20

sudo grep "Invalid user" /var/log/auth.log | \
  awk '{print $8}' | sort | uniq -c | sort -rn | head -20

実際の出力(ユーザー名と回数):

試行ユーザー名 回数 考察
admin 172 定番中の定番
user 170 汎用辞書攻撃
ubuntu 145 クラウドVPSのデフォルトユーザー狙い
user2 113
test 43 開発環境の取り残し狙い
postgres 33 DB直撃
ftpuser 29
deploy 26 CI/CDアカウント狙い
steam 25 ゲームサーバー狙い
claude 24 ← 時代を感じる
administrator 20 Windows系混同狙い
bot 18
sammy 17 DigitalOceanチュートリアル定番名
frappe 17 ERPNext運用者狙い
vpn 15
support 15
ali 15
odoo 14 Odoo運用者狙い
Administrator 14
solana 13 クリプトノード運用者狙い

読み取れること

1. 攻撃者は無差別ではなく「推測」している

solana steam odoo frappe jupyter grafana など、特定のソフトウェアを運用している人を狙う偵察が混ざっている。
「このVPSで何が動いているか」を当てに行って、当たれば即侵入試行、というパターン。

2. AIツールが新しい攻撃ターゲットになっている

claude が24回も試行されていたのは意外だった。
AIエージェントの普及に伴い、「AIツールを動かしているサーバー=狙い目」という認識が攻撃側に広がっているのかもしれない。

3. ヒヤリとした名前:Grafana

grafana もランクインしていた。
過去に所属していた組織では、シニアエンジニアが社内サーバーにGrafanaを入れて運用していた記憶がある。Grafanaはデフォルトポート 3000 が無認証で公開されているケースが多く、過去には深刻なCVE(パストラバーサルなど)も存在した。

運用している人は最低限、以下を確認したい。

「入れてる人が多い」「デフォルトのまま放置される傾向」の2点が揃ったOSSは、例外なく攻撃ターゲットになる。


ハードニング成功の「見えない証拠」

当初、こう期待していた。

sudo grep "Failed password" /var/log/auth.log

→ ほぼヒットしない。

一瞬「ログが壊れた?」と思ったが、そうではなかった。
ログをよく読むと、攻撃試行のほとんどがこう終わっている。

Disconnected from invalid user xxx [preauth]

この [preauth] が**「認証フェーズに到達する前に切断された」**ことを示している。
つまり PasswordAuthentication no が効いていて、攻撃者はパスワードを試すチャンスすら与えられていない。「Failed password」のログが発生しないのは、ハードニングが正常に機能している証拠だった。

攻撃者の流れ:

  1. SSH接続
  2. ユーザー名送信(admin, ubuntu, claude ...)
  3. サーバー「そのユーザー存在しない or 鍵認証のみ」→ 即切断
  4. ログには Invalid user ... [preauth] のみ残る

攻撃元IPランキング

sudo grep "Invalid user" /var/log/auth.log | \
  awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head -20

常連と思しきIPが数個、突出して出現していた。
国別で見ると予想通り偏りがあるが、個別IPの特定は避ける(攻撃元を晒す意味もない)。


次のアクション


fail2ban導入時の落とし穴

導入コマンドを打ったら、こんな画面が出た。

┌──── Pending kernel upgrade ────┐
│  Newer kernel available
│  The currently running kernel version is 5.15.0-126-generic
│  which is not the expected kernel version 5.15.0-176-generic.
│  Restarting the system to load the new kernel will not be
│  handled automatically, so you should consider rebooting.

一瞬「fail2ban入れたら壊れた?」と思ったが、これは無関係の通知
過去の apt upgrade で新カーネルが入っていたが未反映だっただけで、fail2banのインストール処理は正常に継続する。

ただしこの通知が出たということは、いずれ再起動は必要ということ。
Docker(Dify, n8n, Ollama)を動かしている場合は、restart policyが always または unless-stopped になっていることを確認してから、業務影響の少ない時間帯に再起動する。

# Dockerコンテナのrestart policy確認
sudo docker inspect --format='{{.Name}}: {{.HostConfig.RestartPolicy.Name}}' \
  $(sudo docker ps -q)

まとめ

「攻撃されている」という現実を数字で見るのは、セキュリティ意識を一段引き上げる最良の体験だった。


関連記事


fail2ban導入後のもう一つのダイアログ:needrestart

fail2banのインストール完了後、さらにこんなダイアログが出た。

┌── Daemons using outdated libraries ──┐
│  Which services should be restarted?
│    [*] containerd.service
│    [*] cron.service
│    [ ] dbus.service
│    [ ] docker.service
│    [*] systemd-journald.service
│    ...

これは needrestart というUbuntu 22.04以降の標準ツールによるもの。

なぜ出るか

apt で共有ライブラリ(libc, libssl等)がアップデートされたとき、既に起動中のプロセスは古いライブラリをメモリに保持したままになる。新しいライブラリを使うには、対象プロセスを再起動する必要がある、と教えてくれている。

何を再起動すべきか

デフォルト選択のまま進めるのが正解
Ubuntuが気を利かせて、安全に再起動できないもの(docker, dbus, logind等)は最初からチェックを外している。つまりDify/n8n/Ollamaが動いているDockerは触られない設計。

サービス チェック 理由
docker.service OFF 運用中のコンテナに影響するため
dbus.service OFF システム基盤、フル再起動相当が必要
systemd-logind OFF ログインセッション管理
cron, journald, udevd等 ON 再起動しても影響が少ない

needrestartの賢さ

needrestart/proc/[pid]/maps を調べて「古いライブラリをロードしているプロセス」を自動検出している。
昔のLinuxサーバーは「upgradeしたら何も通知なく動作不良」なんてこともあったので、こういう丁寧な確認ダイアログが標準装備されていること自体が、Ubuntuがサーバー運用を考慮した設計になっている証拠

地味だが、サーバー運用者にとっては非常にありがたい機能。Enterで進めて問題なし。


fail2ban の設定:実戦投入編

fail2ban のパッケージインストールが終わったら、次は設定。
ここでハマりがちなポイントも含めて、実際にやったことをまとめる。

設定ファイルの鉄則:jail.conf は触らない

fail2banは設定ファイルが二層構造になっている。

1. /etc/fail2ban/jail.conf          ← パッケージ提供(触らない)
2. /etc/fail2ban/jail.d/*.conf      ← 個別設定ファイル
3. /etc/fail2ban/jail.local         ← 自分のカスタム設定(←ここに書く)
4. /etc/fail2ban/jail.d/*.local     ← より細かい自分のカスタム

後から読まれたものが上書きする仕組み。jail.conf を直接編集するとパッケージアップデート時に消えるので、必ず jail.local に書く

jail.local は初期状態では存在しない。新規作成が正解。

sudo vi /etc/fail2ban/jail.local

実際に採用した設定

[DEFAULT]
bantime  = 600
findtime = 600
maxretry = 5
ignoreip = 127.0.0.1/8 ::1
backend  = systemd

[sshd]
enabled  = true
maxretry = 3

設定の意図

項目 意図
bantime 600 (10分) 自分が誤BANされても10分待てば戻れる
findtime 600 直近10分間の失敗を観測
maxretry (DEFAULT) 5 将来他のjailを有効にしたときの保険値
maxretry (sshd) 3 SSHは攻撃が多いので厳しめ
ignoreip localhost only 動的IP環境なので自IPは入れない
backend systemd Ubuntu 22.04以降の推奨方式

動的IP環境でのホワイトリスト判断

自宅回線が動的IP(固定IPではない)の場合、
ignoreip に自分のグローバルIPを入れると、IPが変わった瞬間に自分が弾かれるリスクがある。

そのため今回の運用方針は:

この**「待てば戻れる」設計**が、固定IPを持たない環境では現実的。

起動手順

# 文法チェック
sudo fail2ban-client -t

# 起動+自動起動有効化
sudo systemctl enable --now fail2ban

# 状態確認
sudo systemctl status fail2ban
sudo fail2ban-client status
sudo fail2ban-client status sshd

systemctl statusActive: active (running) になっていれば成功。

締め出された時の復活コマンド(お守り)

Xserver VPSパネルの「コンソール」ボタンからログインすれば、
SSHを経由しない物理コンソール相当のアクセスが可能。
この経路なら、仮に自分がBANされていても入れる。

# BAN中のIP一覧確認
sudo fail2ban-client status sshd

# 自分のIPをBAN解除
sudo fail2ban-client set sshd unbanip <自分のIP>

# 緊急時:fail2ban一時停止
sudo systemctl stop fail2ban

この「最終ログイン経路が常にある」というのは、クラウドVPSの大きな利点。
自前サーバーだと物理アクセスが必要になるので、復旧の難易度が桁違いに上がる。

Enterキー誤爆対策としての3回制限

SSH接続時、うっかり間違ったキーでログインしようとしてEnter連打、
というミスは誰にでもある(パスワード認証を無効化している今となっては起こりにくいが)。

鍵認証のみの環境なら、maxretry = 3 でも十分。
攻撃者からすれば3回試せれば辞書の上位候補は全部投げられるので、
ユーザー配慮で5回」みたいなパスワード時代の緩さはもう不要。

観測は30分後からが楽しい

起動直後は Total banned: 0 から始まるが、
30分〜1時間も経てば、攻撃者が自動的にBANされていく様子が見える。

sudo fail2ban-client status sshd

で:

Status for the jail: sshd
|- Filter
|  |- Currently failed: 2
|  |- Total failed:     XX
|  `- Journal matches:  _SYSTEMD_UNIT=ssh.service
`- Actions
   |- Currently banned: X
   |- Total banned:     XX
   `- Banned IP list:   xxx.xxx.xxx.xxx ...

「Total banned」の数字が増えていくのが、謎の達成感を生む
攻撃を受ける側から、自動で撃退する側に回った瞬間でもある。

リアルタイム監視

動作を眺めたい時は:

sudo tail -f /var/log/fail2ban.log

こんなログが流れてくる:

fail2ban.filter: INFO    [sshd] Found xxx.xxx.xxx.xxx
fail2ban.actions: NOTICE  [sshd] Ban xxx.xxx.xxx.xxx

Found は「怪しい試行を検知」、Ban は「実際にBAN実行」。
Ctrl+C で抜けられる。


ここまでで達成したこと

インターネットに開いた瞬間から戦場というこのサーバーは、
ようやく「自分の目と手で守られている」状態になった。

次はSSHポート変更とufwで、攻撃ノイズそのものを減らしていくフェーズに進む予定。