Always Check Capabilities Before Privileged Operations
Share
WordPress capabilities (current_user_can) are the authorization layer. Missing checks in REST endpoints, admin handlers, and AJAX allow subscribers to perform admin actions. [CWE-862 · A01:2021]
Why This Matters
prevents privilege escalation allowing low-role users to perform admin-level operations
WordPress has a granular capability system (manage_options, edit_posts, delete_users, etc.) but it's opt-in — no function automatically enforces capabilities. Every REST API endpoint, admin handler, AJAX callback, and Server Action must explicitly call current_user_can() before performing privileged operations.
CVE-2024-10924 (Really Simple Security, CVSS 9.8, 4M installs) — authentication bypass via a REST endpoint that didn't properly verify user identity, allowing unauthenticated admin login. CVE-2024-8485 (REST API TO MiniProgram) — missing validation enabled unauthenticated user email update, leading to admin account takeover.
Incorrect (missing capability checks):
// ❌ REST endpoint with no permission_callback — accessible to everyoneregister_rest_route( 'myplugin/v1', '/settings', [ 'methods' => 'POST', 'callback' => function( WP_REST_Request $request ) { update_option( 'my_plugin_config', $request->get_json_params() ); return new WP_REST_Response( 'Updated', 200 ); }, // Missing permission_callback entirely!]);// ❌ permission_callback returns true — same as no checkregister_rest_route( 'myplugin/v1', '/users', [ 'methods' => 'DELETE', 'callback' => 'delete_user_handler', 'permission_callback' => '__return_true', // Anyone can delete users!]);// ❌ Admin page action without capability checkadd_action( 'admin_init', function() { if ( isset( $_POST['action'] ) && $_POST['action'] === 'export_data' ) { // No current_user_can() — any logged-in user visiting wp-admin triggers this $data = export_all_user_data(); send_csv_download( $data ); }});