
php2shell をきっかけに WordPress そのものも手を入れた
前回は WordPress を運用する nginx 側の設定を見直したが、WordPress そのものも手を入れたので今回はその内容について書いておく。
Table of Contents
大前提として
大前提の大前提だが、WordPress 本体とプラグインの随時更新は必須になる。
当ブログのような個人サイトなら多少動かなくても問題はないが、業務で WordPress を使っている場合は
- カスタマイズテンプレート
- 独自のプラグイン
といったものは WordPress や PHP のバージョンアップに随時対応できるよう、必ず保守契約を結んでおく必要がある。保守契約できないのであれば、静的 HTML を置くだけにしておいた方がいい。
それでも WordPress を使い続けるには
それでも WordPress を使い続けると決め、随時バージョンアップを続けるとなった場合、前回の内容は最低限やった方がいい。
いわゆるレンタルサーバだと前回の内容そのものが対応できないので、コンテナ化なり仮想サーバ化なりする必要がある。できないのであれば、WordPress を諦める選択肢も検討した方がいいだろう。
さらに WordPress 側もある程度手を入れる必要がある。ログイン周りはできるだけセキュアにした方がいいので、今回はその内容を紹介する。
二段階認証の導入
まず WordPress アカウントに二段階認証を導入する。Jetpack を導入して WordPress アカウントに移行している場合はここは対応しなくてもすでに WordPress アカウント側で対応されている。
WordPress アカウントを使わない場合は、Two Factor プラグインをインストールする。
Two Factor プラグインの設定で「認証アプリ」にチェックが入っていることを確認し、入っていない場合はチェックを入れて保存する。

次に、個人プロフィール画面の下の方に「2要素認証の設定」があるので、そこに表示されている QR コードを Google Authenticator などのスマホ認証アプリで読み込む。

Google Authenticator の場合は右下の「+」から「QR コードをスキャン」すればよい。

QR コードをスキャンすると WordPress の項目が追加されるので、そこに表示されている 6 桁の数字を WordPress 側の 3. にあるフォームに入力する。

数字は毎分 0 秒と 30 秒に更新されるので、時刻を秒単位で見つつ入力する必要がある。

「認証アプリを有効化」にチェックが入れば設定は完了となる。
API の認証必須化
WordPress の API はいくつかあるが、ユーザ一覧取得 API 経由でユーザの slug = WordPress にログインするための ID が漏えいしてしまう。つまり、WordPress への不正アクセスを防ぐ一つの鍵がないに等しい。
よって、slug を API 経由で取得できないようにする必要がある。
slug の確認
WordPress にログインしていない状態で https://ホスト名/wp-json/wp/v2/users にアクセスしてみて、ユーザ ID を含む文字列が落ちてくる。この状態が上で指摘した内容になる。
運用上、この情報はログインしている場合のみ取れればいいはずなので、認証を通すように WordPress を修正する。
ログインしていないと slug が取得できないようにする
以下内容を WordPress のディレクトリ以下にある wp-content/mu-plugins ディレクトリに置く。mu-plugins はデフォルトだと存在しないこともあるので、存在しない場合は別途作成する。
<?php
// REST の users 読み取りを、匿名のときだけ 401 で拒否(ルートは残す)
add_filter('rest_endpoints', function ($endpoints) {
foreach (['/wp/v2/users', '/wp/v2/users/(?P<id>[\d]+)'] as $route) {
if (empty($endpoints[$route])) {
continue;
}
foreach ($endpoints[$route] as $i => $handler) {
$methods = isset($handler['methods']) ? $handler['methods'] : [];
$is_get = is_array($methods)
? !empty($methods['GET'])
: (false !== stripos((string) $methods, 'GET'));
if (!$is_get) {
continue; // 書き込み系は元々要権限なので触らない
}
$original = isset($handler['permission_callback'])
? $handler['permission_callback'] : '__return_true';
$endpoints[$route][$i]['permission_callback'] = function ($request) use ($original) {
if (!is_user_logged_in()) {
return new WP_Error('rest_forbidden', 'Authentication required.', ['status' => 401]);
}
return is_callable($original) ? call_user_func($original, $request) : true;
};
}
}
return $endpoints;
});
// ?author=N による列挙を未ログイン時は 403
add_action('parse_request', function () {
if (!is_user_logged_in() && isset($_GET['author']) && is_numeric($_GET['author'])) {
status_header(403);
exit;
}
});
// author アーカイブ直アクセスも未ログインは 403
add_action('template_redirect', function () {
if (is_author() && !is_user_logged_in()) {
status_header(403);
exit;
}
});
ファイル名は何でもいいが、.php で終わるようにしておく。当サイトの場合は harden-enumeration.php とした。
slug が取れないことを確認する
ファイルをアップロードしたら、まずはサイトが問題なく表示されるかを確認する。
問題なく表示されれば、WordPress にログインしていない状態で https://ホスト名/wp-json/wp/v2/users にアクセスする。以下が表示されれば ok。
{"code":"rest_forbidden","message":"Authentication required.","data":{"status":401}}
次に、WordPress にログインし、既存の記事の「投稿者」をクリックし、以下のようにプルダウンメニューが表示され、かつ投稿者全員が表示されるかどうかを確認する。

表示されれば完了となる。
2026年10月時点では概ねここまでやれば少しはマシになるだろう。
PR
- nginx実践入門 WEB+DB PRESS plus
- マンガ+図解で基礎がよくわかる 情報セキュリティの教科書
- これからのWordPressセキュリティ本格講座: 「月1回・30分」のメンテナンスでサイトを安全に保つ!