Amazon Lightsailのphp2shell対応をClaude Codeにほぼおまかせしてみた

2026年10月21日、PHPに「認証なしでリモートからコードを実行できる」脆弱性が2件公表されました。通称 php2shell です。このブログは Amazon Lightsail の Bitnami 版 WordPress で動いていたので、さっそく対応が必要になりました。

今回は調査から移行、パッチ適用までを Claude Code(デスクトップアプリ)にほぼおまかせしてみました。私がやったのは、判断して承認すること、ブラウザ SSH にコマンドを2回貼ったこと、管理画面とメールの動作確認くらいです。

結果はこうなりました。

対応前対応後
サーバーLightsail の Bitnami 版 WordPressLightsail 版 WordPress(Bitnami なし)
OSDebian 11(LTS 終了済み)Debian 12
PHP8.2.7(Bitnami 同梱で更新手段なし)8.2.35(Debian の修正版)
更新手動夜間に自動更新、必要なら自動再起動

道中で2回サイトを止めてしまったので、そのあたりも含めて残しておきます。

何が起きたか

PHP 公式の発表はこちらです。

https://www.php.net/archive/2026.php

  • CVE-2026-109618 と CVE-2026-109619 の2件で、どちらも CVSS v4.0 で 9.2
  • 修正版は 8.5.12 / 8.4.27 / 8.3.36 / 8.2.35
  • PHP-FPM、Apache の mod_php、php-cgi など、Web で動いている PHP はすべて対象。中でも mod_php が一番危ないとされていて、アプリのコードが動く前に攻撃が成立する実例が示されています
  • 8.1 以前は本家からのリリースなし。7.4 / 8.0 / 8.1 向けにはソースコードのパッチだけが公開されています

公式の発表は日本時間 23:00 予定でしたが、事前告知の段階で「重大な脆弱性が来る」と分かっていたので、昼のうちから動き始めました。

まずは環境を見てもらった

Chrome で Lightsail のコンソールを開いた状態で「ここを見ながら対応を考えて」と頼んだところ、画面から Bitnami 版のインスタンスだと判別してくれました。そこから SSH で読み取りだけの調査をしてもらった結果がこちらです。

  • OS は Debian 11。LTS も 2026年8月で終わっている
  • PHP は 8.2.7。Bitnami が /opt/bitnami/php に同梱しているもので、apt upgrade では更新されない
  • Apache や MariaDB も 2023年当時のまま

そしてLightsail の Bitnami 版ブループリントは、2026年5月19日で更新が止まっています。

https://docs.aws.amazon.com/lightsail/latest/userguide/amazon-lightsail-faq-bitnami-blueprints.html

つまり、PHP 本家が修正版を出しても、このサーバーには当てる手段がありません。私は最初「Debian の修正を待つのと、サーバーを移行するのは何が違うの?」と聞いたのですが、返ってきた答えは「今のサーバーの PHP は Debian のものではないので、Debian が修正を出しても届かない」でした。言われてみればそうですね。

侵害されていないかのチェック

移行の前に、すでにやられていないかを確認してもらいました。どれも読み取りだけです。

  • wp core verify-checksums で WordPress 本体のファイルを公式と照合
  • wp plugin verify-checksums --all でプラグインを照合。不一致はキャッシュファイルなど説明がつくものだけ
  • uploads フォルダに PHP ファイルが置かれていないか
  • eval(base64_decode( のような難読化コードがないか
  • 管理者ユーザー、SSH の公開鍵、cron、sudoers に見覚えのない変更がないか
  • 不審なプロセスや外への通信がないか
  • アクセスログに攻撃が成功した跡がないか

結果は痕跡なしでした。

暫定策

この時点では非公式情報でしたが、「セッションのアップロード進捗機能を止めると一部を防げる」という話があったので、旧サーバーの php.ini に1行入れました。

session.upload_progress.enabled = Off

あとで出た公式発表でも、2件のうち CVE-2026-109619 の確認済みの経路はこれで防げる、と書かれていました。ただし完全な対策ではないので、本命はあくまで修正版を当てることです。

Lightsail 版 WordPress へ引っ越し

最初「同じスペックで新しいインスタンスを立てて、スナップショットから戻して IP を付け替えればいいよね」と言ったのですが、これは止められました。スナップショットから戻すと中身は Bitnami 版のままなので、移行する意味がありません。新しいインスタンスを作って、データだけを移します。

移行先は、Lightsail が自前で用意している WordPress ブループリントです。中身は Debian 12 で、PHP は Debian のパッケージ(Apache + mod_php)、最初から unattended-upgrades が有効になっていました。

やったことはこんな感じです。

  1. 旧サーバーの手動スナップショットを取る
  2. 新しいインスタンスを作る(東京、512MB プラン)
  3. 旧サーバーの DB を mysqldump で書き出し、wp-content と一緒に SSH で転送する。キャッシュと All-in-One WP Migration の古いバックアップは除いて、合計 540MB ほど
  4. 新サーバーで DB を取り込み、wp-content を展開し、WordPress 本体を 7.1.3 に上げる
  5. 足りない PHP 拡張(imagick、intl など)と Redis を入れる
  6. wp-config.php を旧サーバーに合わせる
  7. アラーム、自動スナップショットの時刻、SSH の接続元を旧サーバーに合わせる
  8. 主要なページを新旧で比べる

このブログは Lightsail のディストリビューション(CDN)経由で配信していて、オリジンへは HTTP で取りに来ます。そのため wp-config.php には、CDN 経由なら HTTPS とみなす処理を入れています。

if ( isset( $_SERVER['HTTP_CLOUDFRONT_FORWARDED_PROTO'] ) && $_SERVER['HTTP_CLOUDFRONT_FORWARDED_PROTO'] === 'https' ) {
    $_SERVER['HTTPS'] = 'on';
}

切り替えのとき、ディストリビューションのオリジンを新サーバーに変えようとしたら「オリジンには静的 IP の付いたインスタンスが必要」と言われました。結局、旧サーバーから静的 IP を外して新サーバーに付けたところ、オリジンも IP に追従して新サーバーに切り替わりました。止まったのは1〜2分です。

ハマったところ

自動更新中に MariaDB が固まって 504(約8分)

切り替えから30分ほどして、サイトが 504 を返し始めました。

原因は、新サーバーで最初の unattended-upgrades が動いて MariaDB を更新したことでした。このブループリントの MariaDB は、接続が来たら自動で起動するソケット起動(mariadb.socket)になっています。更新に伴う再起動の最中に Web から接続が来て、新しいプロセスが起動途中のまま固まりました。systemd は 15 分待ってから強制終了する設定なので、それまで DB が止まったままになっていました。

Claude Code は固まったプロセスを強制終了しようとしましたが、auto mode の権限判定で止められたので、ブラウザ SSH で実行して復旧しました。再発防止として、ソケット起動をやめて、固まっても2分で自動復旧する設定にしています。

sudo systemctl stop mariadb.service mariadb.socket
sudo systemctl disable mariadb.socket
sudo systemctl mask mariadb.socket
sudo mkdir -p /etc/systemd/system/mariadb.service.d
sudo tee /etc/systemd/system/mariadb.service.d/override.conf <<'EOF'
[Service]
TimeoutStopSec=120
Restart=on-failure
RestartSec=5s
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now mariadb.service

ついでに、ソケット起動のときは 3306 番ポートが全インターフェースで待ち受けていましたが、これで 127.0.0.1 だけになりました。

画像と CSS だけが無限リダイレクト

ページは表示されるのに、画像や CSS がじわじわ壊れていきました。

Lightsail 版ブループリントの Apache には、最初からこんなルールが入っています。

RewriteCond %{HTTP:CloudFront-Forwarded-Proto} !=https
RewriteCond %{HTTP:X-Forwarded-Proto} !=https
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301,NE]

ディストリビューションは、ページ本体には CloudFront-Forwarded-Proto ヘッダーを付けて送ってきます。一方で、キャッシュ対象の wp-content/* と wp-includes/* には付けてきません。そのため静的ファイルだけが「同じ URL へ 301」を繰り返し、しかもその 301 が CDN に最大1日キャッシュされていました。旧サーバーの CDN キャッシュが切れたファイルから順に壊れていた、というわけです。

この3行をコメントアウトして(ここも auto mode に止められたので小生が実行)、ディストリビューションのキャッシュをリセットして直りました。HTTPS への誘導はディストリビューション側でやっているので問題ありません。その後、ページ内の画像・CSS・JS を 296 件確認して、全部 200 でした。

権限の一括変更で画像の最適化が止まっていた

移行のときに wp-content の全ファイルを 664 にそろえたせいで、EWWW Image Optimizer に同梱されている実行ファイルの実行権限が外れていました。表示には影響しませんが、新しくアップロードした画像が圧縮されなくなります。これは Claude Code 自身が「自分の作業ミスです」と見つけて直しました。

Download Monitor のファイルパスが旧サーバーのまま

ダウンロードのリンクで「ファイルパスに誤りがあります」と出ていました。DB に /opt/bitnami/wordpress/... という旧サーバーの絶対パスがそのまま残っていたのが原因です。

もう1件は URL 形式で登録されていましたが、こちらは www.danishi.net 側から開くと外部ファイル扱いになり、403 で失敗していました(移行前からの問題)。2件とも新サーバーの絶対パスで登録し直しています。

DB 全体で /opt/bitnami を検索してもらったら、ブログカード(Pz-LinkCard)のサムネイル保存先も旧パスのままだったので、ついでに直しました。

メンテナンスフリーにする

「DB の再起動を自分でやるのは嫌」とお願いしたら、こんな構成にしてくれました。

サーバーの時計は UTC なので、標準の「6:00」だと自動更新が日本時間の昼に動きます。深夜 3:00 に移しました。

sudo mkdir -p /etc/systemd/system/apt-daily-upgrade.timer.d
sudo tee /etc/systemd/system/apt-daily-upgrade.timer.d/override.conf <<'EOF'
[Timer]
OnCalendar=
OnCalendar=*-*-* 18:00
RandomizedDelaySec=30m
EOF
sudo systemctl daemon-reload
sudo systemctl restart apt-daily-upgrade.timer

カーネルの更新などで再起動が必要なときは、深夜 4:00 に自動で再起動します。

sudo tee /etc/apt/apt.conf.d/52unattended-upgrades-local <<'EOF'
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-WithUsers "true";
Unattended-Upgrade::Automatic-Reboot-Time "19:00";
EOF

更新のあと、古いライブラリを掴んだままのサービスを自動で再起動する needrestart も入れました。これを入れた理由は次の章に書きます。

sudo mkdir -p /etc/needrestart/conf.d
sudo tee /etc/needrestart/conf.d/50-autorestart.conf <<'EOF'
$nrconf{restart} = 'a';
$nrconf{kernelhints} = 0;
$nrconf{ucodehints} = 0;
EOF
sudo apt-get install -y needrestart

修正版が出た夜

時刻できごと
22:47PHP 本家が修正版を公開(予定の 23:00 より少し早かった)
23:05Debian の配布サーバーに 8.2.35-1~deb12u1 のソースが登録される
23:45sury.org にはすでに 8.2.35 があったが、公式の Debian を待つことにした
0:04寝たいので、20分おきにセキュリティ更新を当てる一時タイマーを入れてもらう
1:06タイマーが 8.2.35 を自動で適用
1:12Apache が古い PHP を掴んだままだったので再起動

bookworm は今は LTS の期間なので、更新は Debian LTS チームから出ます。ソースの登録からサーバー用パッケージの公開までは2時間ほどかかりました。

一番の学びは 1:12 の件です。パッケージが 8.2.35 になっても、動いている Apache は削除済みの古い libphp8.2.so を掴んだままで、脆弱なコードで動き続けていました。Claude Code は事前に「PHP を更新すれば Apache も自動で再起動される」と説明していましたが、これは誤りでした。ただ、適用後の確認で自分で気づいて、すぐ再起動してくれました。脆弱なままだったのは6分ほどです。

確認はこれでできます。(deleted) と出たら、古いコードのまま動いています。

sudo grep libphp /proc/$(systemctl show -p MainPID --value apache2)/maps

この件があったので needrestart を入れて、次からは更新後に自動で再起動されるようにしました。

並行して攻撃検知のシェルも3分おきに流してもらっていましたが怪しい動きはなかったようです。

Claude Code におまかせしてみて

始めたのが昼の 15 時ごろで、16 時すぎには移行が終わり、翌 1 時すぎに修正版が当たりました。途中で寝ていたので、私の作業時間はそこまで長くありません。

この記事が私のように個人でWordPressの運用をしている方の助けになれば幸いです。


これからのWordPressセキュリティ本格講座