はじめに
10月21日、PHP本体に重大な脆弱性が2件見つかり、修正版が同日23時(日本時間)に緊急リリースされました。
通称「php2shell」(CVE-2026-109618/CVE-2026-109619)と呼ばれていて、深刻度はCVSS v4.0で9.2。外部から任意のコードを実行されてしまう(いわゆるRCE)類の脆弱性のようです。PHPで動いているものは全般的に対象になりうるとのことで、WordPressなどのCMSも例外ではありません。
私がこの件を認識したのは21日の15時過ぎ、ニュース記事を見てでした。しかも、その時点で「悪用できると主張するコードが既に出回り始めている」という追記まであり、パッチ公開までの約8時間をどうしのぐか、という状況でした。
実は今週頭からインフルエンザにかかっており自宅療養中のため時間はある。なので、いい機会ととらえリアルタイムに対処をしようと考えました。
当サーバの構成(対応前)
当サーバは自宅に置いたWindows Server 2025で、IIS上でPHPをFastCGIとして動かしています。このブログ(WordPress)のほか、自作のWebアプリやブラウザゲームなど、PHPを使うサービスがいくつか稼働しています。
で、今回改めて確認してみたところ、PHPが2つ共存した状態になっていました。
| PHP | 場所 | 使っていたサービス |
|---|---|---|
| 8.3.1 | IIS Express同梱のPHP | ブログ、自作Webアプリなど(サーバの既定) |
| 8.5.10 | 別途導入したフォルダ | 9月に構築した一部のサービスのみ |
9月に新しいサービスを作った際、既存サービスへの影響を考慮して、新サービスだけを新しいPHPで動かす分離構成にしていました。そのときは「ブログ側は後でじっくり上げよう」と思っていたのですが、まぁ、そのまま忘れていたわけです。。。
問題は8.3.1の方です。IIS Expressに同梱されていたもので、ビルドは2023年12月。自動で更新されることもなく、それ以降に出た修正は何も当たっていない状態でした。しかも、ブログだけでなく自作のWebアプリもサーバの既定設定を引き継いでこの8.3.1で動いていたので、攻撃されうる入り口は想像していたより多かったです。
対応の流れ
15:20ごろ 情報収集
ニュース記事と、GMO Flatt Securityさんのブログを確認。パッチ公開前の緩和策として「Session Upload Progress機能の無効化(session.upload_progress.enabled=0)」が挙げられていました。ただ、14時の追記で「この設定だけでは緩和にとどまり、攻撃自体は回避できない見込み」と書かれていて、正直なところ設定変更だけでは安心できない状況です。
15:30ごろ 現状把握
php -v を叩いてみると「8.3.1」。ところがフォルダにあるphp.exeのプロパティは「8.5.10」。ここで初めて、PHPが2つ共存していることを思い出しました。IISの設定を確認したところ、ブログと自作Webアプリが8.3.1、一部のサービスだけが8.5.10で動いていました。
ここでIIS Express同梱の8.3.1は修正版が出ない可能性が高いと判断しました。
16:00ごろ パッチ公開まで外から届かないようにする
設定で防ぎきれないのであれば、PHPに届く経路ごと閉じてしまうのが確実です。個人ブログ(しかも更新滞り)なので、数時間止まっても実害はほぼありません。
- ブログのサイトを停止
- メインのサイトは、要求フィルターで「.php」へのリクエストをすべて拒否
これで静的なページ(ツール紹介ページなど)はそのまま表示しつつ、PHPだけをまとめて塞ぐことができました。外からcurlで確認して、静的ページは200、PHPは404になっていることを確認しています。
16:00〜16:45 PHPを1つにまとめる準備
この機会に、PHPは新しい方に1本化することにしました。8.3.1を個別に更新する手もありましたが、管理するものが2つあると、また同じように片方を忘れそうなので。。。
ただし、既存のphp.iniは新サービス向けに作ったもので、WordPressに必要な mysqli などが有効になっていません。そこで、実行ファイルは1つ、php.iniは用途ごとに分ける構成にしました。IISのFastCGIは、同じphp-cgi.exeでも引数(-c でiniを指定)が違えば別のアプリとして登録できるので、パッチは1回当てれば両方に効きます。
Web用のiniは旧PHPのものを土台にして、次の点を直しています。
- extension_dir を新しいPHPのフォルダに変更
- 8.5には存在しない拡張(xmlrpc、imap)を削除
- intl、sodium、zip を追加
- session.upload_progress.enabled = 0 を追記(念のため)
- expose_php を Off に
- date.timezone がなぜか Asia/Seoul になっていたので Asia/Tokyo に(どちらもUTC+9なので実害はなかったようです)
ここまでで、新しいiniで拡張がすべて読み込めることを確認し、FastCGIへの登録まで済ませておきました。あとはパッチを待つだけです。
23:16 PHP 8.5.12 を適用
23時に修正版が公開されたので、IISを止めてphp.iniを退避し、Windows版のZIPを上書き展開。php -v で 8.5.12 になっていることを確認してから、サーバ既定のPHPハンドラーを新しいPHPに切り替えました。
23:17 ブログだけ8.3.1のままだった
開ける前にサイトごとの設定を確認したところ、ブログだけがまだ8.3.1を向いていました。日中に調べたときは「既定を引き継いでいる」ように見えていたのですが、実際にはapplicationHost.configにブログ専用の設定が書かれていたようです。このまま開けていたら、ブログだけパッチ未適用のPHPで動き続けるところでした。。。
ブログの設定も書き換えたうえで、FastCGIの登録から8.3.1そのものを削除しました。こうしておけば、どこかに古い指定が残っていても「黙って古いPHPで動く」のではなく「エラーで動かない」になるので、気づけます。
23:19 再開
すべてのサイトが8.5.12を使うことを確認してから、.phpの拒否を解除し、ブログを開始。ブログの表示と管理画面へのログイン、各サービスの動作を確認して、エラーは出ませんでした。WordPressがPHP 8.3から8.5へ一気に上がったので少し身構えていたのですが、拍子抜けするほど何も起きませんでした。
23:25 侵害チェック
悪用コードが昼から出回っていたので、念のため次の点を確認しました。
- 過去3日間に更新された.phpファイル
- 攻撃でよく使われるコード(eval(base64_decode(…)) など)が含まれていないか
- 不審なアカウントが作られていないか
更新されたファイルは約5,000件もあって一瞬ひやっとしましたが、すべて「23時台」で、中身はWordPress本体やプラグイン、テーマのファイルでした。PHPを止めていた間に溜まっていた自動更新が、再開直後にまとめて走ったようです。uploadsなどに不審なファイルはなく、不審なコードもアカウントも見つかりませんでした。
振り返り
結果として、当サーバのPHPは修正済みの8.5.12のみになり、すべてのサービスが同じPHPで動く構成になりました。パッチ公開から再開までは20分ほどです。
昨今、AIによるものと思われる攻撃が爆増していて、どこが狙われるか分かりません。今回のように大きく取り上げられ、尚且つ時間が確保できていれば対処そのものはできるかもしれませんが、もしもそうでなければ「週末に」あるいは「長期休みにでも」と後回しにしていたかもしれません。アンテナは高く、メンテは早くを心掛けないとサーバ維持は難しい時代に入ったと思った次第。
教訓としては、こんなところかなと思います。
- 「設定で緩和」の情報は、半日で評価が変わることがある。迷ったら止めてしまう方が早い
- アプリ単位でPHPを分けるときは、「いつ戻すか」を決めておく
- ハンドラーを切り替えたら、サイトごとの実際の値を確認する(既定値だけ見て安心しない)
- 使わなくなった古い実行環境は、残さず登録ごと消す
まだサーバの構成で見直したいところはあるので、そのうち少しずつ手を入れていこうと思います。とりあえず今回は無事に乗り切れたようで一安心です。