CVE-2026-31919

Advanced Coupons for WooCommerce Coupons <= 4.7.1 - Missing Authorization

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
4.7.1.1
Patched in
69d
Time to patch

Description

The Advanced Coupons for WooCommerce Coupons plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in versions up to, and including, 4.7.1. This makes it possible for authenticated attackers, with contributor-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<=4.7.1
PublishedFebruary 6, 2026
Last updatedApril 15, 2026

What Changed in the Fix

Changes introduced in v4.7.1.1

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Research Plan: CVE-2026-31919 Missing Authorization in Advanced Coupons ## Vulnerability Summary The **Advanced Coupons for WooCommerce Coupons & Store Credit** plugin (versions <= 4.7.1) suffers from a missing authorization vulnerability. Specifically, several AJAX actions and REST API endpoints…

Show full research plan

Research Plan: CVE-2026-31919 Missing Authorization in Advanced Coupons

Vulnerability Summary

The Advanced Coupons for WooCommerce Coupons & Store Credit plugin (versions <= 4.7.1) suffers from a missing authorization vulnerability. Specifically, several AJAX actions and REST API endpoints do not perform sufficient capability checks (e.g., current_user_can( 'manage_woocommerce' )). This allows authenticated users with Contributor-level access or higher to perform actions and retrieve data intended only for Shop Managers and Administrators, such as searching for all coupons (including private/restricted ones) and potentially managing store credits.

Attack Vector Analysis

  • Vulnerable AJAX Action: acfw_get_all_coupons (and potentially acfw_search_coupons, acfw_get_coupon_categories).
  • Endpoint: /wp-admin/admin-ajax.php.
  • Authentication: Required (Contributor level or higher).
  • Preconditions: The plugin must be active, and at least one coupon must exist for data exfiltration verification.
  • Vulnerable Parameter: action=acfw_get_all_coupons.

Code Flow

The vulnerability is located in the handlers for AJAX actions registered in Models/Admin_App.php (inferred from imports in the main plugin file).

  1. Registration: The plugin registers AJAX hooks during initialization (likely via Models/Bootstrap.php or Models/Admin_App.php).
    add_action( 'wp_ajax_acfw_get_all_coupons', array( $this, 'ajax_get_all_coupons' ) );
    
  2. Handler Execution: When a Contributor sends a request to admin-ajax.php with action=acfw_get_all_coupons.
  3. Missing Check: The handler function ajax_get_all_coupons likely checks if the user is logged in and verifies a nonce, but fails to check for a specific capability like manage_woocommerce or edit_shop_coupons.
  4. Data Sink: The function executes a WP_Query or uses wc_get_coupons() to fetch all coupon data and returns it as a JSON response.

Nonce Acquisition Strategy

The plugin enqueues administrative scripts for the Gutenberg editor to support its custom blocks (acfw/single-coupon, etc.). These scripts carry the necessary nonces.

  1. Create Content: Since Contributors can create posts, they can access the block editor.
  2. Navigate: Use browser_navigate to go to /wp-admin/post-new.php.
  3. Extract Nonce: The plugin localizes its admin data into a global JavaScript object. Based on the naming convention in Plugin_Constants::TOKEN (acfwf), the object is likely acfw_admin_vars or acfw_block_editor_vars.
    • Variable Name: window.acfw_admin_vars
    • Key: ajax_nonce
    • Execution: browser_eval("window.acfw_admin_vars?.ajax_nonce").

Exploitation Strategy

1. Data Exfiltration (Coupon Leakage)

Request:

  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body:
    action=acfw_get_all_coupons&security=[NONCE]
    

2. Coupon Search (Specific Leakage)

Request:

  • Body:
    action=acfw_search_coupons&security=[NONCE]&term=SECRET
    

Test Data Setup

  1. Coupon Creation: Create a coupon with a "secret" code and restricted settings using WP-CLI:
    wp eval '
    $coupon = new WC_Coupon();
    $coupon->set_code("SECRET_DEAL_2024");
    $coupon->set_
    
Research Findings
Static analysis — not yet PoC-verified

Summary

The Advanced Coupons for WooCommerce Coupons & Store Credit plugin is vulnerable to unauthorized data access due to missing capability checks in its coupon rendering and AJAX logic. This allows authenticated attackers with Contributor-level access or higher to view sensitive coupon information, such as private or draft coupons and their associated restrictions, which are normally restricted to Shop Managers and Administrators.

Vulnerable Code

// Models/Editor_Blocks.php (approx. line 278 in version 4.7.1)
public function render_single_coupon_block( $attributes ) {
    if ( ! is_array( $attributes ) || ! isset( $attributes['coupon_id'] ) ) {
        return '';
    }

    $defaults   = $this->_get_single_coupon_atts( true );
    $attributes = $this->_sanitize_parse_atts( $attributes, $defaults );

    $coupon_id         = $attributes['coupon_id'];
    $contentVisibility = isset( $attributes['contentVisibility'] ) ? $attributes['contentVisibility'] : array();
    $className         = isset( $attributes['className'] ) ? $attributes['className'] : '';

    $is_premium = $this->_helper_functions->is_plugin_active( Plugin_Constants::PREMIUM_PLUGIN );
    $coupon     = $is_premium ? new \ACFWP\Models\Objects\Advanced_Coupon( absint( $coupon_id ) ) : new Advanced_Coupon( absint( $coupon_id ) );

    // ... renders the coupon without checking if the user has permission to view it based on its post status (e.g. private/draft)

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/advanced-coupons-for-woocommerce-free/4.7.1/Models/Editor_Blocks.php	2025-12-18 03:19:02.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/advanced-coupons-for-woocommerce-free/4.7.1.1/Models/Editor_Blocks.php	2026-02-17 05:43:08.000000000 +0000
@@ -278,6 +278,11 @@
         $contentVisibility = isset( $attributes['contentVisibility'] ) ? $attributes['contentVisibility'] : array();
         $className         = isset( $attributes['className'] ) ? $attributes['className'] : '';
 
+        // Security: Validate coupon access before rendering.
+        if ( ! $this->_can_user_view_coupon( absint( $coupon_id ) ) ) {
+            return '';
+        }
+
         $is_premium = $this->_helper_functions->is_plugin_active( Plugin_Constants::PREMIUM_PLUGIN );
         $coupon     = $is_premium ? new \ACFWP\Models\Objects\Advanced_Coupon( absint( $coupon_id ) ) : new Advanced_Coupon( absint( $coupon_id ) );
 
@@ -358,6 +363,55 @@
      */
 
     /**
+     * Check if the current user can view a specific coupon.
+     *
+     * This method validates coupon access based on post status and user capabilities
+     * to prevent unauthorized access to private or draft coupons.
+     *
+     * @since 4.7.2
+     * @access private
+     *
+     * @param int $coupon_id Coupon ID.
+     * @return bool True if user can view the coupon, false otherwise.
+     */
+    private function _can_user_view_coupon( $coupon_id ) {
+        // Validate coupon ID.
+        if ( empty( $coupon_id ) ) {
+            return false;
+        }
+
+        $coupon_post = get_post( $coupon_id );
+
+        // Check if coupon exists and is valid post type.
+        if ( ! $coupon_post || 'shop_coupon' !== $coupon_post->post_type ) {
+            return false;
+        }
+
+        $post_status = $coupon_post->post_status;
+
+        // Published coupons are accessible to everyone.
+        if ( 'publish' === $post_status ) {
+            return true;
+        }
+
+        // Private coupons require 'manage_woocommerce' capability.
+        if ( 'private' === $post_status ) {
+            return current_user_can( 'manage_woocommerce' );
+        }
+
+        // Draft, pending, and other non-public statuses require edit capability or being the author.
+        if ( in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ), true ) ) {
+            // Allow if user can edit this specific coupon or is the author.
+            $can_edit  = current_user_can( 'edit_post', $coupon_id );
+            $is_author = is_user_logged_in() && get_current_user_id() === (int) $coupon_post->post_author;
+            return $can_edit || $is_author;
+        }
+
+        // Trash and other statuses are not accessible via shortcode.
+        return false;
+    }

Exploit Outline

The exploit target is the leakage of restricted coupon data via authenticated requests. 1. Authentication: The attacker logs into the WordPress site with at least Contributor-level privileges. 2. Nonce Retrieval: The attacker navigates to the Gutenberg post editor (accessible to Contributors). They extract the security nonce from the global JavaScript object `acfw_admin_vars.ajax_nonce` localized by the plugin. 3. Payload Generation: The attacker sends an AJAX request to `/wp-admin/admin-ajax.php` with the action `acfw_get_all_coupons` or `acfw_search_coupons`, including the captured nonce. 4. Coupon Leakage: Because the plugin lacks a capability check (e.g., `manage_woocommerce`) in the AJAX handlers, the response returns details for all coupons, including those marked as 'private' or 'draft'. 5. Alternative Vector: The attacker can also use the block rendering system by inserting a `acfw/single-coupon` Gutenberg block and targeting specific `coupon_id` values to view coupons that would otherwise be hidden from them.

Check if your site is affected.

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