Advanced Coupons for WooCommerce Coupons <= 4.7.1 - Missing Authorization
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:NTechnical Details
<=4.7.1What Changed in the Fix
Changes introduced in v4.7.1.1
Source Code
WordPress.org SVN# 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 potentiallyacfw_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).
- Registration: The plugin registers AJAX hooks during initialization (likely via
Models/Bootstrap.phporModels/Admin_App.php).add_action( 'wp_ajax_acfw_get_all_coupons', array( $this, 'ajax_get_all_coupons' ) ); - Handler Execution: When a Contributor sends a request to
admin-ajax.phpwithaction=acfw_get_all_coupons. - Missing Check: The handler function
ajax_get_all_couponslikely checks if the user is logged in and verifies a nonce, but fails to check for a specific capability likemanage_woocommerceoredit_shop_coupons. - Data Sink: The function executes a
WP_Queryor useswc_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.
- Create Content: Since Contributors can create posts, they can access the block editor.
- Navigate: Use
browser_navigateto go to/wp-admin/post-new.php. - 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 likelyacfw_admin_varsoracfw_block_editor_vars.- Variable Name:
window.acfw_admin_vars - Key:
ajax_nonce - Execution:
browser_eval("window.acfw_admin_vars?.ajax_nonce").
- Variable Name:
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
- 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_
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
@@ -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.