対象:PHPで動くサイトを運営していて、緊急のPHP更新をするとき、またはしたあとに確認の漏れが心配な人。
2026年10月、PHPの重大な脆弱性2件(CVE-2026-109618・CVE-2026-109619)の修正版が出ました。当サイトの運営者が関わるサイトでも、その夜に更新しました。このページは、そのときに実際にあった「最初の思い込みが外れた2つの点」と、更新後に全サーバーへ同じ項目を当てた侵入確認を、一般の手順として整理したものです。脆弱性の内容と修正版は PHPの重大な脆弱性2件と緊急の修正版(2026年10月) にまとめています。
何があったか(当サイトの運営者が関わるサイトでの出来事)
このサイトは、複数の環境でPHPを使っています。PHPの開発チームが修正版の公開日時を予告していたため、運営者は公開の前にメンテナンス表示へ切り替え、修正版が出るまで公開を止めました。修正版が出たあとに差し替え、メンテナンス表示を解いて戻しました。
予告を受けて
サイトをメンテナンス表示にして、修正版を待つ修正版の公開後
修正版に差し替え、メンテナンス表示を解除する更新後の確認
Web側は更新済みだったが、cronが使うコマンドライン用のPHPは古い版を指したままだったので、向き先を直した同じ夜
メンテナンス表示を解く一時スクリプトが、書き込みの前に止まった。原因は set -e と diff の組み合わせ。被害はなく、スクリプトを作り直したその後
全サーバーに同じ侵入確認の項目を当て、更新前に届いていた疑わしい1件のアクセスを、ファイルの比較で確かめた
その後、米CISAはこの2件を、実際に悪用された脆弱性の一覧(KEV)に加えました(CISAの発表)。急いで更新した判断は裏づけられましたが、更新は入口を閉じるだけです。すでに入られていないかの確認は、別に必要です。
思い込み1:「WebのPHPを更新したから終わり」
最初の思い込みは、サイトが新しい版で動いていれば、そのサーバーのPHPは全部更新済みだ、というものでした。実際には、cronから呼ばれるコマンドライン用のPHPの向き先が、古い版のままでした。
PHPは、動かし方(SAPI:Server API)によって別のプログラムになることがあります。php.net は、CLIはほかのSAPIと違う点が多いと説明しています(PHP: コマンドラインからの利用)。1台に複数の版を入れていると、Webは新しい版、コマンドラインは古い版、という組み合わせが起こります。
Webのリクエスト → PHP-FPM / Apacheのモジュール
更新後の版で動いている(サイトヘルスなどで確認できる)
cron・シェルのスクリプト → コマンドライン用のPHP
向き先が古い版のまま。Webの確認では見えない
どのPHPが動いているかを、WebとCLIで別々に確かめる
コマンドラインのPHPの版を見る
php -v で、そのシェルから呼ばれるPHPの版が分かります。php.net は、php -v でCGI版かCLI版かも分かると説明しています。which php で、実際に呼ばれる実行ファイルの場所を確かめます。
Debian系で版を切り替えているなら、向き先を見る
update-alternatives --display php で、php というコマンドがどの版を指しているかが表示されます(update-alternativesのマニュアル)。切り替えは --config で行いますが、向き先を変えるのはサーバーの設定変更なので、手順と戻し方を確かめてから行ってください。
cronのジョブは、PHPをフルパスで書く
cronは、ログインシェルとは別の環境でジョブを動かします(crontab(5))。php とだけ書くと、どのPHPが選ばれるかが環境に左右されます。当サイトの考えでは、更新の対象にした版のPHPをフルパスで書いておくと、更新後の確認が一意になります。
Webが使うPHPの版は、Web側から確かめる
WordPressなら管理画面の「サイトヘルス」の情報で確かめられます。コードで確かめるなら、PHPの定数 PHP_VERSION(版)と PHP_SAPI(実行方式。CLIなら cli、PHP-FPMなら fpm-fcgi、Apacheのモジュールなら apache2handler)が使えます(php_sapi_name)。
phpinfo() のページは、確認後すぐに削除する
phpinfo() を書いたファイルを公開ディレクトリに置くと版は分かりますが、設定やサーバーの内部情報が全部見えます。確認が終わったら、その場で削除してください。置いたままにしないことが、この方法を使う条件です。
思い込み2:「diff の結果は、成功か失敗かのどちらか」
メンテナンス表示を解除する一時スクリプトは、書き込みの前に、いまの内容と新しい内容の差を diff で見る作りでした。このスクリプトは set -e を付けていました。
diff は、差が見つかると終了コード1を返します。GNUのマニュアルでは、0は差なし、1は差あり、2は異常(trouble)です(GNU diffutils: diff)。差があるのは、そのスクリプトの想定どおりでした。しかし set -e(errexit)は、条件文の中などの例外を除き、0以外の終了コードでシェルを止めます。差があるだけでスクリプトが止まり、書き込みには進みませんでした。
結果として、メンテナンス表示を解く処理は何も書かないまま止まり、被害はありませんでした。止まる側に倒れたので安全でしたが、逆の組み合わせ(止まるべきところで止まらない)だと危険です。スクリプトは作り直しました。
終了コード1が「異常」ではないコマンド
| コマンド | 終了コード1の意味 | 2の意味 | 出典 |
|---|---|---|---|
diff | 差がある | 異常 | GNU diffutils: diff |
cmp | 差がある | 異常 | GNU diffutils: cmp |
grep | 選ばれた行がない(一致なし) | エラー | GNU grep |
安全な書き方
GNU Bashのマニュアルは、set -e でも、if の条件の一部、&& や || の列の最後以外のコマンド、! で反転したコマンドなどが0以外を返してもシェルは終了しない、と説明しています(GNU Bash: The Set Builtin)。これを使います。
止まる、または異常を隠す書き方
set -eのもとでdiff a bだけを書く(差があるだけで止まる)diff a b || true(差ありも異常(2)も区別せず、握りつぶす)grepを|| trueで受けて、一致なしと読み取りエラーを同じ扱いにする
終了コードを区別して受ける
- 終了コードを変数に受けて、0・1・2を分けて分岐する(こちらを勧める)
- 差の有無だけでよいなら
if diff -q a b; then …でもよいが、2(異常)も「差あり」と同じ側に入る点に注意する || trueを使うなら、2(異常)を見逃してよいかを考えたうえで使う
0・1・2を分ける例です。|| の左側の失敗は errexit の対象外なので、スクリプトは止まらず、rc に終了コードが入ります。
#!/usr/bin/env bash
set -euo pipefail
rc=0
diff -rq deployed/ live/ || rc=$?
case "$rc" in
0) echo "差なし" ;;
1) echo "差あり: 内容を確認してから進める" ;;
*) echo "diff が異常終了" >&2; exit 2 ;;
esacset -e は、完全な安全装置ではありません。例外が多く、使い方によっては見落としが起こるため、重要な分岐は、終了コードを自分で見て判断するほうが読み間違いません(当サイトの考えです)。
更新後の侵入確認:全サーバーに同じ項目を当てる
更新は入口を閉じる作業で、更新前に入られていないかを調べる作業ではありません。KEVの対応では、米CISAが更新の前の侵入の確認を求めています(詳しくは PHPの重大な脆弱性2件と緊急の修正版 の「悪用の確認と米CISAのKEVへの追加」)。当サイトの運営者は、サーバーごとに調べる項目がばらつかないよう、全サーバーに同じチェックリストを当てました。
| 項目 | 見るもの | コマンドの例 |
|---|---|---|
| ログイン用の鍵 | 登録した覚えのない公開鍵がないか | authorized_keys を開く |
| ユーザー | 知らないユーザー、管理者権限のグループの増減 | getent passwd、getent group sudo |
| パッケージ管理下のファイルの改変 | パッケージが入れたファイルが、配布時から変わっていないか | debsums -s(Debian系)、rpm -Va(RHEL系) |
| パッケージに属さない実行ファイル | /usr/local、/opt、PATH上に、どのパッケージにも属さない実行ファイルが増えていないか | dpkg -S <ファイル>(Debian系)、rpm -qf <ファイル>(RHEL系)で所有パッケージが出ないものを調べる |
| SUID | 実行した人でなく所有者の権限で動くファイルが増えていないか | find / -xdev -perm -4000 -type f |
| cronとsystemd | 知らない定期実行、知らないタイマー | crontab -l、/etc/crontab、/etc/cron.d、systemctl list-timers |
| /tmp | 実行できるファイルが置かれていないか | find /tmp /var/tmp -type f -perm /111 |
| 外向きの通信 | 見覚えのない接続先への通信 | ss -tunp |
| コンテナの中 | 動いているプロセス、起動後に書き込まれたファイル | docker top、docker diff |
crontab -l が表示するのは、実行したユーザー自身のジョブだけです。/etc/crontab、/etc/cron.d、ユーザーごとのcronの保管場所は別に確かめます。ss -tunp は、すべてのプロセス名を見るには管理者権限(root)が必要です。dpkg -S と rpm -qf は、ファイルがどのパッケージのものかを調べる機能で、「所有パッケージなし」と出ても、自分で置いたファイルである可能性があるため、置いた覚えがあるかを照らし合わせます。
find の -perm -4000 は、指定したビットがすべて立っているファイルを探します(find(1))。docker diff は、コンテナを作ってから変わったファイル(A:追加、C:変更、D:削除)を表示し、docker top は、動いているプロセスを表示します(docker container diff、docker container top)。コンテナは普通、作り直して使うものなので、docker diff に出る実行ファイルは、想定外のものとして調べます(当サイトの考えです)。
各項目の見方と、レンタルサーバー・WordPress・VPSごとの手順は サイト・サーバーが不正アクセスされていないか確かめる方法 で解説しています。
疑わしいログは、ファイルの比較で決着させる
この確認の中で、更新の前に、問い合わせフォームへ、Tor(通信元を隠す仕組み)の出口ノードからのPOSTが届いていたことが見つかりました。ログに残っているのは、リクエストが届いた事実までで、その結果、何かが書き込まれたかどうかは分かりません。
そこで、公開中のファイルと、デプロイ時に手元に残したコピーを比べました。結果は完全に一致し、増えたPHPファイルもありませんでした。この確認では、ファイルが書き換えられた形跡は見つかりませんでした。
配布時のコピーを、サーバーの外に残しておく
比べる相手がサーバーの中にしかないと、同時に書き換えられても気づけません。デプロイのたびに、公開した状態をサーバーの外に残しておきます。
公開中のファイルと比べる
diff -rq で差のあるファイルを探すか、ファイルごとのハッシュ(sha256sum)を一覧にして比べます。差があれば、それが想定した変更かを確かめます。
増えたファイルも確認する
内容が一致していても、新しく置かれたPHPファイルは、元のコピーに無いので別に探す必要があります。diff -rq は、片側にしかないファイルも表示します。
一致しても、安全の証明にはならない
この方法で分かるのは、比べた範囲のファイルが変わっていないことだけです。データベース、アップロード用のフォルダ、サーバーの外側の変更は見えません。ほかの項目の確認と合わせて判断します(当サイトの考えです)。
当サイトの視点:緊急対応の夜こそ、確認の項目を固定しておく
急いでいる夜は、どのサーバーで何を見たかが、ばらつきやすくなります。項目の一覧を先に決めておくと、全部に同じものを当てられ、見ていないものが分かります。今回、古い版のコマンドラインのPHPを見つけられたのも、更新の確認をWebだけで終わらせなかったからです。
今日やること
- サーバーにログインできるなら、
php -vとwhich phpを実行し、cronのジョブにPHPのフルパスを書く - Webが使うPHPの版は、サイトヘルスなど、Web側から確かめる(
phpinfo()を使ったら、確認後すぐに削除する) set -eを使うスクリプトでdiff・grep・cmpを呼んでいる箇所を探し、終了コード1の扱いを決める- 公開したファイルのコピーをサーバーの外に残し、更新後に比べる
- 侵入確認の項目を全サーバーで同じにして、1つずつ当てる
出典
- PHP: コマンドラインからの利用
- PHP: php_sapi_name
- GNU Bash Reference Manual: The Set Builtin
- GNU diffutils: diff、cmp、GNU grep
- update-alternatives(1)、crontab(5)、find(1)
- docker container diff、docker container top
- CISA: Known Exploited Vulnerabilities Catalogに2件を追加
次に読む
- 脆弱性の内容と修正版:PHPの重大な脆弱性2件と緊急の修正版(2026年10月)
- 侵入の確認:サイト・サーバーが不正アクセスされていないか確かめる方法
- 備え:バックアップと復旧の基本 / セキュリティの棚卸しチェックリスト
- 用語:RCE(リモートコード実行)とは / IOC(侵害指標)とは