CMS・WordPress

【php2shell】PHPの緊急脆弱性に、Xserver上のWordPressで一晩対応した記録|棚卸しから.htaccessの緊急メンテナンスまで

2026年10月20日、PHPのセキュリティチームから「重大な脆弱性2件を修正するリリースを翌日に出す」という事前告知がありました。どちらもCVSS v4.0で9.2、リモートでコードを実行される恐れがあるという内容です。通称はphp2shellです。

この記事では、担当しているWordPressのサイト(本番はXserver、ローカルはDocker)で、告知を見てから修正が適用されるまでに何を確認し、何をしたかをまとめます。

この記事の内容は2026年10月時点のものです。サイトが特定できる情報は伏せています。

このシリーズの記事

php2shellへの対応は、環境によって打てる手がまったく違いました。4本に分けてまとめています。

記事 内容
この記事 Xserver(Apache)のWordPressで、一晩でやったこと
PHPを自分で上げられない環境でどう備えたか マネージドホスティング(Nginx)と静的配信の場合。.htaccessが効かないときの判断
追加の脆弱性CVE-2026-115560の影響を確かめた方法 2日後に出た別の脆弱性。grep 1回で判断できた理由
PHP 7.4から8.3に上げる前にやったこと 8.1以前には修正が出ない。古いサイトのPHPを上げるときの棚卸し

時系列

日時(日本時間) 出来事
10/20 php.netで事前告知。CERT-EUも注意喚起を公開
10/21昼 国内メディアでも報道。手元のリポジトリの棚卸しを開始
10/21 16:00ごろ Xserverが「予防的な安全対策」として、アカウント管理画面などを一時停止
10/21夜 ローカルでWordPress本体を7.1.3に更新して検証 → 本番も更新
10/21 23:00 PHPの修正版(8.5.12 / 8.4.27 / 8.3.36 / 8.2.35)が公開
10/22未明 .htaccessで緊急メンテナンスに切り替え
10/22 XserverでPHP 8.3.36が適用されたことを確認

公開された脆弱性の中身

23時に公開された公式のアドバイザリの要点です。

  • CVE-2026-109618:メモリ不足のときの処理で、内部のリクエストデータが中途半端に初期化された状態になり、その後の終了処理で悪用される
  • CVE-2026-109619:リクエストが持っていた内部の状態が解放後も残り、同じワーカーの次のリクエストがそれを使ってしまう
  • 影響を受けるのは、Webのリクエストを処理するPHPすべて(mod_php、PHP-FPM、php-cgi、LiteSpeedなど)。報道によると、特にmod_phpの構成が危険とされている
  • 修正版は8.5.12 / 8.4.27 / 8.3.36 / 8.2.35
  • 8.1以前には、PHP公式からの修正版は出ない。7.4・8.0・8.1向けには、自分でソースからビルドする人やLTSベンダー向けのパッチだけが用意された
  • 緩和策はあるものの「完全ではない」と明記されていて、確実なのは修正版への更新だけ

攻撃の状況については、情報が割れていました。カナダのサイバーセキュリティセンターは「現時点で実際の悪用は確認されていない(概念実証のコードはある)」としています。一方、Help Net Securityは、公開前の10月17日にAmazonのハニーポットで攻撃の試みが記録されたと報じています。どちらにしても、公開直後は無差別なスキャンが増える時期なので、急ぐ前提で動きました。

1. まずはリポジトリの棚卸し

告知の時点では影響するバージョンも分からないので、まず「自分のサイトが何で動いているか」を把握しました。確認したのは次のファイルです。

対象 見るところ
docker-compose.yml イメージとタグ、ポートが127.0.0.1:に限られているか
Dockerfile FROMのベースイメージ(PHPのバージョン)
php/conf.d/*.ini 本番のサーバーをまねた設定になっているか
wp/wp-includes/version.php WordPress本体のバージョン
wp-content/themes/*/style.css テーマのバージョン
wp-content/plugins/ プラグインの一覧

このサイトで分かったことは次のとおりです。

  • ローカルはwordpress:php8.3-apacheとmariadb:10.5
  • Xserverの設定を再現したiniファイルがあった → 本番はXserver
  • Dockerfileを置いているフォルダの名前はwordpress-php74なのに、中身はPHP 8.3だった。フォルダ名ではなく中身で判断するのが大事です
  • ポートは127.0.0.1:8080:80で、自分のMacからしか接続できない設定

ただし、本番のPHPのバージョンはリポジトリからは分かりません。ここは本番で確かめる必要があります。

2. 本番のPHPのバージョンを確かめる

Xserverなら、サーバーパネルの「PHP Ver.切替」で確認できます。ところが今回は、Xserverが管理画面を一時停止していて開けませんでした。

そんなときは、WordPressの管理画面から確認できます。

ツール → サイトヘルス → 情報 → サーバー → PHPバージョン

パネルに出るのは「8.3」のような大まかな番号ですが、サイトヘルスには8.3.35のような細かい番号まで出ます。修正済みかどうかはこの細かい番号で決まるので、むしろこちらを正として見るのがおすすめです。

結果はPHP 8.3.35。修正版の8.3.36より古いので、対象でした。

3. ついでにWordPress本体も最新にする

PHPの修正版を待つ間に、WordPress本体を7.1.2から7.1.3に上げました。7.1.3は10月6日に公開されたセキュリティリリースで、脆弱性7件の修正が入っています。

WordPress本体を上げても、PHPの脆弱性は直りません。別の話として扱っています。

手順は「ローカルで検証 → 本番の管理画面で同じ操作」です。ローカルのファイルやデータベースを本番にアップロードしないのが原則です。その間に届いた問い合わせや、更新した記事が消えてしまうからです。

ローカルでの準備

ファイルとデータベースを退避します。

cp -a wp wp_bak_7.1.2
docker compose exec -T db mysqldump -u root -p<パスワード> wordpress > bak_7.1.2.sql
ls -la bak_7.1.2.sql

最後のコマンドで、ファイルが0バイトになっていないことを確かめます。

wp-config.phpでは、ローカルで操作しているつもりで本番を触ってしまわないよう、URLを固定しておきます。

define( 'WP_HOME', 'http://localhost:8080' );
define( 'WP_SITEURL', 'http://localhost:8080' );

define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );

既存のdebug.logは別名で退避して空にしておくと、更新後に出たエラーだけを見られます。

更新の順番

  1. プラグインを1つずつ 更新する(まとめて更新すると、不具合の原因が特定できない)
  2. 親テーマを更新する
  3. 本体を更新し、「データベースの更新が必要です」と出たら実行する
  4. 本体の更新後に出てきたプラグインの更新を、また1つずつ

ACF PROやSWELLのような有料のプラグインやテーマは、ローカルにはライセンスがないので更新できません。本番で更新したあと、公式のマイアカウントからzipを取ってきて、ローカルのフォルダを丸ごと入れ替えて揃えます。

確認

grep '^\$wp_version' wp/wp-includes/version.php
grep -E "PHP (Warning|Fatal|Parse)" wp/wp-content/debug.log

FatalとWarningが0件なら合格としました。今回は、プラグインの翻訳の読み込みに関するNoticeだけで、更新前と変わりませんでした。

1つ気をつけたのが、Code Snippetsが3.10系から4.0.0へのメジャーアップデートだったことです。登録しているスニペットが有効のまま残っているか、処理がきちんと効いているかを1つずつ確かめました。

最後に、DockerfileのARG WP_VERSIONも新しいバージョンに合わせておきます。./wpをマウントしている構成では実際には使われませんが、作り直したときに古いバージョンが入るのを防ぐためです。

4. 修正版が出たら:自分のバージョンで分かれる

23時の公開後は、本番のバージョンによって対応が変わります。

本番のPHP 対応
8.2〜8.5 修正版の対象。XserverではPHP本体の更新はXserver側の作業なので、適用を待つ
8.1以下(7.4.33など) 公式の修正版は出ない。PHP Ver.切替で8.3以上へ上げる のが確実

7.4.33は7.4系の最後のバージョンで、2022年11月にサポートが終わっています。ホスティング会社が独自にパッチを当てたとしても、表示上のバージョンは7.4.33のままのはずです。直ったかどうかを番号で確かめられないのも、古いバージョンのつらいところです。古いサイトのPHPを上げるときの注意点は、PHP 7.4から8.3に上げる前にやったこと にまとめています。

なお、PHP 8.2も2026年12月31日 でサポートが終わります。8.2で動いているサイトは、今回の修正が当たっても、年内に8.3以上へ切り替える計画を立てておくのがおすすめです。

今回のサイトは8.3.35だったので、Xserverが8.3.36を適用するのを待つ方針にしました。

スポンサーリンク

5. php.iniで緩和策を入れる(修正が適用されるまで)

公式のアドバイザリには、すぐに更新できない場合の緩和策が載っています。Xserverでは、サーバーパネルの「php.ini設定」から変更できます。

session.upload_progress.enabled = 0
post_max_size = 32M
upload_max_filesize = 24M

upload_max_filesizeは、post_max_sizeより小さくします。

ポイントは3つです。

  • 公式が.user.iniやini_set()には頼らないように と明記しているので、パネルの「php.ini設定」で入れる
  • memory_limit=-1も緩和策として挙がっているが、共用サーバーでは設定できないか、かえってリスクがあるので見送った
  • もともとpost_max_sizeが2Gと大きすぎたので、修正が適用されたあとも32M程度のままにしておくのがおすすめ

本番に入れる前に、ローカルのiniにも同じ値を入れて、メディアのアップロードやContact Form 7の添付ファイルが上限に引っかからないかを確かめておくと安心です。

ただ、今回はXserverの管理画面が止まっていたので、この緩和策を入れられるのは翌朝以降でした。そこで、夜の間の対策として次の緊急メンテナンスを入れました。

結果として、管理画面が使えるようになる前にXserverで修正版が適用されたので、php.iniの緩和策は入れずに済みました。修正の適用が遅れる場合に備えて、値だけはローカルで試しておくと、すぐに入れられます。

6. .htaccessで緊急メンテナンスにする

置く場所

WordPressのindex.phpやwp-config.phpがある階層です。Xserverなら/home/サーバーID/ドメイン名/public_html/です。

public_html/
├── .htaccess          ← 既存のファイルの「先頭」に追記
├── maintenance.html   ← 新しく置く
├── index.php
├── wp-config.php
└── wp-admin/ ...

.htaccessに追記するブロック

# BEGIN WordPressより 上 に書きます。下に書くと、先にWordPressへの転送のルールが効いてしまいます。

# BEGIN 緊急メンテナンス(PHP脆弱性対応。修正の適用を確認したらこのブロックごと削除)
ErrorDocument 503 /maintenance.html
<IfModule mod_headers.c>
Header always set Retry-After "86400"
</IfModule>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_URI} !^/maintenance\.html$
# 自分のIPだけ通す場合は次の行の#を外し、IPを書き換える(ドットは\.)
# RewriteCond %{REMOTE_ADDR} !^203\.0\.113\.10$
RewriteRule ^ - [R=503,L]
</IfModule>
# END 緊急メンテナンス

maintenance.html

maintenance.html以外へのリクエストはすべて503になるので、別のファイルのCSSや画像は読み込めません。CSSはファイルの中に直接書くのがポイントです。検索エンジンに登録されないようnoindexも付けます。

<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow">
<title>メンテナンス中 | サイト名</title>
<style>
  body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;background:#f7f7f5;color:#333;font-family:"Hiragino Sans","Noto Sans JP",sans-serif;line-height:1.9;padding:24px;box-sizing:border-box}
  main{max-width:560px;text-align:center}
  h1{font-size:1.4rem;font-weight:600;margin:0 0 1.2em}
  p{margin:0 0 1em;font-size:.95rem}
</style>
</head>
<body>
<main>
  <h1>ただいまメンテナンス中です</h1>
  <p>サーバーの安全対策のため、一時的にサイトの公開を停止しております。</p>
  <p>ご不便をおかけし申し訳ございません。<br>再開まで今しばらくお待ちください。</p>
</main>
</body>
</html>

Xserverのファイルマネージャーも管理画面と一緒に止まっていたので、アップロードはFTP(またはSSH)で行いました。作業の前に、本番の.htaccessをダウンロードしてバックアップを取っておきます。

これって意味があるの?

設定しながら自分でも考えましたが、結論としては 意味があります。

今回の2件は、どちらも「PHPがリクエストを処理し始めた段階」で突かれるタイプです。.htaccessの503はWebサーバー(Apache)の段階で返されるので、外からのリクエストがPHPまで届きません。PHPが動かなければ、脆弱性を突かれようがありません。

ただし、これはApacheで.htaccessが読まれるサーバーだから使える方法です。Nginxで動くマネージドホスティングでは.htaccessはそもそも読まれず、同じ手は使えませんでした。その場合の判断は、PHPを自分で上げられない環境でどう備えたか にまとめています。

限界もあります。

  • 同じサーバーアカウントにほかのドメインがある場合:Xserverでは、同じアカウントのドメインは同じユーザーの権限で動きます。ほかのサイトのPHPが乗っ取られると、このサイトのファイルも書き換えられるので、1サイトだけ止めても守り切れません
  • サイト全体が止まる:お問い合わせフォームも含めて止まるので、クライアントの了承が必要です。503とRetry-Afterを返しているので、1日程度ならSEOへの影響はほぼありません
  • 管理画面にも入れなくなる:必要なら、自分のIPだけ通す行を有効にします

クライアントに「夜間から翌朝まで止めてもよい」と了承をもらえたので、Xserverの状況が分かるまで止めておくことにしました。

7. 修正の適用を確かめて、元に戻す

10月22日、XserverでPHPが8.3.36に更新されたことを、サイトヘルスで確かめました。

元に戻すときの手順は次のとおりです。

  1. サイトヘルスで、PHPのバージョンが修正版(今回は8.3.36)になっていることを確かめる
  2. .htaccessから# BEGIN 緊急メンテナンス〜# END 緊急メンテナンスのブロックを丸ごと消す
  3. maintenance.htmlを消す(残しておくと、このURLだけ誰でも見られる状態が続く)
  4. サイトを確かめる

今回は修正版の適用を確かめてからブロックを外し、次の3つを確かめました。

  • 主なページを順に開いて、どこかで503やエラーが残っていないか
  • レイアウトが崩れていないか(メンテナンス中にキャッシュされた表示が残っていないか)
  • お問い合わせフォームが、確認画面から送信まで通るか

どれも問題なく、通常の公開に戻りました。

8. ローカルのDockerも忘れずに

ローカルのwordpress:php8.3-apacheはmod_phpなので、構成としてはいちばんリスクが高い部類です。ただ、127.0.0.1でしか受け付けない設定なので、急ぐ必要はありません。

公式のイメージに修正版が入ったら(通常は公開から数時間〜1日後)、次のコマンドで反映します。

docker compose build --pull wordpress && docker compose up -d
docker compose exec wordpress php -v

php -vで8.3.36以上になっていれば完了です。FROMに:latestやphp8.3と書いていても、--pullを付けて作り直さない限り、手元のイメージは古いままです。なお、down -vはデータベースのボリュームまで消してしまうので使いません。

9. 同じ時期に起きた、別の事件にも注意

php2shellとは関係ありませんが、ちょうど同じ時期に、バックアッププラグインUpdraftPlusの開発元が不正アクセスを受け、外部の管理ダッシュボード(UpdraftCentral)の接続キーが盗まれて、WordPressのサイトに裏口を仕込むのに使われたと報じられました。

UpdraftPlusを使っているサイトでは、あわせて次を確かめておくと安心です。

  • UpdraftPlusを1.26.13(無料版)/2.26.13(有料版)以降に更新する
  • UpdraftCentralとつないでいないか確かめ、使っていなければ接続キーを削除する
  • 見覚えのない管理者ユーザーや、見覚えのないプラグインが増えていないか確かめる

まとめ:次に同じことが起きたときのチェックリスト

  • 告知を読んで、公開日時を日本時間に直す(php.netはUTCで書かれている)
  • リポジトリで、ローカルのPHP・WordPress・テーマ・プラグインのバージョンを棚卸しする
  • 本番のPHPのバージョンを、細かい番号まで確かめる(パネルが使えなければWordPressのサイトヘルス)
  • 本番のバックアップを、データベースも含めて取り、手元にもダウンロードしておく
  • 修正版が出たら、自分のバージョンが対象か、公式の修正があるバージョンかを判断する
  • サポートが切れたバージョンなら、サポート中のバージョンへ切り替える
  • 修正が適用されるまでは、パネルのphp.ini設定で緩和策を入れる
  • それもできない状況なら、.htaccessでの緊急メンテナンスを検討する(同じアカウントのほかのドメインにも注意)
  • ローカルのDockerイメージもbuild --pullで更新する
  • 修正版になったことを確かめてから、メンテナンスや緩和策を元に戻す

参考