CVE-2025-69347

Subscription for WooCommerce – WordPress Recurring Payments Plugin <= 1.8.10 - Authenticated (Customer+) Insecure Direct Object Reference

mediumAuthorization Bypass Through User-Controlled Key
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
1.8.11
Patched in
8d
Time to patch

Description

The Subscription for WooCommerce – WordPress Recurring Payments Plugin plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 1.8.10 due to missing validation on a user controlled key. This makes it possible for authenticated attackers, with Custom-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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=1.8.10
PublishedMarch 5, 2026
Last updatedMarch 12, 2026
Affected pluginsubscription

What Changed in the Fix

Changes introduced in v1.8.11

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2025-69347 (Insecure Direct Object Reference) ## 1. Vulnerability Summary The **Subscription for WooCommerce** plugin (versions <= 1.8.10) contains an Insecure Direct Object Reference (IDOR) vulnerability in its subscription action handling logic. The `SpringDevs\S…

Show full research plan

Exploitation Research Plan: CVE-2025-69347 (Insecure Direct Object Reference)

1. Vulnerability Summary

The Subscription for WooCommerce plugin (versions <= 1.8.10) contains an Insecure Direct Object Reference (IDOR) vulnerability in its subscription action handling logic. The SpringDevs\Subscription\Frontend\ActionController::control_action_subscrpt function processes requests to modify subscription statuses (cancel, reactivate, toggle auto-renewal) based on a user-provided subscrpt_id.

While the function validates a WordPress nonce (subscrpt_nonce), the nonce is not bound to a specific subscription ID, and the function fails to verify if the currently authenticated user owns the subscription associated with the provided ID. This allows any authenticated user with "Customer" permissions to perform unauthorized actions on any other user's subscription.

2. Attack Vector Analysis

  • Endpoint: [site-url]/my-account/view-subscription/[ANY_ID]/
  • Query Parameters:
    • subscrpt_id: The target Subscription Post ID (integer).
    • action: The unauthorized action (e.g., cancelled, reactive, renew-on, renew-off).
    • wpnonce: A valid subscrpt_nonce obtained from the attacker's own session.
  • Authentication Level: Authenticated (Customer or higher).
  • Preconditions: The attacker must have at least one subscription of their own to access the "View Subscription" page and obtain a valid nonce.

3. Code Flow

  1. Entry Point: The ActionController class (in includes/Frontend/ActionController.php) registers a listener on the before_single_subscrpt_content hook.
  2. Trigger: When a user visits the "View Subscription" endpoint (/my-account/view-subscription/ID), the MyAccount::view_subscrpt_content function is called, which eventually triggers the before_single_subscrpt_content hook via a template.
  3. Vulnerable Function: ActionController::control_action_subscrpt() is executed.
  4. Verification (Incomplete):
    • Line 35: wp_verify_nonce( $wpnonce, 'subscrpt_nonce' ) verifies the nonce. Since subscrpt_nonce is generated without an object-specific action string (see includes/Frontend/MyAccount.php line 111), the same nonce works for any ID for that user.
  5. Unauthorized Action (Sink):
    • The code proceeds to perform actions based on the action parameter.
    • If action=cancelled, it calls Action::status( 'pe_cancelled', $subscrpt_id ) (Line 52) or Action::status( $action, $subscrpt_id ) (Line 54).
    • If action=renew-off, it calls update_post_meta( $subscrpt_id, '_subscrpt_auto_renew', 0 ) (Line 60).
  6. Missing Check: There is no check (e.g., comparing $subscrpt_id ownership) before these status modifications occur.

4. Nonce Acquisition Strategy

The subscrpt_nonce is required. It is generated during the rendering of any "View Subscription" page the user is authorized to see.

  1. Navigate to Subscription Page: The attacker logs in and navigates to their own subscription: /my-account/view-subscription/[ATTACKER_SUB_ID]/.
  2. Extract Nonce: The nonce is used in the action buttons (Cancel, Renew, etc.).
  3. Browser Eval:
    // The nonce is often found in links within the .customer-details or .actions div
    // Alternatively, it might be enqueued, but here it's typically in the HTML links
    Array.from(document.querySelectorAll('a')).find(a => a.href.includes('wpnonce')).href.split('wpnonce=')[1].split('&')[0]
    
    If the plugin localizes data (not explicitly seen in provided snippets but common), check window.

5. Exploitation Strategy

The goal is to cancel a victim's subscription.

  1. Identify Victim ID: Determine the Subscription Post ID of the victim (e.g., via enumeration or leaked data).
  2. Obtain Nonce: Log in as attacker and visit /my-account/view-subscription/[ATTACKER_ID]. Extract wpnonce.
  3. Craft Request:
    • Method: GET
    • URL: http://localhost:8080/my-account/view-subscription/[ATTACKER_ID]/?subscrpt_id=[VICTIM_ID]&action=cancelled&wpnonce=[NONCE]
    • Note: The ID in the path doesn't matter as much as the subscrpt_id parameter, as the hook triggers on the page load.
  4. Execute: Use the http_request tool to send the authenticated GET request.

6. Test Data Setup

  1. Install Requirements: WooCommerce and the "Subscription" plugin.
  2. Create Product: Create a simple product with subscription enabled.
  3. Create Users:
    • victim_user (Subscriber)
    • attacker_user (Subscriber)
  4. Generate Subscriptions:
    • Process a checkout for victim_user to create subscription V_ID.
    • Process a checkout for attacker_user to create subscription A_ID.
  5. Ensure Status: Ensure V_ID status is active.

7. Expected Results

  • The server will process the request and redirect (via JavaScript/Location header) to the view page.
  • The subscription V_ID will have its post status changed to pe_cancelled (pending cancellation) or cancelled.

8. Verification Steps

  1. Check Status via WP-CLI:
    wp post get [VICTIM_ID] --field=post_status
    
  2. Verify Meta (if testing renewal toggle):
    wp post meta get [VICTIM_ID] _subscrpt_auto_renew
    
  3. Observe Response: A successful exploit often results in the attacker being redirected to /my-account/view-subscription/[VICTIM_ID] (if they try to view it) or seeing a success message if the plugin logic allows.

9. Alternative Approaches

  • Renewal Toggle: If cancellation is too disruptive for a PoC, use action=renew-off and verify the _subscrpt_auto_renew meta changes from 1 to 0.
  • Status Reaction: If a subscription is cancelled, try action=reactive to set it back to active.
  • Direct IDOR Enumeration: If nonces were somehow bound to IDs (which they aren't here), the attacker would need to find a way to leak a victim's specific nonce. Given they are not bound, a simple loop through IDs could theoretically disrupt all subscriptions on the platform.

Check if your site is affected.

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