CVE-2026-42655

Paymattic – Secure, Simple Payment & Donation with Subscription Payments, Recurring Donations, Customer Management <= 4.6.19 - Missing Authorization

mediumMissing Authorization
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
4.6.20
Patched in
6d
Time to patch

Description

The Paymattic – Secure, Simple Payment & Donation with Subscription Payments, Recurring Donations, Customer Management 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.6.19. This makes it possible for unauthenticated attackers to perform an unauthorized action.

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<=4.6.19
PublishedApril 29, 2026
Last updatedMay 4, 2026
Affected pluginwp-payment-form

What Changed in the Fix

Changes introduced in v4.6.20

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

### 1. Vulnerability Summary The Paymattic plugin (<= 4.6.19) contains a **Missing Authorization** vulnerability within its custom routing system. Specifically, certain administrative or dashboard-level actions—such as canceling subscriptions or syncing billing data—were accessible through unauthent…

Show full research plan

1. Vulnerability Summary

The Paymattic plugin (<= 4.6.19) contains a Missing Authorization vulnerability within its custom routing system. Specifically, certain administrative or dashboard-level actions—such as canceling subscriptions or syncing billing data—were accessible through unauthenticated AJAX requests.

The vulnerability stems from the plugin's use of a custom router (defined in app/Http/Routes/routes.php) which dispatches requests to controllers based on a route parameter. In versions up to 4.6.19, routes within the dashboard prefix (intended for customers) and potentially some under forms were not strictly enforced against unauthenticated access, or the FrontendUserPolicy failed to validate that the requester was the owner of the entry.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: wppayform_api or wppayform_frontend_api (dispatched via the WPPayForm\App\Http\Router).
  • Vulnerable Route: dashboard/form/{id}/entries/{entryId}/cancel-subscription
  • HTTP Method: POST
  • Authentication: Unauthenticated (nopriv).
  • Preconditions: An existing submission/subscription ID and form ID must be known (often sequential and easily guessable).

3. Code Flow

  1. The request hits `admin-ajax.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Paymattic plugin for WordPress (<= 4.6.19) lacks proper authorization checks and nonce validation within its custom routing system and AJAX handlers. This allows unauthenticated attackers to perform unauthorized actions, such as canceling subscriptions, accessing sensitive billing data, or submitting forged form data by targeting predictable form and entry IDs.

Vulnerable Code

// app/Hooks/actions.php:145
add_action('wp_ajax_wpf_submit_form', array(new \WPPayForm\App\Hooks\Handlers\SubmissionHandler(), 'handleSubmission'));
add_action('wp_ajax_nopriv_wpf_submit_form', array(new \WPPayForm\App\Hooks\Handlers\SubmissionHandler(), 'handleSubmission'));

---

// app/Hooks/Handlers/SubmissionHandler.php:33
class SubmissionHandler
{
    // ... (omitted properties)
    public function handleSubmission()
    {
        if (!isset($_REQUEST['form_data'])) {
            return;
        }
   
        parse_str($_REQUEST['form_data'], $form_data);
        // ...
        if(isset($_REQUEST['form_id'])){
            $formId = absint(sanitize_text_field(wp_unslash($_REQUEST['form_id'])));
        }
        // No nonce verification or capability check performed before processing data

---

// app/Http/Routes/routes.php:84
$router->prefix('dashboard')->withPolicy('FrontendUserPolicy')->group(function ($router) {
    $router->get('/forms/formatted', 'FormsController@formatted');
    $router->prefix('/form/{id}/entries')->group(function ($router) {
        $router->prefix('/{entryId}')->group(function ($router) {
            $router->get('/', 'SubmissionController@getSubmission')->int('id', 'entryId');
            $router->post('/cancel-subscription', 'SubmissionController@cancelSubscription')->int('id', 'entryId');
        });
    });
});

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wp-payment-form/4.6.19/app/Hooks/Handlers/SubmissionHandler.php /home/deploy/wp-safety.org/data/plugin-versions/wp-payment-form/4.6.20/app/Hooks/Handlers/SubmissionHandler.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-payment-form/4.6.19/app/Hooks/Handlers/SubmissionHandler.php	2026-03-11 04:30:34.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-payment-form/4.6.20/app/Hooks/Handlers/SubmissionHandler.php	2026-04-16 07:13:06.000000000 +0000
@@ -40,17 +40,20 @@
         if (!isset($_REQUEST['form_data'])) {
             return;
         }
-
+        if (!isset($_REQUEST['wpf_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['wpf_nonce'])), 'wpf_form_submission')) {
+            wp_send_json_error(array(
+                'message' => __('Security verification failed. Please refresh and try again.', 'wp-payment-form'),
+            ), 403);
+        }
+
         parse_str($_REQUEST['form_data'], $form_data);
-        $form_localize = Arr::get($_REQUEST['form_localize'], 'conditional_logic');
         // Now Validate the form please
         if(isset($_REQUEST['form_id'])){
             $formId = absint(sanitize_text_field(wp_unslash($_REQUEST['form_id'])));
         }
         $this->formID = $formId;
 
-        // Get Original Form Elements Now
-        $totalPayableAmount = intval(wp_unslash($_REQUEST['main_total'] ?? 0));
         do_action('wppayform/form_submission_activity_start', $formId);
 
         $form = Form::getForm($formId);
@@ -60,13 +63,11 @@
                 'message' => __('Invalid request. Please try again', 'wp-payment-form'),
             ), 423);
         }
- 
         $formattedElements = Form::getFormattedElements($formId);
 
         $numericCalculation = [];
         $submittedNumericCalculation = apply_filters('wppayform/dynamic_payment_calculation', '', $numericCalculation, $formattedElements, $form_data);
-
-        $this->validate($form_data, $formattedElements, $form, $form_localize);
+        $this->validate($form_data, $formattedElements, $form);
 
         $paymentMethod = apply_filters('wppayform/choose_payment_method_for_submission', '', $formattedElements['payment_method_element'], $formId, $form_data);

Exploit Outline

To exploit this vulnerability, an attacker targets the AJAX endpoint /wp-admin/admin-ajax.php or the custom routing mechanism used by Paymattic. For unauthorized subscription cancellation, an unauthenticated attacker crafts a POST request to the custom router endpoint (often wppayform_api) specifying the target route 'dashboard/form/{id}/entries/{entryId}/cancel-subscription'. Because the plugin fails to verify that the requester owns the entry or has sufficient capabilities, providing a valid form ID and entry ID (which are often sequential and guessable) allows the attacker to cancel any active subscription. Similarly, the form submission handler (wpf_submit_form) can be exploited via CSRF or unauthorized unauthenticated requests due to the lack of nonce verification.

Check if your site is affected.

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