CVE-2026-9187

Abandoned Contact Form 7 <= 2.2 - Missing Authorization to Unauthenticated Arbitrary Post Deletion via 'recover_id' Parameter

mediumMissing Authorization
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
Unpatched
Patched in
N/A
Time to patch

Description

The Abandoned Contact Form 7 plugin for WordPress is vulnerable to unauthorized arbitrary post deletion in versions up to, and including, 2.2. This is due to a missing capability check and missing nonce validation in the action__remove_abandoned() function, which is registered to both the wp_ajax_remove_abandoned and wp_ajax_nopriv_remove_abandoned hooks. The handler takes a user-supplied recover_id parameter from $_POST and passes it directly to wp_delete_post() with the force-delete flag set to true, without verifying that the ID belongs to the plugin's own cf7af_data post type. This makes it possible for unauthenticated attackers to permanently delete arbitrary posts, pages, or other content on the affected site by sending a single admin-ajax.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=2.2
PublishedJune 15, 2026
Last updatedJune 16, 2026
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-9187 ## 1. Vulnerability Summary The **Abandoned Contact Form 7** plugin (versions <= 2.2) contains a missing authorization vulnerability in its AJAX handling logic. Specifically, the function `action__remove_abandoned()` is registered to both authenticated (`…

Show full research plan

Exploitation Research Plan: CVE-2026-9187

1. Vulnerability Summary

The Abandoned Contact Form 7 plugin (versions <= 2.2) contains a missing authorization vulnerability in its AJAX handling logic. Specifically, the function action__remove_abandoned() is registered to both authenticated (wp_ajax_) and unauthenticated (wp_ajax_nopriv_) AJAX hooks. This function fails to perform capability checks, nonce validation, or verify that the target post belongs to the plugin's intended custom post type (cf7af_data). As a result, any unauthenticated user can trigger the permanent deletion of arbitrary WordPress posts, pages, or media.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: remove_abandoned (inferred from hook registration wp_ajax_nopriv_remove_abandoned)
  • Vulnerable Parameter: recover_id (sent via $_POST)
  • Required Authentication: None (Unauthenticated)
  • Preconditions: The plugin "Abandoned Contact Form 7" must be active.

3. Code Flow

  1. Entry Point: An unauthenticated POST request is sent to admin-ajax.php with the parameter action=remove_abandoned.
  2. Hook Execution: WordPress executes the hook wp_ajax_nopriv_remove_abandoned, which calls the handler action__remove_abandoned().
  3. Missing Checks: The handler does not call current_user_can() or check_ajax_referer().
  4. Input Processing: The handler retrieves $_POST['recover_id'].
  5. The Sink: The handler calls wp_delete_post( $_POST['recover_id'], true ).
    • The first argument is the ID to delete.
    • The second argument true bypasses the Trash and performs a permanent deletion.
  6. Result: The post associated with the provided ID is deleted from the database regardless of its post type or author.

4. Nonce Acquisition Strategy

According to the vulnerability description, this specific vulnerability is exploitable because nonce validation is entirely missing in the action__remove_abandoned() function.

No nonce acquisition is required for this exploit.

5. Exploitation Strategy

The goal is to permanently delete a critical post or page (e.g., the site's front page or a post created by an admin).

Step-by-Step Plan:

  1. Target Identification: Identify the ID of a post or page to delete.
  2. Payload Construction: Prepare a POST request to the AJAX endpoint.
  3. Execution: Send the request using the http_request tool.

Technical Request Details:

  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Method: POST
  • Headers:
    • Content-Type: application/x-www-form-urlencoded
  • Body:
    • action=remove_abandoned&recover_id=[TARGET_POST_ID]

6. Test Data Setup

To safely test the exploit, create a dummy post to serve as the target:

  1. Use WP-CLI to create a "Victim" post:
    wp post create --post_type=post --post_title="Victim Post" --post_status=publish --post_content="This content should be deleted."
    
  2. Note the ID of the created post (e.g., 123).
  3. Ensure the plugin is active:
    wp plugin activate abandoned-contact-form-7
    

7. Expected Results

  • HTTP Response: Usually, WordPress AJAX handlers that do not explicitly return JSON or use wp_die() with a message will return a 200 OK with a body of 0 or 1 (or nothing if the function terminates early).
  • Database State: The post with the ID provided in recover_id should be completely removed from the wp_posts table.

8. Verification Steps

After sending the exploit request, verify the deletion using WP-CLI:

  1. Attempt to retrieve the post:
    wp post get [TARGET_POST_ID]
    
  2. Expected Verification Outcome: WP-CLI should return an error: Error: Could not find the post with ID [TARGET_POST_ID].

9. Alternative Approaches

If the $_POST parameter recover_id is not accepted, the plugin might be reading from $_GET or $_REQUEST.

  • Alternative 1 (GET Request):
    GET /wp-admin/admin-ajax.php?action=remove_abandoned&recover_id=[TARGET_POST_ID]
  • Alternative 2 (Different Parameter Name):
    If recover_id (inferred from description) fails, check the source code for other parameters passed to wp_delete_post. If the handler iterates through an array, try recover_id[]=[ID].
Research Findings
Static analysis — not yet PoC-verified

Summary

The Abandoned Contact Form 7 plugin for WordPress is vulnerable to unauthenticated arbitrary post deletion in versions up to and including 2.2. This occurs due to a lack of authorization checks and nonce validation in the 'action__remove_abandoned' AJAX handler, allowing any visitor to permanently delete posts, pages, or media.

Vulnerable Code

// In abandoned-contact-form-7.php
function action__remove_abandoned() {
    // ... (missing check_ajax_referer and current_user_can)
    wp_delete_post( $_POST['recover_id'], true );
    wp_die();
}

Security Fix

--- abandoned-contact-form-7.php
+++ abandoned-contact-form-7.php
@@ -1,4 +1,9 @@
 function action__remove_abandoned() {
+    check_ajax_referer( 'cf7af_remove_action', 'nonce' );
+    if ( ! current_user_can( 'delete_posts' ) ) {
+        wp_die( 'Unauthorized' );
+    }
+
     $recover_id = isset( $_POST['recover_id'] ) ? intval( $_POST['recover_id'] ) : 0;
-    wp_delete_post( $recover_id, true );
+    if ( get_post_type( $recover_id ) === 'cf7af_data' ) {
+        wp_delete_post( $recover_id, true );
+    }
     wp_die();
 }

Exploit Outline

The vulnerability is exploited by sending an unauthenticated POST request to the WordPress AJAX endpoint (/wp-admin/admin-ajax.php). The request must include the 'action' parameter set to 'remove_abandoned' and a 'recover_id' parameter containing the ID of the target post to be deleted. Because the handler lacks permission checks and does not verify the post type, it performs a permanent deletion (bypassing the trash) of the specified ID.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.