0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

33万回攻撃のその後 — 「PHP 8.3.6 のまま」でも中身は 5 回変わっていた:小さな VPS の更新確認と再起動の記録

0
Last updated at Posted at 2026-10-22

はじめに

個人サイト(静的 HTML + 自作アクセスカウンターの PHP)を、Vultr の小さな VPS(1vCPU / 1GB)で運用しています。

ある朝、AI(Claude Code)に「PHP のバージョン確認」と頼んだのがきっかけで、次の 3 つが分かりました。

  1. php -v は 8.3.6 のまま。ところが中身には、今年だけで 5 回 セキュリティ修正が入っていた
  2. PHP の修正は自動で効いていた。一方で カーネルの更新は 8 日間、再起動待ちのまま 放置されていた
  3. 再起動しようとしたら、サーバー丸ごとのバックアップが無い ことに気づいた

この記事は、その確認から再起動までの記録です。

前回の記事:1日数人の個人サイトが1か月で33万回攻撃されていた

1. 「8.3.6」は変わらないが、中身は変わっている

$ php -v
PHP 8.3.6 (cli) (built: Oct  7 2026 18:06:38) (NTS)
    with Zend OPcache v8.3.6, Copyright (c), by Zend Technologies

Ubuntu 24.04 の PHP は、上流のバージョン番号(8.3.6)を固定したまま、修正だけを取り込む(バックポート)方式で更新されます。変わるのは、パッケージ版の末尾の数字だけです。

/var/log/apt/history.log を見ると、今年の更新は次の 5 回でした(すべて自動更新 unattended-upgrades による早朝の適用です。最後の 1 行は、記事の公開後に自分で行った更新で、末尾の追記で説明しています)。

適用日時(日本時間) パッケージ版の変化
2026-01-14 06:44 0ubuntu0.24.04.5 → .6
2026-05-29 06:19 .6 → .9
2026-07-22 06:39 .9 → .10
2026-09-12 06:43 .10 → .11
2026-10-21 06:02 .11 → .12
2026-10-22 10:19 .12 → .13(自動更新を待たず、自分でコマンドを打って更新。首相が呼びかけた 2 件の修正。詳しくは末尾の追記)

「PHP のバージョンは?」と聞かれて 8.3.6 とだけ答えると、パッチが当たっているかどうかは分かりません。 確認すべきなのはパッケージ版です。

dpkg -l | grep php8.3-fpm
# 修正内容は changelog に CVE 番号付きで書かれている
zcat /usr/share/doc/php8.3-common/changelog.Debian.gz | head -40

最新の .12 には 10 件の CVE 修正が入っていました(changelog より)。主なもの 3 件:

CVE 内容
CVE-2026-92842 ストリームフィルタの改行文字オプションに NUL を含めると、ヒープの範囲外を読む
CVE-2026-91768 PHP-FPM の allowed_clients が、IPv6 アドレスの先頭 96 ビットしか照合していなかった
CVE-2026-91769 OpenSSL の相手確認で、本来使わない CN(証明書の名前欄)に戻って照合してしまう

2. PHP は自動で効いていた。カーネルは効いていなかった

更新されたファイルは、動いているプロセスが読み直すまで効きません。 確認したのは次の 2 点です。

# php-fpm がいつ起動したか(更新より後なら、新しい版で動いている)
systemctl show php8.3-fpm -p ActiveEnterTimestamp
# → Thu 2026-10-22 06:30:58 JST

# 再起動が必要なサービスとカーネルの一覧
needrestart -b
# NEEDRESTART-KCUR: 6.8.0-136-generic   ← 動いているカーネル
# NEEDRESTART-KEXP: 6.8.0-147-generic   ← 入っている新しいカーネル
# NEEDRESTART-KSTA: 3                    ← 3 = 新しいカーネルが入っているのに使われていない
  • php-fpm:更新の後に起動し直しており、needrestart の「再起動が必要な一覧」にも入っていませんでした。修正は効いています。
  • カーネル:/var/run/reboot-required の日付は 10/14。このサーバーでは自動再起動(Unattended-Upgrade::Automatic-Reboot)を有効にしていないので、自動更新はカーネルを入れるところで止まります(有効にすることもできます)。そのため、8 日間、古いカーネルのまま動いていました。

稼働日数は 94 日でした。「落ちずに動き続けている」ことと「最新の修正が効いている」ことは、別の話です。

3. 再起動の前に:バックアップが「ある気がしていた」

再起動を頼むとき、私は「バックアップは充分ですね」と添えました。AI の答えは「充分とは言えません」でした。

対象 実際の状態
サイトの HTML 手元の作業フォルダと git にある → 戻せる
サーバー丸ごと 無い(手元にあるのは 1 年以上前の移行時の zip だけ)
nginx・SSH の設定(7 月に固めた防御設定を含む) 無い
アクセスカウンターのデータ・攻撃の月次記録 無い

再起動そのものでデータが消えることはありません。怖いのは「新しいカーネルで起動しない」という、まれなケースです。それでも、「戻せる範囲でバックアップがあればよい」と判断し、再起動の前に必要なものだけ固めて手元に取りました。

# 出力先は /var/tmp(理由は後述)
tar czf /var/tmp/prereboot_20261022.tgz -C / \
  etc var/www var/lib/tkurume-counter var/lib/attack-summary \
  var/lib/fail2ban var/spool/cron usr/local root home

# 環境を再現するための一覧も別に取る
dpkg --get-selections > packages.txt
apt-mark showmanual   > packages_manual.txt
systemctl list-unit-files --state=enabled > enabled_units.txt

約 355MB でした。手元に scp で取り、SHA-1 がサーバー側と一致する ことを確かめてから再起動しました。この一致は転送の確認で、復元できることの確認ではありません。完全なシステムイメージでもなく、復元テストは未実施です。

つまずき:tar が自分自身を固めていた

最初は出力先を /root/prereboot_20261022.tgz にし、--exclude=/root/prereboot_20261022.tgz を付けていました。

tar: root/prereboot_20261022.tgz: file changed as we read it
-rw-r--r-- 1 root root 709296016 ...

-C / で相対パス(root/...)として固めているため、先頭に / の付いた除外指定は一致しません。結果、書きかけの自分自身を取り込み、約 2 倍の 709MB に膨らんでいました。出力先を、固める対象の外(/var/tmp)に変えて解決しました。

4. 再起動と確認

# SSH のセッションが切れる前にコマンドを返すため、少し遅らせて再起動
nohup sh -c "sleep 2; systemctl reboot" >/dev/null 2>&1 &

つまずき:「復帰した」と早とちりする待ちループ

最初に書いた待ちループは、次のようなものでした。

ssh host 'cat /proc/uptime' | awk '{exit !($1<600)}'   # 起動 600 秒未満なら復帰とみなす

再起動 5 秒後に「復帰した」と判定しましたが、実際にはまだ SSH がつながっていませんでした。ssh が失敗すると awk には 1 行も入力が来ません。行が無いと判定式は一度も実行されず、awk は終了コード 0 で終わります。 その結果、「成功」と判定されていました。

再起動の前後で boot_id(起動ごとに変わる ID)を比べるのが確実です。SSH がつながるだけでは、再起動前のサーバーにつながった場合と区別できません。

before=$(ssh host 'cat /proc/sys/kernel/random/boot_id') && [ -n "$before" ] || exit 1
ssh host 'nohup sh -c "sleep 2; systemctl reboot" >/dev/null 2>&1 &'
ok=0
for i in $(seq 1 60); do                      # 最大 60 回試行(5 秒間隔+SSH の待ち時間)
  sleep 5
  after=$(ssh -o ConnectTimeout=5 host 'cat /proc/sys/kernel/random/boot_id' 2>/dev/null) || continue
  if [ -n "$after" ] && [ "$after" != "$before" ]; then echo "再起動完了($i 回目の試行)"; ok=1; break; fi
done
[ "$ok" -eq 1 ] || { echo "時間切れ"; exit 1; }

そのうえで、カーネル版・サービス・HTTP 応答を別に確認します(下の表)。

再起動後の確認結果

確認項目 結果
カーネル 6.8.0-136 → 6.8.0-147
/var/run/reboot-required 消えた
nginx / php8.3-fpm / fail2ban / ssh / cron / ufw すべて active。failed のサービスは 0
SSH の設定(sshd -T) passwordauthentication no のまま
サイト(トップ・主要ページ) 200、応答 0.05 秒前後
PHP のカウンター 200 で正常に応答

サイトが止まったのは 1 分ほどでした。

なお、カウンターの PHP では、今も mb_* 関数を使えません(サーバーに mbstring が入っていないため。前の記事 で詳しく書きました)。PHP が更新されても、ここは変わりません。

まとめ

  • php -v の 8.3.6 だけでは、パッチの有無は分からない。 パッケージ版(0ubuntu0.24.04.12 など)と changelog を見る。緊急時は apt-get update で一覧を更新してから apt list --upgradable を見る
  • 自動更新は(自動再起動を有効にしていなければ)「入れる」まで。 php-fpm は再起動され修正が効いていたが、カーネルは再起動するまで古いまま。needrestart -b と /var/run/reboot-required で確認できる
  • 再起動の前に「戻せるか」を確かめる。 「バックアップがある気がする」は、確かめると無いことがある
  • tar の出力先は、固める対象の外に置く。-C / を使うときは、除外指定に先頭の / を付けない
  • 待ちループは「失敗したときに何が返るか」まで考えて書く

今回のバックアップは手動で 1 回取っただけです。自動バックアップの仕組み作りは、次の宿題です。

追記(2026-10-22 10:19):首相が呼びかけた 2 件は、この時点ではまだ直っていなかった

この記事を公開した後、高市首相が X で「PHP のパッチを大至急適用して」と呼びかけていたことを知りました。対象は CVE-2026-109618 / 109619(外部からプログラムを実行される恐れ・CVSS 9.2)で、本文で紹介した .12 の 3 件とは別物です。

調べると、Ubuntu からさらに新しい 0ubuntu0.24.04.13 が出ていました。まだ入っていなかったので、すぐに入れました。

apt-get update                       # 一覧を更新(これを忘れると新しい修正版が見えない)
apt list --upgradable | grep php     # .13 が出ていた
apt-get install --only-upgrade php8.3-cli php8.3-common php8.3-fpm php8.3-opcache php8.3-readline
dpkg -l php8.3-fpm                                   # 入った版
systemctl show php8.3-fpm -p ActiveEnterTimestamp    # サービスが更新後に起動し直したか
zcat /usr/share/doc/php8.3-common/changelog.Debian.gz | grep 109618   # 修正が入ったことを確認

PHP-FPM は自動で起動し直し、止まったのは 1 秒ほどでした。

「今朝、自動更新が効いていた」は「今日の最新の修正が入っている」と同じではありません。 大きな脆弱性のニュースを見たら、apt-get update で一覧を更新してから apt list --upgradable を見ます。入れたあとは、パッケージ版とサービスの起動時刻も確認します。

追記 2(2026-10-22 22:30):放っておいたらどうなっていたか — 12 時間後に CVE 番号を名乗るアクセスが来ていた

.13 を入れたあと、「自動更新に任せていたらどうなっていたか」を nginx のログで調べました。

  • 10/22 22:06、User-Agent にこの脆弱性の CVE 番号を名乗るアクセス(POST /index.php)が 1 件ありました。前日 19:25 に同じ発信元からトップページへのアクセスが 1 回あります(下見だったかどうかは推測です)。
  • このサーバーの nginx は index.php を PHP-FPM に渡す設定なので、要求は PHP まで届いています。応答は通常のトップページ(200・38,261 バイト)でした。
  • ただし、アクセスログだけでは、この要求が実際に脆弱性を突く内容だったか、修正によって阻止されたかまでは断定できません。言えるのは「その時点で修正版は適用済みだった」「確認したファイルとログの範囲では、侵入・改ざんを示す形跡は見つかっていない」ことです。
  • .13 を自分で入れたのが 10:19。次の自動更新処理は 10/23 06:41 に実行予定(最大 1 時間のランダム遅延あり)でした。それまでに別の更新が走らなければ、未修正の状態でこのアクセスを受けていた可能性があります。
# CVE 番号を名乗るスキャナは、番号で grep すると拾える
zcat -f /var/log/nginx/access.log* | grep -iE "10961[89]"

修正版の公開から 1 日たらずで、CVE 番号を名乗るスキャナが「1 日数人の個人サイト」にまで届きます。緊急の修正は「出た当日に来る」前提で動くのがよさそうです。

あわせて確認したこと(侵入・改ざんの形跡なし):

  • サーバー上の PHP 14 本すべてを、手元の作業フォルダと SHA-1 で照合(差分なし)
  • 拡張子を偽装した PHP、画像の中に仕込まれた PHP などの不正ファイル:なし
  • PHP-FPM の異常終了・nginx の upstream エラー:10/20 以降 0 件

これらは確認した範囲での結果で、あらゆる侵害を否定するものではありません。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?