2026年7月7日
2026年7月7日
WordPressのアクセス制御の不備(Broken Access Control)を防ぐ対策
WordPress の Broken Access Control(アクセス制御の不備)は、「ログインしているか」や「nonce が正しいか」だけを確かめて、「その人にその操作をする権限があるか」を確かめていないコードから生まれます。対策の中心は、AJAX・REST API・フォーム処理のすべての入口で current_user_can() による権限確認を行うことと、サイト運営側でロールと登録設定を最小限にすることです。この記事では、WordPress で起きやすい不備のパターン、安全なコードの書き方、運営者が確認すべき設定とテストの方法を説明します。
Broken Access Control とは
Broken Access Control は、本来許可されていない操作や情報の閲覧ができてしまう脆弱性の総称です。Web アプリケーションの代表的なリスクをまとめた OWASP Top 10 では、2021 年版で最上位に挙げられています。
WordPress のプラグイン脆弱性として公開される情報を見ても、「購読者(Subscriber)権限で設定を変更できる」「未ログインで他人の個人情報を取得できる」といった、この種類の不備は頻繁に見られます。WordPress は誰でも会員登録できる設定にしているサイトも多く、購読者アカウントは攻撃者でも簡単に手に入ります。そのため「ログインしている人しか呼べないから安全」という考え方は通用しません。
WordPress で起きやすい 4 つのパターン
1. AJAX ハンドラーに権限確認がない
wp_ajax_{action} フックに登録した関数は、管理者だけでなく、ログインしているすべてのユーザーから呼び出せます。管理画面の設定ページから使う前提で書いた AJAX 処理でも、購読者が直接 admin-ajax.php にリクエストを送れば実行されてしまいます。wp_ajax_nopriv_{action} は未ログインの訪問者からも呼べるので、さらに慎重な扱いが必要です。
2. REST API の permission_callback が甘い
register_rest_route() の permission_callback に __return_true を指定すると、そのエンドポイントは誰でも呼べる公開 API になります。読み取り専用で公開してよい情報なら問題ありませんが、データを更新する処理や、個人情報を返す処理で使うと不備になります。WordPress 5.5 以降は permission_callback を省略すると開発者向けの警告が出るようになりましたが、警告を消すために __return_true を入れてしまう例が見られます。
3. ID を差し替えると他人のデータを操作できる
「自分の投稿を編集する」「自分の注文を表示する」といった処理で、リクエストに含まれる ID をそのまま使うと、ID を書き換えるだけで他人のデータを操作できます。これは IDOR(Insecure Direct Object Reference)と呼ばれる典型的な不備です。「編集する権限があるか」だけでなく、「この ID のデータを編集する権限があるか」を確かめる必要があります。
4. is_admin() を権限確認と取り違える
is_admin() は「いま管理画面のページを表示しているか」を返す関数で、「管理者かどうか」は判定しません。admin-ajax.php へのリクエストでも true になるため、is_admin() だけで重要な処理を守ると、購読者からも実行できます。同様に、admin_init フックも管理画面にアクセスしたすべてのユーザーで動くので、ここに書いた処理にも権限確認が必要です。
安全なコードの書き方
AJAX:nonce と権限を両方確かめる
add_action( 'wp_ajax_myplugin_save_setting', 'myplugin_save_setting' );
function myplugin_save_setting() {
// 意図したリクエストかを確認(CSRF 対策)
check_ajax_referer( 'myplugin_save_setting', 'nonce' );
// この操作をしてよい人かを確認(アクセス制御)
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => 'forbidden' ), 403 );
}
$value = isset( $_POST['value'] ) ? sanitize_text_field( wp_unslash( $_POST['value'] ) ) : '';
update_option( 'myplugin_setting', $value );
wp_send_json_success();
}
nonce は「本人が自分の画面から送ったリクエストか」を確かめる CSRF 対策で、権限の確認にはなりません。購読者でも自分用の nonce は取得できる場合があるため、check_ajax_referer() と current_user_can() は役割の違う別の確認として両方書きます。
オブジェクト単位で権限を確かめる
ID を受け取る処理では、edit_post や delete_post のような「メタ権限」に ID を添えて確認します。WordPress がその投稿の作成者や公開状態を見て、実際に必要な権限に変換して判定してくれます。
$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
// 「投稿を編集できるか」ではなく「この投稿を編集できるか」を確認する
if ( ! $post_id || ! current_user_can( 'edit_post', $post_id ) ) {
wp_send_json_error( array( 'message' => 'forbidden' ), 403 );
}
投稿者ロールのユーザーは、自分の投稿については edit_post が許可され、他人の投稿では拒否されます。独自のテーブルに保存しているデータの場合は、レコードの所有者 ID と get_current_user_id() を比較するなど、同じ考え方で所有者を確認します。
REST API:permission_callback で判定する
add_action( 'rest_api_init', function () {
register_rest_route(
'myplugin/v1',
'/notes/(?P<id>\d+)',
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => 'myplugin_update_note',
'permission_callback' => function ( WP_REST_Request $request ) {
return current_user_can( 'edit_post', (int) $request['id'] );
},
'args' => array(
'id' => array(
'sanitize_callback' => 'absint',
),
),
)
);
} );
権限の判定は callback の中ではなく permission_callback に書きます。こうすると、権限がない場合は WordPress が処理本体を呼ぶ前に 401 または 403 のエラーを返します。クッキー認証で REST API を呼ぶ場合、X-WP-Nonce ヘッダーの nonce がないとログインユーザーとして扱われないことにも注意してください。
サイト運営者が確認すべき設定
コードを書かない運営者でも、アクセス制御の不備による被害を小さくする設定があります。
- ユーザー登録の設定:「設定 → 一般」の「だれでもユーザー登録ができるようにする」は、会員機能を使っていないなら無効にします。有効にする場合でも、「新規ユーザーのデフォルト権限グループ」は購読者のままにします。ここが管理者や編集者になっていると、登録しただけで高い権限が手に入ります。
- ロールの棚卸し:「ユーザー → ユーザー一覧」でロールごとの人数を確認し、不要な管理者・編集者を下げるか削除します。
- ロール編集プラグインの設定:User Role Editor などで独自に権限を付け替えている場合、購読者や投稿者に
edit_usersやmanage_optionsのような強い権限が付いていないかを確認します。 - プラグインの更新:アクセス制御の不備は、修正版の公開と同時に内容が知られることが多い種類の脆弱性です。脆弱性情報の通知を受け取れるセキュリティプラグインやサービスを使い、更新を後回しにしないようにします。
権限の不備をテストする方法
アクセス制御の不備は、管理者でログインした状態の動作確認では見つかりません。ステージング環境に購読者・投稿者・編集者のテスト用アカウントを作り、それぞれでログインして次の点を確認します。
- 管理者だけが使うはずの設定画面や操作ボタンにアクセスできないか
- 投稿者アカウントで、他人の投稿の編集画面の URL(
post.php?post=ID&action=edit)を直接開いたときに拒否されるか - ブラウザの開発者ツールで管理者の操作時に送られる AJAX や REST API のリクエストを控えておき、低い権限のアカウントで同じリクエストを送ったときに 403 になるか
User Switching プラグインを使うと、ログアウトせずにユーザーを切り替えられるので、ロールごとの確認が楽になります。本番環境でテスト用アカウントを作る場合は、確認後に削除してください。
まとめ
- Broken Access Control は「ログイン済み」「nonce が正しい」だけで操作を許してしまうことから生まれます。
wp_ajax_フックはログイン中の全ユーザーから呼べ、is_admin()は管理者判定ではない点に注意します。- 権限確認は
current_user_can()で行い、ID を扱う処理ではedit_postなどに ID を添えて確認します。 - REST API では
permission_callbackで判定し、更新系に__return_trueを使いません。 - 運営側では登録設定とロールを最小限にし、低い権限のアカウントで実際に操作を試して確認します。