The Exploit
Authenticated attacker needs Contributor-level access or higher.
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: target.example.com
Content-Type: application/x-www-form-urlencoded
Cookie: wordpress_logged_in_XXXXXXXXXXXXXXXXXXXXXXXX=SESSION
action=taxopress_ai_add_post_term&post_id=123&added_tags[]=security&removed_tags[]=
An attacker can send this request to any vulnerable site running TaxoPress AI Autotagger <= 3.41.0 and observe a 200 OK JSON response from admin-ajax.php indicating the AJAX handler accepted the request. The side effect is that the target post with ID 123 gains the injected tag, even if the attacker does not own that post.
What the Patch Did
Before:
$post_type_label = $post_type_details->labels->singular_name;
}
if (empty($added_tags) && empty($removed_tags)) {
After:
$post_type_label = $post_type_details->labels->singular_name;
}
if (!current_user_can('edit_post', $post_id)){
$response['status'] = 'error';
$response['content'] = esc_html__('You do not have permission to edit this post.', 'simple-tags');
wp_send_json($response);
exit;
}
if (empty($added_tags) && empty($removed_tags)) {
The patch adds a WordPress capability check using current_user_can('edit_post', $post_id) before any taxonomy changes are processed. It also sends a sanitized JSON error response and exits immediately if the check fails.
Root Cause
This is an authorization bypass in a WordPress AJAX endpoint: attacker-controlled post_id arrives in the taxopress_ai_add_post_term request and reaches the tag-modification logic without verifying whether the current user may edit that post. The plugin trusted that an authenticated user with a valid AJAX session should be allowed to modify taxonomy terms, crossing the edit-permission trust boundary unchecked. CWE-862: Missing Authorization.
Why It Works
The single load-bearing line is current_user_can('edit_post', $post_id). Without that check, the request can proceed to change tags on any post_id the attacker supplies. The added wp_send_json and exit lines are necessary supporting controls: they turn the authorization failure into a proper AJAX response and stop the handler from continuing. If you removed current_user_can(...), the bug still exists; if you kept it but removed exit, a poorly-structured handler could still continue processing after sending an error response.
Hardening Checklist
- Add per-object capability checks for post-specific actions, e.g.
current_user_can('edit_post', $post_id)before modifying taxonomy or post metadata. - Protect AJAX endpoints with
check_ajax_referer()where appropriate to reduce CSRF risk for authenticated actions. - Use
wp_send_json_error()orwp_send_json()after permission failures and follow withexitto prevent further execution. - Validate user-supplied IDs before use, and do not assume
post_idbelongs to the current user. - Keep role boundaries explicit: Contributor accounts should not be allowed to alter terms on arbitrary posts unless a higher capability is verified.
References
- https://nvd.nist.gov/vuln/detail/CVE-2025-14371