The Exploit
An unauthenticated attacker can approve an arbitrary event by calling the plugin’s AJAX endpoint with action=ajax_admin_event_approval, eventlist, and a valid event_approval_nonce.
curl -s "https://TARGET_HOST/wp-admin/admin-ajax.php?action=ajax_admin_event_approval&eventlist=123&event_approval_nonce=VALID_NONCE"
The request is accepted and the event with ID 123 is marked approved without any WordPress login. The response typically returns a success indicator, and the backend event status changes even though the requester is not authenticated as an admin.
What the Patch Did
Before:
if ( !wp_verify_nonce( $_GET['event_approval_nonce'], 'event_approval_nonce' ) ) {
exit;
}
After:
if ( !current_user_can( 'manage_options' ) || !wp_verify_nonce( $_GET['event_approval_nonce'], 'event_approval_nonce' ) ) {
exit;
}
The patch added a WordPress capability check: current_user_can('manage_options'). This enforces that only users with administrative privileges can execute the event approval action, not just anyone who can present a nonce.
Root Cause
This was an access control bug (CWE-284) in ajax_admin_event_approval(): attacker-controlled input from eventlist reached sensitive approval logic without an authorization check. The function only validated the nonce from $_GET['event_approval_nonce'], but it did not verify the caller’s capabilities before approving events. That means the trust boundary between a public request and admin-only event modification was crossed unchecked.
Why It Works
The load-bearing change is the current_user_can( 'manage_options' ) check. Without that line, the endpoint still accepts requests from unauthenticated users as long as the nonce validation passes. The existing wp_verify_nonce() remains important for CSRF protection, but it does not substitute for authorization. The patch combines both checks in a single conditional so the request is rejected if either the caller lacks admin rights or the nonce is invalid.
Hardening Checklist
- Use
current_user_can('manage_options')for admin-only actions before performing state-changing operations. - Protect AJAX endpoints with
wp_verify_nonce()orcheck_admin_referer()for CSRF defense. - Register privileged AJAX handlers under
wp_ajax_...only, notwp_ajax_nopriv_..., unless anonymous access is explicitly required. - Treat
$_GET['event_approval_nonce']and$_POST/$_GETparameters as attacker-controlled and gate them with authorization checks before use. - Avoid relying on nonce checks alone as an authorization mechanism; always require capability checks for admin workflows.
References
https://nvd.nist.gov/vuln/detail/CVE-2025-14029