Wired Impact Volunteer Management <= 2.8 - Missing Authorization
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:NTechnical Details
<=2.8What Changed in the Fix
Changes introduced in v2.8.1
Source Code
WordPress.org SVN# 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_rsvpaction. - The attacker must know or guess a valid
rsvp_id.
- The attacker must obtain a valid WordPress nonce for the
3. Code Flow
- 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' ); - Entry Point: An unauthenticated POST request is sent to
admin-ajax.phpwithaction=wivm_remove_rsvp. - Vulnerable Function:
WI_Volunteer_Management_Admin::remove_rsvp()is executed. - 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... } - Sink: The RSVP record is deleted from the
{prefix}volunteer_rsvpstable.
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.
- Identify Target Page: Find a page containing the Volunteer Opportunities list.
- 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 /-->' - Extract Nonce: Navigate to the page and use
browser_evalto extract the nonce from thewivm_admin_varsorwivm_public_varsobject.- Variable Name:
window.wivm_admin_vars(quoted fromadmin/class-admin.phplocalization patterns). - Key:
remove_rsvp_nonce. - Command:
browser_eval("window.wivm_admin_vars?.remove_rsvp_nonce")
- Variable Name:
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
- Activate Plugin: Ensure
wired-impact-volunteer-managementis active. - Create Opportunity:
wp post create --post_type=volunteer_opp --post_title="Community Clean Up" --post_status=publish - 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) - Deploy Shortcode: Create a page to leak
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
@@ -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.