WP Easy Pay – Payment and Donation form Builder for Square <= 4.2.11 - Missing Authorization
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:NTechnical Details
<=4.2.11What Changed in the Fix
Changes introduced in v4.2.12
Source Code
WordPress.org SVN# 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 targetwp_easy_paypost) - Bypass Parameter:
donation_goal_nonce(Omit this to bypass the broken nonce check)
3. Code Flow
- Entry Point: An authenticated user sends a POST request to
admin-ajax.phpwithaction=wpep_reset_donation_goal. - Hook Registration: In
wpep-setup.php, the action is registered:add_action( 'wp_ajax_wpep_reset_donation_goal', 'wpep_reset_donation_goal' ); - Vulnerable Function: The execution moves to
wpep_reset_donation_goal()inwpep-setup.php. - Broken Nonce Check (lines 55-57):
If theif ( isset( $_POST['donation_goal_nonce'] ) && ! wp_verify_nonce( ... ) ) { exit; }donation_goal_nonceparameter is missing from the request, theisset()check returnsfalse, short-circuiting the logic and bypassing thewp_verify_noncecall entirely. - Missing Authorization: No
current_user_can()check exists before the state-changing operation. - Data Sink (lines 60-61):
The plugin updates the meta valueupdate_post_meta( sanitize_text_field( wp_unslash( $_POST['form_id'] ) ), 'wpep_donation_goal_achieved', 0 );wpep_donation_goal_achievedto0for the providedform_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_nonceparameter, theisset($_POST['donation_goal_nonce'])check evaluates tofalse, 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_paypost type, localized under the variable namewpep_hide_elements(inferred fromassets/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-urlencodedCookie: [Subscriber Cookies]
- Body:
action=wpep_reset_donation_goal&form_id=[FORM_ID]
6. Test Data Setup
- Plugin Installation: Ensure WP Easy Pay <= 4.2.11 is active.
- Form Creation: Use the
Example Formcreated on activation or manually create awp_easy_paypost. - State Setup: Manually set the meta
wpep_donation_goal_achievedto1for the target post ID. - User Creation: Create a user with the
subscriberrole.
7. Expected Results
- Response Code: 200 OK
- Response Body:
done - Database Effect: The
post_metavalue forwpep_donation_goal_achievedassociated with theform_idshould be changed from1to0.
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:
- Navigate to a page where the Square product popup is active (likely the form editor).
- Execute
browser_eval("jQuery('#square_product_nonce').val()").
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
@@ -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.