SECURITY ADVISORY / 01

CVE-2026-94505 Exploit & Vulnerability Analysis

Complete CVE-2026-94505 security advisory with proof of concept (PoC), exploit details, and patch analysis.

cve_patchdiff:nelio-content NVD ↗
Exploit PoC Vulnerability Patch Analysis

The Exploit

An authenticated WordPress user with contributor-level access can permanently delete any reusable social message, including those authored by administrators, by sending a DELETE request to the REST API endpoint without ownership or capability verification.

DELETE /wp-json/nelia-content/v1/reusable-messages/42 HTTP/1.1
Host: target.local
Authorization: Bearer <contributor_token>
Cookie: wordpress_logged_in=<contributor_session>
Content-Type: application/json

The server responds with HTTP 200 and the message is permanently deleted from the database. An attacker with contributor credentials observes the message disappears from the editorial calendar UI immediately, and subsequent requests to fetch that message ID return 404. No audit log or capability error is raised; the deletion appears legitimate.

What the Patch Did

Before:

'permission_callback' => 'nelio_content_can_current_user_use_plugin',

After:

'permission_callback' => array( $this, 'can_current_user_delete_reusable_message_from_request' ),

And within the endpoint handler:

Before:

public function remove_reusable_message( $request ) {
	$message = $request->get_param( 'message' );
	assert( $message instanceof Nelio_Content_Reusable_Message );
	$message->delete();
	return new WP_REST_Response( array( 'deleted' => true ), 200 );
}

After:

public function remove_reusable_message( $request ) {
	$message = $request->get_param( 'message' );
	assert( $message instanceof Nelio_Content_Reusable_Message );
	if ( ! empty( $message->ID ) && ! $this->can_current_user_delete_reusable_message( $message->ID ) ) {
		return new WP_Error(
			'rest_forbidden',
			_x( 'You are not allowed to delete this message.', 'text', 'nelio-content' ),
			array( 'status' => 403 )
		);
	}
	$message->delete();
	return new WP_REST_Response( array( 'deleted' => true ), 200 );
}

The patch replaced a generic plugin-access check (nelio_content_can_current_user_use_plugin) with a granular, message-aware authorization function (can_current_user_delete_reusable_message_from_request) that verifies both the route-level permission callback and a second guard inside the handler itself. The handler now invokes can_current_user_delete_reusable_message() to validate that the requesting user either owns the message, is a plugin manager, or holds the delete_post capability scoped to that post ID.

Root Cause

CWE-639: Authorization Bypass Through User-Controlled Key — The DELETE /wp-json/nelia-content/v1/reusable-messages/{id} endpoint accepted a message request parameter containing a Nelio_Content_Reusable_Message object. The route's permission callback only checked whether the current user had general plugin access via nelio_content_can_current_user_use_plugin(), which returns true for any authenticated user with contributor role or above. This callback did not examine the specific message being deleted — only the user's global permissions. The handler then unconditionally called $message->delete() without re-validating that the user owned the message or held explicit delete authority over that post. An attacker could craft a DELETE request targeting any message ID in the database; as long as they were authenticated with a contributor account, the permission callback would pass and the deletion would succeed.

Why It Works

The load-bearing line is the authorization check inside remove_reusable_message():

if ( ! empty( $message->ID ) && ! $this->can_current_user_delete_reusable_message( $message->ID ) ) {

Without this, the old code reaches $message->delete() unchecked. The permission callback alone is insufficient because REST permission callbacks execute before the handler sees the request body or route parameters — they cannot evaluate the specific post ID being deleted. The callback upgrade to can_current_user_delete_reusable_message_from_request() acts as a secondary gate at the route level, but the real gatekeeper is the check inside the handler, which has access to the hydrated Nelio_Content_Reusable_Message object and can call can_current_user_delete_reusable_message( $message->ID ) with the actual post ID. If the engineer had removed only the route callback change and left the handler unguarded, the vulnerability would persist; if they had added only the route callback without the handler check, a bypass might still be possible if the callback failed to fully validate the message ID parameter. Defence-in-depth: the callback validates early; the handler validates late with full context.

Hardening Checklist

  • Separate route callbacks from handler logic. Do not assume a route-level permission callback (permission_callback in REST registration) can evaluate request-specific resources like post IDs. Always re-validate inside the handler using get_post( $id ) or $post->ID and call current_user_can( 'delete_post', $id ) with the post as a second argument to scope the capability check.

  • Use current_user_can() with resource IDs. Never call current_user_can( 'manage_options' ) or current_user_can( 'delete_post' ) without passing the post ID as a second parameter. WordPress capability checks that omit the resource ID may return true for users who should not have access to that specific post.

  • Audit all REST endpoints that mutate state. Search for register_rest_route() calls with 'methods' => 'POST|PUT|DELETE' and verify that each endpoint callback contains an explicit capability or ownership check that references the resource ID from the request, not just the global user role.

  • Log authorization failures. Wrap capability denials in error_log() or a dedicated audit table so privilege-escalation attempts are visible to site administrators monitoring the error log or audit trail.

  • Test with contributor-level roles. Reproduce all CRUD operations as a contributor, subscriber, and unauthenticated user; verify that delete and edit operations return 403 when the user does not own the resource or hold an admin role.

References

  • https://nvd.nist.gov/vuln/detail/CVE-2026-94505

Frequently asked questions about CVE-2026-94505

What is CVE-2026-94505?

CVE-2026-94505 is a security vulnerability. This security advisory provides detailed technical analysis of the vulnerability, exploit methodology, affected versions, and complete remediation guidance.

Is there a PoC (proof of concept) for CVE-2026-94505?

Yes. This writeup includes proof-of-concept details and a technical exploit breakdown for CVE-2026-94505. Review the analysis sections above for the PoC walkthrough and code examples.

How does CVE-2026-94505 get exploited?

The technical analysis section explains the vulnerability mechanics, attack vectors, and exploitation methodology. PatchLeaks publishes this information for defensive and educational purposes.

What products and versions are affected by CVE-2026-94505?

CVE-2026-94505 — check the affected-versions section of this advisory for specific version ranges, vulnerable configurations, and compatibility information.

How do I fix or patch CVE-2026-94505?

The patch analysis section provides guidance on updating to patched versions, applying workarounds, and implementing compensating controls.

What is the CVSS score for CVE-2026-94505?

The severity rating and CVSS scoring for CVE-2026-94505 is documented in the vulnerability details section. Refer to the NVD entry for the current authoritative score.