13年放置したWordPressサイトを、いまの安全なサイトに救出した話

2013年に作ったままのWordPressサイト「イノベーションの軸」(wonder9.com)が、PHPの更新をきっかけにまったく表示されなくなりました。記事も画像も失わず、その日のうちに最新のWordPress 7.1.3で復旧させた経緯をまとめます。

きっかけ:WordPressとPHPに相次いだ重大な脆弱性

2026年の秋は、WordPressで動くサイトにとって厳しい季節でした。数週間のうちに、WordPress本体と、それを動かすPHPの両方に、外部からサーバーを乗っ取られかねない脆弱性が見つかったからです。

公開 対象 内容 対策
2026年10月21日 PHP(通称 PHP2shell、CVE-2026-109618 / 109619) 外部から任意のコードを実行されるおそれ。CVSS 9.2。IPAがパッチ公開前に注意喚起 PHPの修正版(セキュリティパッチ)を適用
2026年9月22日 WordPress本体 4.7.0〜7.1.1(CVE-2026-87902) ログイン不要でサーバー内のPHPファイルを読み込ませられる。条件がそろうとコード実行。CVSS 9.2 WordPress 7.1.2 以降、または各系列の修正版へ更新

このサイトを預けているお名前.com レンタルサーバー「共用サーバーSD」では、10月22日にPHPの脆弱性への対応が告知され、ファイルマネージャーとWebメールが一時停止されました。10月23日には、PHP 7.4と8.4へのセキュリティパッチ適用が完了したと発表されています。PHP 7.3以下は緩和策のみで、7.4以上への変更が推奨されました。

これを受けてサイトのPHPを 8.4 に切り替えたところ、wonder9.com はまったく開かなくなりました。

13年放置していたサイトの姿

サイトは2013年にお名前.comの共用サーバーSDで立ち上げ、コントロールパネルの「WordPressインストール」(かんたんブログインストール)で入れたものです。その後はごくたまに更新しただけで、中身はほぼ当時のままでした。調べてみると、ひとつのデータベースに2つのWordPressが同居していました。

場所 WordPress テーブル名の頭 中身
www.wonder9.com(本体) 4.9.26 wp_ 固定ページ6、投稿33、画像10。テーマは Twenty Twelve
www.wonder9.com/stuff(スタッフ用) 3.5.2 wp2_ 固定ページ2(うち1つは初期のサンプル)

ほかにも、当時の資料(MaenoLab.pdf、27MB)や、記事一覧を出すためにページの中にPHPを直接書く「Exec-PHP」という古いプラグインが使われていました。コメント6件は、すべて承認待ちのままたまったスパムでした。

2013年のWordPressとプラグインは、2026年の脆弱性への修正が届かないどころか、今のPHPでは動かないものばかりでした。「動いているから大丈夫」ではなく、「古いまま動いていること自体が危険」な状態だったわけです。

なぜ表示されなくなったのか

WordPressは PHP というプログラミング言語の上で動いています。PHPは版が上がるたびに古い書き方を廃止するので、2013年に書かれたプログラムは新しいPHPでは動きません。

PHPとWordPressの組み合わせごとの結果:壊れたのはプログラムとPHPの組み合わせで、データは無事だった
PHPとWordPressの組み合わせごとの結果

壊れたのは「古いプログラム×新しいPHP」という組み合わせだけです。記事や設定が入ったデータベースはどの場合も無事だったので、プログラム側を最新に入れ替えるだけで復旧できました。

救出の進め方

方針は「まず控えを取る、本番に触る前に手元で試す」の2つだけです。デザインは変えてもよいと決めていましたが、結果的には新しいサーバーを用意するのではなく、同じサーバーの同じ場所でプログラムだけを入れ替える方法を選びました。

救出の流れ:控えを取り、手元で試してから本番を入れ替えた
救出の流れ(6つの手順)

1〜3で取った控えがあるので、どの段階でも元に戻せます。5では古いフォルダを消さずに名前を変えておき、表示されなければ戻せるようにしました。データベースはサーバー上のものをそのまま使い、wp-admin/upgrade.php で最新の形に更新しています。スパムコメントの削除や、PHPを書いていたページの置き換えは、初回のアクセスで1回だけ動く小さなプログラムに任せました。

作業の実際とつまずいたところ

今回は AI(Claude)と相談しながら進めました。Claude がブラウザでコントロールパネルやサイトの状態を確かめ、私がFTPでファイルを出し入れする、という分担です。

管理者でログインしないと何も見えない

お名前.comのコントロールパネル(cp.rentalserver.jp)に、最初はメールアドレスのアカウントで入っていました。この権限では「PHPバージョン変更」「MySQL」「FTPアカウント」がすべて押せません。サーバーの管理者アカウントで入り直して、ようやく中身を確認できました。

FileZilla がウイルス対策に止められた

FTPソフトの定番 FileZilla を入れようとしたところ、Windows が「望ましくない可能性のあるソフトウェア」として停止しました。標準の配布物に広告ソフトが同梱されていたためです。広告同梱のない WinSCP に切り替え、サイトのファイル一式(約144MB、6,064ファイル)を手元にダウンロードしました。

phpMyAdmin がない中でのデータベースの書き出し

共用サーバーSDのMySQLはサーバー内部からしか接続できず、コントロールパネルにも書き出し機能が見当たりませんでした。そこで、書き出し専用の小さなPHPファイルを一時的に置き、ブラウザから1回だけ実行して、全データを .sql ファイル(約1.2MB)として受け取りました。接続先は wp-config.php から読み取り、推測されにくい合言葉がないと動かないようにして、使い終わったらすぐ削除しています。

「元に戻す」応急処置は効かなかった

途中でPHPを 7.4 に戻してみましたが、「このページは動作していません」のままでした。WordPress本体だけでなく、2013年頃のプラグインも動かなかったためです。PHPを古くするのではなく、WordPressを新しくするのが正解でした。

古いフォルダが消えない

入れ替え後、退避した古いフォルダ(wp-old)をWinSCPで削除すると、中身は消えたのにフォルダだけが「ディレクトリではありません」と残りました。FTPでは、中身を先にすべて消してからフォルダを消すので、このようなことが起きるようです。古いプログラムそのものはすべて削除できています。

技術解説:SQLでデータベースを書き出し、中身を確かめる

WordPressの記事や設定は、すべてMySQLのテーブルに入っています。画面が壊れても、テーブルさえ無事ならサイトは取り戻せます。今回使ったSQLを、仕組みが分かる程度に紹介します。

1. 書き出し:3つのSQLをテーブルごとに繰り返す

書き出し用のPHPがやっているのは、次の繰り返しだけです(要点だけを書き直したもので、接続情報や合言葉の部分は省いています)。

$db->set_charset('utf8');                       // 保存時と同じ文字コードで読む
echo "SET NAMES utf8;\nSET FOREIGN_KEY_CHECKS=0;\n";
$tables = $db->query('SHOW TABLES');             // (1) テーブルの一覧
while ($t = $tables->fetch_row()) {
  $name = $t[0];
  $c = $db->query("SHOW CREATE TABLE `$name`")->fetch_row();   // (2) 設計図
  echo "DROP TABLE IF EXISTS `$name`;\n" . $c[1] . ";\n";
  $r = $db->query("SELECT * FROM `$name`", MYSQLI_USE_RESULT); // (3) 中身
  while ($row = $r->fetch_row()) {
    $v = array_map(fn($x) => $x === null ? 'NULL'
         : "'" . $db->real_escape_string($x) . "'", $row);
    echo "INSERT INTO `$name` VALUES (" . implode(',', $v) . ");\n";
  }
}
  • SHOW TABLES:データベース内のテーブル名を全部取ります。今回は wp_ が12個、wp2_ が11個の計23個でした。
  • SHOW CREATE TABLE:テーブルを作り直すための CREATE TABLE 文をサーバー自身に書いてもらいます。列の型や索引がそのまま写せます。
  • SELECT * → INSERT:1行ずつ読んで INSERT 文に置き換えます。real_escape_string は、本文中の ‘ や改行が SQL を壊さないようにエスケープする役目です。
  • MYSQLI_USE_RESULT:結果をメモリに貯め込まず、流しながら出力します。共用サーバーの小さなメモリでも大きなテーブルを扱えます。
  • SET NAMES utf8 / FOREIGN_KEY_CHECKS=0:読み戻すときに文字化けや順番待ちのエラーが起きないように、先頭で宣言しておきます。

出来上がった .sql は、こんな形のテキストです。最後の行に入れておいた「END OF DUMP」があるのを確認して、途中で切れていないことを確かめました。

SET NAMES utf8;
SET FOREIGN_KEY_CHECKS=0;

DROP TABLE IF EXISTS `wp_posts`;
CREATE TABLE `wp_posts` ( `ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT, ... );
INSERT INTO `wp_posts` VALUES ('1','1','2013-07-01 02:02:13', ... );
...
-- END OF DUMP

2. 手元のMySQLに読み込んで、中身を数える

.sql を作業用のMySQL(MariaDB)に読み込み、何がどれだけあるかを数えました。WordPressは記事も固定ページも画像も、すべて wp_posts の1行として持ち、post_type 列で種類を分けています。

SELECT post_type, post_status, COUNT(*)
  FROM wp_posts
 GROUP BY post_type, post_status;
post_type post_status 件数 意味
post publish 33 公開中の投稿
page publish 6 公開中の固定ページ
attachment inherit 10 画像などの添付ファイル
nav_menu_item publish 6 メニューの項目
revision inherit 213 編集履歴(移行時に削除)
post auto-draft 1 自動保存の下書き(削除)

サイトの場所や使っているテーマ、有効なプラグインは wp_options に入っています。siteurl が /wp、home が / になっていて、「プログラムは /wp フォルダ、公開アドレスはトップ」という構成だと分かりました。入れ替えを「同じ場所で」行えばよいと判断できたのは、この2行のおかげです。

SELECT option_name, option_value
  FROM wp_options
 WHERE option_name IN ('siteurl', 'home', 'template', 'active_plugins');

3. 新しいPHPで動かない記事を探す

Exec-PHP は本文に PHP のコードを直接書かせるプラグインです。これを外すと、コードがそのまま画面に出てしまいます。本文に「<?php」を含む記事を探すと、「グローバル社会の政治経済キーワード30」の1ページだけでした。このページは WordPress 標準の「最新の投稿」ブロック(カテゴリー指定・30件・古い順)に置き換えています。

SELECT ID, post_title
  FROM wp_posts
 WHERE post_status = 'publish'
   AND post_content LIKE '%<?php%';

4. 日本語が壊れていないかをバイトで確かめる

最初に結果を表示したとき、サイト名が「?????????」と出てひやりとしました。文字化けがデータそのものの問題か、表示側の問題かは、中身をバイト列で見れば分かります。

SELECT option_value, HEX(LEFT(option_value, 6))
  FROM wp_options
 WHERE option_name = 'blogname';
-- 結果:イノベーションの軸 | E382A4E3838EE38399E383BCE382B7E383A7

E382A4 は UTF-8 の「イ」、E3838E は「ノ」です。データは正しく、画面に出すときの文字コード指定が抜けていただけだと分かりました。

5. 古いデータベースを最新の形へ

WordPressはデータベースの形の版を wp_options の db_version で管理しています。今回は 38590(WordPress 4.9 系)から 61833(WordPress 7.1 系)へ一気に上げました。本番では wp-admin/upgrade.php を開くだけで、必要な ALTER TABLE などを WordPress が順に実行してくれます。事前に手元のコピーで同じ更新を試し、全ページが表示できることを確かめてから本番に臨んでいます。

復旧の結果

入れ替え後、www.wonder9.com は PHP 8.4 上の WordPress 7.1.3 で正常に表示され、記事・固定ページ・画像・PDF はすべてそのまま残りました。

項目 作業前 作業後
表示 「このページは動作していません」 正常に表示
PHP 8.4(サイトが対応できず) 8.4(お名前.comのセキュリティパッチ適用済み)
WordPress 本体 4.9.26 と 3.5.2 7.1.3(CVE-2026-87902 修正済み)
テーマ Twenty Twelve(古い版) Twenty Twelve(最新版、見た目はそのまま)
プラグイン Jetpack、Exec-PHP など5個(2013年頃の版) WP Multibyte Patch、Akismet のみ
コメント スパム6件 0件
/stuff サイト WordPress 3.5 のまま、英語のエラーを表示 中身を本体の /re-start/ へ移し、残りはトップへ転送
復旧後のトップページ(Twenty Twelve)
復旧後のトップページ(Twenty Twelve)

デザインはまず従来と同じ Twenty Twelve で立ち上げました。管理画面でテーマを切り替えれば、最新の Twenty Twenty-Five のような見た目にもできます。

Twenty Twenty-Five にした場合の記事ページ
Twenty Twenty-Five にした場合の記事ページ

これからの保守と脆弱性対策

今回いちばんの教訓は、「放置した年数だけ、直すのが大変になる」ということです。13年分の差は一気に埋められましたが、次は差をためない仕組みにします。

定期的にやること

いつ やること どこで
自動 WordPress 本体のセキュリティ更新(標準で有効)。プラグインとテーマも自動更新にする 管理画面の「プラグイン」「外観>テーマ」で「自動更新を有効化」
毎月 管理画面にログインし、更新が溜まっていないか、サイトヘルスに警告がないかを見る 「ダッシュボード>更新」「ツール>サイトヘルス」
毎月 お名前.comのお知らせと障害・メンテナンス情報を確認する コントロールパネルのホーム画面
毎月と大きな更新の前 データベースと wp-content(特に uploads)を手元にバックアップする バックアップ用プラグイン、または WinSCP
注意喚起が出たとき WordPress や PHP の重大な脆弱性が報じられたら、その週のうちに更新を確認する IPA・JPCERT/CC の注意喚起、お名前.comのお知らせ
PHP の切り替え時 先にバックアップ、切り替え後に全ページを開いて確認する。一度捨てた古い版には戻せないことがある コントロールパネル「PHPバージョン変更」
毎年 使っていないプラグイン・テーマ・ユーザーを整理し、ドメインとサーバーの契約も確認する 管理画面、お名前.com Navi

攻撃されにくくするための考え方

  • 入れるものを減らす:プラグインもテーマも、入っているだけで攻撃の入口になります。今回は5個あったプラグインを2個に減らしました。
  • 作業用のファイルを残さない:データベース書き出し用のスクリプトや古い WordPress のフォルダは、使い終わったらすぐ消します。
  • ログインを守る:管理者は長く推測されにくいパスワードにし、二段階認証も検討します。使っていないアカウントは削除します。
  • サーバーの守りも使う:共用サーバーSDのコントロールパネルには「WAF設定」があります。不正なリクエストをサーバーの手前で止める仕組みです。
  • バックアップがあれば怖くない:今回落ち着いて作業できたのは、最初にファイルとデータベースを手元に確保したからです。

このサイトの保守方針

  • 更新はできるかぎり自動にし、月に1回は管理画面で状態を確かめる
  • 使わないプラグイン・テーマ・作業用のファイルは、サーバーに置かない
  • 大きな変更の前には、必ずデータベースとファイルの控えを取る
  • WordPress 側だけでなく、サーバー側の防御機能も併せて使う
  • 管理者アカウントは、強いパスワードと必要最小限の人数で守る
  • これからは定期的に記事も更新し、「放置しないサイト」にする

参考にした情報