[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fnVYXPX7uIsACAGftvpgNTXVVERWi0P98-mD7yiUKOYc":3},{"id":4,"url_slug":5,"title":6,"description":7,"plugin_slug":8,"theme_slug":9,"affected_versions":10,"patched_in_version":11,"severity":12,"cvss_score":13,"cvss_vector":14,"vuln_type":15,"published_date":16,"updated_date":17,"references":18,"days_to_patch":20,"patch_diff_files":21,"patch_trac_url":9,"research_status":29,"research_verified":30,"research_rounds_completed":31,"research_plan":32,"research_summary":33,"research_vulnerable_code":34,"research_fix_diff":35,"research_exploit_outline":36,"research_model_used":37,"research_started_at":38,"research_completed_at":39,"research_error":9,"poc_status":9,"poc_video_id":9,"poc_summary":9,"poc_steps":9,"poc_tested_at":9,"poc_wp_version":9,"poc_php_version":9,"poc_playwright_script":9,"poc_exploit_code":9,"poc_has_trace":30,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":30,"source_links":40},"CVE-2026-5069","fluent-forms-incorrect-authorization-to-authenticated-subscriber-arbitrary-subscription-cancellation-via-subscriptionid","Fluent Forms \u003C= 6.2.1 - Incorrect Authorization to Authenticated (Subscriber+) Arbitrary Subscription Cancellation via 'subscription_id'","The Fluent Forms plugin for WordPress is vulnerable to incorrect authorization via the 'subscription_id' parameter in versions up to, and including, 6.2.1. This is due to insufficient ownership authorization checks in the payment cancellation AJAX flow. This makes it possible for authenticated attackers, with subscriber-level access and above, to submit cancellation requests for other users' subscriptions.","fluentform",null,"\u003C=6.2.1","6.2.2","medium",5.4,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:L\u002FUI:N\u002FS:U\u002FC:N\u002FI:L\u002FA:L","Incorrect Authorization","2026-07-09 13:51:54","2026-07-10 02:30:44",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002Fe7521577-ce13-4b60-ae11-9c0f9c077cf9?source=api-prod",1,[22,23,24,25,26,27,28],"app\u002FHelpers\u002FHelper.php","app\u002FHttp\u002FRoutes\u002Fapi.php","app\u002FModels\u002FFormMeta.php","app\u002FModels\u002FTraits\u002FPredefinedForms.php","app\u002FModules\u002FAcl\u002FAcl.php","app\u002FModules\u002FComponent\u002FComponent.php","app\u002FModules\u002FEntries\u002FEntryViewRenderer.php","researched",false,3,"This research plan outlines the methodology for verifying **CVE-2026-5069**, an incorrect authorization vulnerability in Fluent Forms that allows authenticated users (Subscriber level and above) to cancel arbitrary subscriptions by manipulating the `subscription_id` parameter.\n\n### 1. Vulnerability Summary\nThe vulnerability exists in the payment management logic of Fluent Forms. When a subscription cancellation request is processed via AJAX, the plugin fails to verify if the currently authenticated user has the authority (ownership) over the specific `subscription_id` being cancelled. While the endpoint requires a valid nonce and authentication, it lacks a check to ensure the `user_id` associated with the subscription matches the `wp_get_current_user()->ID`.\n\n### 2. Attack Vector Analysis\n*   **Endpoint:** `wp-admin\u002Fadmin-ajax.php`\n*   **Action:** `fluentform_cancel_subscription` (or the REST API equivalent via `fluentform\u002Fv1\u002Fsubscriptions\u002Fcancel`)\n*   **HTTP Method:** `POST`\n*   **Payload Parameter:** `subscription_id` (Integer)\n*   **Required Authentication:** Subscriber level or higher.\n*   **Preconditions:** \n    *   The plugin must have at least one form configured with a recurring payment\u002Fsubscription (e.g., Stripe or PayPal).\n    *   The attacker must be logged in to an account with at least `subscriber` capabilities.\n\n### 3. Code Flow\n1.  **Entry Point:** The request hits `admin-ajax.php` with the action `fluentform_cancel_subscription`.\n2.  **Dispatch:** The request is routed to a handler (typically in a payment-related controller, e.g., `FluentForm\\App\\Services\\Payments\\PaymentAction::handleSubscriptionCancel`).\n3.  **Missing Check:** The handler retrieves the `subscription_id` from the `$_POST` or `$_REQUEST` array.\n4.  **Database Query:** The code fetches the subscription record from the `fluentform_subscriptions` table.\n5.  **Processing:** The code proceeds to call the payment gateway's cancellation API (Stripe\u002FPayPal) and updates the internal database status to `cancelled`.\n6.  **Failure:** Between steps 4 and 5, there is no verification that the `user_id` on the subscription record matches the requester's ID, nor a check for `fluentform_manage_payments` capabilities.\n\n### 4. Nonce Acquisition Strategy\nFluent Forms typically localizes its nonces in the global `fluent_forms_global_var` object or specific payment objects.\n\n1.  **Identify the Trigger:** Subscriptions are usually managed by users on a \"My Account\" or \"Subscription Management\" page created with a shortcode.\n2.  **Shortcode:** Check for the presence of payment management shortcodes. If unknown, create a page with `[fluentform_info]` or a specific subscription list shortcode (often part of the Pro features, but the vulnerability is reported in the base plugin's handling of the IDs).\n3.  **Extraction:**\n    *   Navigate to the page where a user would naturally cancel their own subscription.\n    *   Use the execution agent's `browser_eval` to extract the nonce:\n        ```javascript\n        \u002F\u002F Common locations for Fluent Forms nonces\n        window.fluent_forms_global_var?.nonce || \n        window.fluentform_payment_config?.nonce || \n        document.querySelector('input[name=\"_fluentform_payment_nonce\"]')?.value\n        ```\n4.  **Action Name:** The action string used for `wp_create_nonce` is likely `fluentform_payment_nonce` or `fluentform_recurrent_cancel`.\n\n### 5. Exploitation Strategy\nThe exploit involves sending a crafted AJAX request as an authenticated Subscriber to cancel a subscription belonging to another user.\n\n**Request Details:**\n*   **URL:** `http:\u002F\u002F[target-ip]\u002Fwp-admin\u002Fadmin-ajax.php`\n*   **Method:** `POST`\n*   **Headers:** `Content-Type: application\u002Fx-www-form-urlencoded`\n*   **Body:** \n    *   `action=fluentform_cancel_subscription`\n    *   `subscription_id=[VICTIM_SUBSCRIPTION_ID]`\n    *   `nonce=[EXTRACTED_NONCE]`\n\n### 6. Test Data Setup\n1.  **Users:**\n    *   **Victim:** Subscriber user (ID: 5).\n    *   **Attacker:** Subscriber user (ID: 10).\n2.  **Subscription Data:**\n    *   Create a form with a recurring payment.\n    *   Log in as **Victim** and submit the form to generate a subscription record in the `fluentform_subscriptions` table.\n    *   Record the `id` of this subscription (e.g., `subscription_id = 1`).\n3.  **Attacker Login:** Log in as **Attacker**.\n\n### 7. Expected Results\n*   **Successful Exploit:** The server returns a `200 OK` with a JSON body indicating success (e.g., `{\"success\": true, \"message\": \"Subscription cancelled successfully\"}`).\n*   **Impact:** The subscription record in the database for the **Victim** now has a status of `cancelled`, and any linked recurring payment gateway would receive a cancellation request.\n*   **Unsuccessful Exploit (Patched):** The server returns a `422` or `403` error with a message like \"You do not have permission to perform this action.\"\n\n### 8. Verification Steps\nAfter performing the HTTP request, verify the state change via `wp-cli`:\n\n1.  **Check Subscription Status:**\n    ```bash\n    wp db query \"SELECT status FROM wp_fluentform_subscriptions WHERE id = [VICTIM_SUBSCRIPTION_ID]\"\n    ```\n    *Status should be 'cancelled' if successful.*\n2.  **Check Submission Logs:**\n    ```bash\n    wp db query \"SELECT * FROM wp_fluentform_submission_meta WHERE meta_key = 'payment_log' ORDER BY id DESC LIMIT 1\"\n    ```\n    *Review the logs to see if the cancellation was triggered by the Attacker's user ID.*\n\n### 9. Alternative Approaches\nIf the `admin-ajax.php` action is protected by a different nonce or requires specific routing:\n1.  **REST API Route:** Fluent Forms uses a REST wrapper. Try the request at:\n    `POST \u002Fwp-json\u002Ffluentform\u002Fv1\u002Fsubscriptions\u002F[VICTIM_SUBSCRIPTION_ID]\u002Fcancel`\n    *   Header: `X-WP-Nonce` (obtained from `wp_rest` action).\n2.  **Status Toggle:** Check if the vulnerability extends to the `updateStatus` route identified in `api.php`:\n    `POST \u002Fwp-json\u002Ffluentform\u002Fv1\u002Fsubmissions\u002F{entry_id}\u002Fstatus`\n    *   Parameter: `status=cancelled`.\n    *   Test if this allows bypassing payment-specific checks to terminate the billing cycle.","The Fluent Forms plugin for WordPress is vulnerable to incorrect authorization via the 'subscription_id' parameter in versions up to 6.2.1. This allows authenticated attackers with Subscriber-level access or higher to cancel arbitrary subscriptions belonging to other users due to a lack of ownership validation and an over-permissive ACL system that grants broad access to users with any plugin-specific capability.","\u002F\u002F app\u002FModules\u002FAcl\u002FAcl.php line 161\npublic static function hasPermission($permissions, $formId = false)\n{\n    if ($formId && !FormManagerService::hasFormPermission($formId)) {\n        return false;\n    }\n    $userCapability = static::getCurrentUserCapability();\n\n    if ($userCapability) {\n        return true;\n    } else {\n        if (current_user_can('fluentform_full_access')) {\n            return true;\n        }\n\n        $permissions = (array) $permissions;\n\n        foreach ($permissions as $permission) {\n            $allowed = current_user_can($permission);\n\n            if ($allowed) {\n                \u002F\u002F ... (logic to apply filters and return true)\n            }\n        }\n\n        return false;\n    }\n}","--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ffluentform\u002F6.2.1\u002Fapp\u002FModules\u002FAcl\u002FAcl.php\t2026-04-16 11:38:36.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ffluentform\u002F6.2.2\u002Fapp\u002FModules\u002FAcl\u002FAcl.php\t2026-04-23 12:58:12.000000000 +0000\n@@ -159,38 +159,51 @@\n         if ($formId && !FormManagerService::hasFormPermission($formId)) {\n             return false;\n         }\n-        $userCapability = static::getCurrentUserCapability();\n \n-        if ($userCapability) {\n+        \u002F\u002F Only explicit full-access users should bypass individual permission checks.\n+        if (static::hasExplicitFullAccess()) {\n             return true;\n-        } else {\n-            if (current_user_can('fluentform_full_access')) {\n-                return true;\n-            }\n+        }\n \n-            $permissions = (array) $permissions;\n+        $grantedRole = static::getCurrentUserCapability();\n \n-            foreach ($permissions as $permission) {\n-                $allowed = current_user_can($permission);\n+        foreach ((array) $permissions as $permission) {\n+            $allowed = current_user_can($permission);\n \n-                if ($allowed) {\n-                    $allowed = apply_filters_deprecated(\n-                        'fluentform_verify_user_permission_' . $permission,\n-                        [\n-                            $allowed,\n-                            $formId\n-                        ],\n-                        FLUENTFORM_FRAMEWORK_UPGRADE,\n-                        'fluentform\u002Fverify_user_permission_' . $permission,\n-                        'Use fluentform\u002Fverify_user_permission_' . $permission . ' instead of fluentform_verify_user_permission_' . $permission\n-                    );\n+            \u002F\u002F A granted role can satisfy scoped permissions, but never full access.\n+            if (!$allowed && $grantedRole && 'fluentform_full_access' !== $permission) {\n+                $allowed = true;\n+            }\n \n-                    return apply_filters('fluentform\u002Fverify_user_permission_' . $permission, $allowed, $formId);\n-                }\n+            if (!$allowed) {\n+                continue;\n             }\n \n-            return false;\n+            return static::filterPermissionCheck($permission, $allowed, $formId);\n         }\n+ \n+        return false;\n     }","1. Log in as a user with Subscriber privileges or higher.\n2. Obtain a valid security nonce from the global 'fluent_forms_global_var' JavaScript object or payment management metadata on the site's frontend.\n3. Identify a 'subscription_id' belonging to another user (e.g., via numerical enumeration).\n4. Send a POST request to 'wp-admin\u002Fadmin-ajax.php' with the action 'fluentform_cancel_subscription' (or the REST API equivalent 'POST \u002Fwp-json\u002Ffluentform\u002Fv1\u002Fsubscriptions\u002F{id}\u002Fcancel') including the targeted 'subscription_id' and the nonce.\n5. The plugin processes the cancellation request without verifying if the subscription record's 'user_id' matches the requester's ID, resulting in the cancellation of the victim's subscription and billing cycle.","gemini-3-flash-preview","2026-07-15 11:29:22","2026-07-15 11:30:38",{"type":41,"vulnerable_version":42,"fixed_version":11,"vulnerable_browse":43,"vulnerable_zip":44,"fixed_browse":45,"fixed_zip":46,"all_tags":47},"plugin","6.2.1","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Ffluentform\u002Ftags\u002F6.2.1","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Ffluentform.6.2.1.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Ffluentform\u002Ftags\u002F6.2.2","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Ffluentform.6.2.2.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Ffluentform\u002Ftags"]