WordPressプラグインやテーマの開発において、最も頻繁に発見されるセキュリティ脆弱性のひとつが「Missing Authorization(認可チェックの欠如・不備)」です。
「管理画面(wp-admin)からしか送信できないフォームだから安全」「URLを知らなければ叩かれない」という思い込みは極めて危険です。一般購読者(Subscriber)や悪意ある攻撃者が直接リクエストを送信した場合、適切な認可チェックがなければ、設定の改ざんや管理者権限の奪取、非公開データの閲覧を許してしまいます。
💡 本記事は「WordPress セキュアコーディング実践シリーズ」の第3弾です
- 第1弾:WordPressのnonceを理解しセキュアな機能開発へステップアップ(CSRF対策の基礎)
- 第2弾:WordPress REST APIの認証・権限管理入門(wp_rest_nonce & permission_callback)
- 第3弾(本記事):current_user_can()によるWordPress権限チェック実践(認可・Capability設計)
- 第4弾:sanitize_text_fieldとwp_ksesの使い分け完全ガイド(入力無害化・XSS対策)
本記事では、WordPressの認可制御の基本関数 current_user_can() の正しい使い方から、やってしまいがちなアンチパターン(is_admin()の誤用など)、メタCapabilityによる投稿単位の厳格制御、そしてカスタムCapabilityの設計までを実践的に解説します。
1. なぜ「is_admin()」やロール名直接チェックは危険なのか?
WordPress開発で最も多い致命的な勘違いが、is_admin() の誤用です。
【危険】is_admin() は「権限チェック関数」ではない
is_admin() は単に「現在リクエストされているページが /wp-admin/ 配下のURLかどうか」を判定するだけのUI判定関数です。
- 一般購読者(Subscriber)が管理画面のダッシュボード(プロファイル画面等)を開いている時も
is_admin() === trueになります。 - さらに、Ajax処理のエンドポイント
/wp-admin/admin-ajax.phpへのリクエストは、ログインしていない未認証ゲストからのリクエストであってもis_admin() === trueになります。
// ❌ 致命的な脆弱性コード(未ログインでもSubscriberでも通過してしまう)
if ( is_admin() ) {
delete_option('my_plugin_important_settings');
}
WordPressの原則:Role(役割名)ではなく Capability(権限)で判定する
WordPressでは「管理者(administrator)かどうか」をロール名で直接判定するのではなく、「設定変更できる権限(manage_options)を持っているか」というCapability(ケーパビリティ)で判定するのが大原則です。
// ⭕ 正しいセキュアな判定
if ( current_user_can('manage_options') ) {
delete_option('my_plugin_important_settings');
}
2. current_user_can() の基本と代表的なCapability一覧
current_user_can( string $capability, mixed ...$args ) は、現在アクセス中のログインユーザーが指定した権限(Capability)を持っているかどうかを真偽値(true / false)で返します。
| Capability(権限名) | 標準で保持するロール | 主な利用シーン |
|---|---|---|
manage_options | 管理者(Administrator) | プラグイン設定、サイト全般の重要設定の保存・変更 |
edit_posts | 寄稿者以上(Contributor+) | 自身の投稿の作成・下書き保存 |
publish_posts | 投稿者以上(Author+) | 投稿の公開 |
edit_others_posts | 編集者以上(Editor+) | 他ユーザーが書いた投稿の編集 |
upload_files | 投稿者以上(Author+) | メディア(画像・ファイル)のアップロード |
read | 購読者以上(Subscriber+) | 管理画面のプロフィール閲覧・ログイン状態の確認 |
3. メタCapability(Meta Capabilities)による個別リソース認可
ブログ投稿やカスタム投稿タイプの編集・削除処理を行う場合、edit_posts(投稿を作成できるか)だけをチェックすると、「寄稿者Aが寄稿者Bの記事を勝手に書き換えてしまう」という権限昇格・越境操作の脆弱性が発生します。
WordPressには、対象リソースのIDを渡すことで「そのユーザーが“その特定の投稿”を編集できるか」を文脈に応じて動的に判定するメタCapability(Meta Capabilities)が備わっています。
メタCapabilityの実装コード例
<?php
// 投稿IDを指定して個別編集権限をチェック
$post_id = isset($_POST['post_id']) ? absint($_POST['post_id']) : 0;
if ( ! current_user_can('edit_post', $post_id) ) {
wp_die(
esc_html__('あなたにはこの記事を編集する権限がありません。', 'my-plugin'),
esc_html__('権限エラー', 'my-plugin'),
array('response' => 403)
);
}
// 権限チェック通過後に更新処理を実行
wp_update_post(array(
'ID' => $post_id,
'post_content' => wp_kses_post($_POST['custom_content']),
));
current_user_can('edit_post', $post_id) は、内部で map_meta_cap() を呼び出し、ユーザーがその投稿の作成者であるか、あるいは edit_others_posts 権限を持つ編集者以上であるかを自動判定してくれます。
4. 開発シーン別:セキュアな権限チェックの実装パターン
パターン1: 管理画面のPOST処理(admin-post.php)
管理画面フォームの送信処理では、nonce検証と権限チェックの「2重防御(Defense in Depth)」が必須です。
<?php
add_action('admin_post_my_save_action', 'my_handle_admin_save');
function my_handle_admin_save() {
// 1. nonceの検証(CSRF対策)
check_admin_referer('my_save_action_nonce', 'my_nonce_field');
// 2. 権限チェック(認可チェック)
if ( ! current_user_can('manage_options') ) {
wp_die(
esc_html__('この設定を変更する権限がありません。', 'my-plugin'),
403
);
}
// 3. サニタイズして保存
$input_value = isset($_POST['my_setting']) ? sanitize_text_field($_POST['my_setting']) : '';
update_option('my_plugin_setting_key', $input_value);
// リダイレクト
wp_safe_redirect(add_query_arg('updated', 'true', wp_get_referer()));
exit;
}
パターン2: Ajax処理(wp_ajax_*)での権限チェック
<?php
add_action('wp_ajax_my_delete_item', 'my_handle_ajax_delete');
function my_handle_ajax_delete() {
// 1. nonce検証
check_ajax_referer('my_ajax_security_nonce', 'security');
// 2. 権限チェック
if ( ! current_user_can('manage_options') ) {
wp_send_json_error(array('message' => '権限がありません。'), 403);
}
$item_id = isset($_POST['item_id']) ? absint($_POST['item_id']) : 0;
// 削除処理の実行...
wp_send_json_success(array('message' => '正常に削除されました。'));
}
5. カスタムCapabilityの設計と追加手順
プラグイン専用の高度な機能を提供する場合、manage_options(全権限を持つ管理者のみ)に縛られるのではなく、独自のCapability(例: manage_my_reports)を定義して特定のロールに付与するのが理想的な設計です。
<?php
// プラグイン有効化フックで一度だけ実行
register_activation_hook(__FILE__, 'my_plugin_activate_capabilities');
function my_plugin_activate_capabilities() {
// 管理者ロールを取得してカスタムCapabilityを追加
$admin_role = get_role('administrator');
if ($admin_role) {
$admin_role->add_cap('manage_my_custom_reports');
}
// 編集者ロールにもレポート閲覧権限を付与
$editor_role = get_role('editor');
if ($editor_role) {
$editor_role->add_cap('view_my_custom_reports');
}
}
6. 権限チェック実装のセキュリティ5箇条
- is_admin()を権限判定に使わない:表示環境判定であり、認可には必ず
current_user_can()を用いる。 - nonce検証と権限チェックを両方行う:nonce(CSRF防止)だけでは認可不足、権限チェックだけでもCSRFに無防備になる。
- 個別リソースにはメタCapabilityを使う:投稿やユーザーの編集・削除には
edit_postやedit_userにIDを添えて判定する。 - 最小権限の原則(Least Privilege):何でも
manage_optionsにせず、適切な粒度のCapabilityを割り当てる。 - 早期リターン&適切なHTTPステータス:権限不足時は即座に処理を中断し、403 Forbidden を返却する。
7. まとめ&セキュアコーディングシリーズ関連記事
WordPressにおける権限チェックは、アプリケーションの安全性を担保する最も基本的な防壁です。current_user_can() を正しく理解し、Capabilityベースで設計することで、認可不備による情報漏えいや不正操作のリスクを根絶できます。
📚 開発者向けセキュアコーディング関連記事
- 【シリーズ第1弾】WordPressのnonceを理解しセキュアな機能開発へステップアップ – [CVE-2025-1463]脆弱性解説
フォーム・リンク・AjaxにおけるCSRF防御の基礎と実装手法を解説。 - 【シリーズ第2弾】WordPress REST APIの認証・権限管理入門|wp_rest_nonceとpermission_callbackの実践実装
RESTエンドポイントの安全な公開とCookie認証・認可のベストプラクティス。 - 【シリーズ第4弾】sanitize_text_fieldとwp_ksesの使い分け完全ガイド|WordPress入力無害化とXSS対策
プレーンテキストとリッチHTMLの無害化手法、出力エスケープとの組み合わせ方を整理。
🛡️ WordPressサイトの権限設定やセキュリティ状態をチェック!
プラグインの認可不備やセキュリティホールは、外部からの攻撃に直結します。「MozCheck」なら、URLを入力するだけでサイト全体のセキュリティ状態を自動診断できます。
コメントを残す