WordPressのパーマリンクを年月日時分秒から投稿IDに変更する


2026年9月22日、WordPressの深刻な脆弱性「CVE-2026-87902」が発表された。GHSA(GitHub Security Advisories)のCVSSスコア(v4.0)は9.2で、これはやばい。サポート中の全バージョンに対して修正版がリリースされたことからも、事の重大性が認識出来る。

発見者のブログによると、有効なテーマに「page-」で始まる最上位ディレクトリが含まれていること、投稿ID(page_id)でページが公開されていることなどが条件のようで、私が運営しているサイトすべてに当てはまるわけではないが、今後のこともあるのでバージョンアップに踏み切ることにした。

「WordPressは最新版にしなくても、過去メジャーバージョンのセキュリティリリース最新版にしていれば脆弱性はない」に書いたとおり、これまでWordPressは過去バージョンに対してもセキュリティリリースを必ず出してきたので、新機能を求めないならそれで充分だった。しかし25年6月をもって4.1~4.6に対するサポートを終了してしまい、主に4.1.xを使用していた私はどうしようかと考えていたところだった。

まずは、修正版が提供されている最下位バージョンである4.7.37にすることを目指す。急に上位バージョンにするとUIに慣れていないし、そもそも個人サイトには不要な機能が多く、逆に使いづらい。

  1. 「wp_social_bookmarking_light」の削除

    準備段階で身軽になるため、アップデートが停止して脆弱性が指摘されていたプラグイン「wp_social_bookmarking_light」を削除したが、少し手こずったのでメモしておく。

    「wp_social_bookmarking_light」を停止させると、記事タイトルしか表示されない。プラグインを入れたことではなく、停止したことで発生する場合は、手動でテーマにPHPコードを追加しているはず。記事の件名しか表示されないときは、single.php、page.php、content.phpなどのテンプレートからプラグインを呼び出していないか確認すればよい。確認すると、下記を追加していたので削除して解決。

    <?php wp_social_bookmarking_light_output_e(null, get_permalink(), the_title("", "", false)); ?>
    
  2. パーマリンクにスラッグか投稿IDが含まれないといけない

    4.7.37に手動アップデートしたところ、トップページに記事タイトルとmoreタグで区切った位置まで表示されるが、そこから個別ページを開いても記事全体ではなく、moreタグの位置までしか表示されない。トップページが正しく表示されるので気づくのが遅れ、手戻りが生じた。最初はmoreタグの障害だと思ったが、個別ページ自体のパーマネントリンクが有効になっていないようだ。

    調べたところ、20年5月に5.4.1がリリースされた際、URLをカスタム構造にしている場合は、記事名(postnameまたはスラッグ)か投稿ID(post_id)が必ず含まれないといけないことになったらしい。時間をURLにしていると、大規模サイトでバッティングが起こる可能性があるからだそうだが、個人サイトには関係ないだろう。4.7.37は5.4.1より前だが、今回の修正版にも反映されているのだろう。20年5月と言えば、コロナ禍で大変な時期だったが、WordPressも世界でも大事件が起きていたのだ。

    パーマリンクを途中で変更するのは本来あり得ないことで、財産である被リンクが全部切れてしまう。しかし変更しないと、今後も脆弱性に対応することが出来ない。6年半遅れで悩んだ結果、URLを投稿IDを使ったパーマリンクに変更し、旧URLへのアクセスはなんらかの方法でリダイレクトさせることにした。

  3. パーマリンクを変更し、個別ページを正しく生成させる

    4.7.37にする前に、4.1系の最終版4.1.42でテスト。パーマリンクに「年/月日時秒」を使っている当サイトのURLを投稿IDに変更する。[設定]の[パーマリンク]で「共通設定」を「カスタム構造」から「数字ベース」にする。CMSを始めたころは、個別ページのURL末尾には拡張子(.phpや.html)がないと落ち着かない感覚だったが、いまは数字で終わってもなんとも思わなくなっている。

    (カスタム構造)
    %year%/%monthnum%/%day%%hour%%minute%%second%.html
     ↓
    (数字ベース)
    /archives/123

    これでパーマリンクが変わり、moreタグが有効になり、個別ページが正しく表示されることを確認。パーマリンク設定を変えても、DB内のデータを書き換えるわけではなく、いつでも旧URLに戻せることも確認。

  4. 旧URLから新URLへリダイレクトするPHPコード

    Chat-GPTと対話しながら、テーマのfunctions.phpに追加するコードを書いてもらった。これは「年/月日時秒.html」からのパターンなので、異なる場合は各自でアレンジしてほしい。

    /**
     * 旧投稿URLを新しい投稿ID URLへ301リダイレクト
     *
     * 旧URL形式:
     * /%year%/%monthnum%%day%%hour%%minute%%second%.html
     *
     * 例:
     * /2026/1004152931.html
     *
     * ↓
     *
     * /archives/2931/
     *
     * ※ 現在のパーマリンク設定は
     *   /archives/%post_id%/
     *   に変更済みであることを前提とする。
     */
    
    add_action('template_redirect', function () {
    
        // 管理画面・Ajax・REST APIは対象外
        if (
            is_admin() ||
            wp_doing_ajax() ||
            (defined('REST_REQUEST') && REST_REQUEST)
        ) {
            return;
        }
    
        /*
         * REQUEST_URIからパスだけを取得
         *
         * 例:
         * /2026/1004152931.html?foo=bar
         *
         * ↓
         *
         * /2026/1004152931.html
         */
        $request_uri = wp_unslash($_SERVER['REQUEST_URI'] ?? '');
    
        $path = wp_parse_url($request_uri, PHP_URL_PATH);
    
        if (!$path) {
            return;
        }
    
        /*
         * 旧URL形式に一致するか確認
         *
         * %year%        = 4桁
         * %monthnum%    = 2桁
         * %day%         = 2桁
         * %hour%        = 2桁
         * %minute%      = 2桁
         * %second%      = 2桁
         *
         * 例:
         * /2026/1004152931.html
         */
        if (
            !preg_match(
                '#^/(\d{4})/(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\.html$#',
                $path,
                $matches
            )
        ) {
            return;
        }
    
        /*
         * URLから投稿日時を取得
         */
        $year   = (int) $matches[1];
        $month  = (int) $matches[2];
        $day    = (int) $matches[3];
        $hour   = (int) $matches[4];
        $minute = (int) $matches[5];
        $second = (int) $matches[6];
    
        /*
         * 日付として正しいか確認
         *
         * 例:
         * 2026/13/99/99/99/99
         * のような不正URLをDB検索しない。
         */
        if (!checkdate($month, $day, $year)) {
            return;
        }
    
        if (
            $hour > 23 ||
            $minute > 59 ||
            $second > 59
        ) {
            return;
        }
    
        /*
         * WordPressのDBから投稿を検索
         */
        global $wpdb;
    
        $post_date = sprintf(
            '%04d-%02d-%02d %02d:%02d:%02d',
            $year,
            $month,
            $day,
            $hour,
            $minute,
            $second
        );
    
        $post_id = $wpdb->get_var(
            $wpdb->prepare(
                "
                SELECT ID
                FROM {$wpdb->posts}
                WHERE post_date = %s
                  AND post_type = 'post'
                  AND post_status = 'publish'
                LIMIT 1
                ",
                $post_date
            )
        );
    
        /*
         * 該当する投稿がなければ何もしない
         */
        if (!$post_id) {
            return;
        }
    
        /*
         * 新URL
         *
         * /archives/2931/
         */
        $new_url = home_url(
            '/archives/' . absint($post_id) . '/'
        );
    
        /*
         * 301リダイレクト
         */
        wp_safe_redirect($new_url, 301);
    
        exit;
    });
    

    旧URL「年/月日時秒.html」でアクセスすると、「/archives/記事ID」にリダイレクトされることを確認。

  5. 4.7.37へアップデート。

    4.1.42から「CVE-2026-87902」の脆弱性対策がされている4.7.37へ手動アップデート。

    動作確認したところ、新規投稿画面にメニューが出なくなったり、「メディアを追加」ボタンが無反応になる現象。プラグイン「WP Multibyte Patch」を停止すると解消したので、現行の2.2から4.7系に対応している2.8.5に手動アップデートして復旧。

現在ここまで。


Leave a Comment.