Points Management System For Gamification, Ranks, Badges, and Loyalty Rewards Program – myCred <= 3.0.3 - Missing Authorization
Description
The Points Management System For Gamification, Ranks, Badges, and Loyalty Rewards Program – myCred plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in versions up to, and including, 3.0.3. This makes it possible for authenticated attackers, with subscriber-level access and above, to perform an unauthorized action.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:NTechnical Details
What Changed in the Fix
Changes introduced in v3.0.4
Source Code
WordPress.org SVNThis research plan targets a **Missing Authorization** vulnerability in the **myCred** plugin (versions <= 3.0.3), specifically within the **Transfer** add-on. The flaw allows authenticated users (Subscriber and above) to perform unauthorized actions on point transfers, such as triggering payouts or…
Show full research plan
This research plan targets a Missing Authorization vulnerability in the myCred plugin (versions <= 3.0.3), specifically within the Transfer add-on. The flaw allows authenticated users (Subscriber and above) to perform unauthorized actions on point transfers, such as triggering payouts or status changes for transfers they do not own.
1. Vulnerability Summary
The myCRED_Transfer class in addons/transfer/includes/mycred-transfer-object.php manages the lifecycle of point transfers between users. While the plugin implements AJAX handlers for managing these transfers (like completing or "paying out" a pending transfer), it fails to verify that the user requesting the action has sufficient capabilities (e.g., manage_options) or is a legitimate party to the transaction. An attacker with Subscriber-level access can manipulate the status of transfers by providing a guessable or discovered transfer_id.
2. Attack Vector Analysis
- Endpoint:
wp-admin/admin-ajax.php - AJAX Action:
mycred-payout-transfer(inferred from common myCred transfer handlers) - Parameter:
transfer_id(The unique ID of the transfer, e.g.,TXID17400012). - Authentication: Authenticated, Subscriber-level or higher.
- Vulnerability Type: Missing Authorization (Missing
current_user_can()check). - Precondition: The "Transfers" add-on must be activated in myCred settings.
3. Code Flow
- Entry Point: An AJAX request is sent to
admin-ajax.phpwith the actionmycred-payout-transfer. - Hook Registration: The Transfer add-on registers the action:
add_action( 'wp_ajax_mycred-payout-transfer', 'mycred_ajax_payout_transfer' ); - Handler Logic: The handler
mycred_ajax_payout_transfer()(inaddons/transfer/mycred-transfers.php) retrieves thetransfer_idfrom the$_POSTrequest. - Object Initialization: It instantiates the
myCRED_Transferobject and calls$transfer->get_transfer( $transfer_id ). - Vulnerable Sink: The handler proceeds to call a method like
$transfer->payout()or updates the transfer status in the database without checking if thecurrent_user_id()is authorized to modify this specific transaction or if the user is an administrator. - Database Interaction: In
mycred-transfer-object.php, theget_transfer()method (Line 144) uses the ID to query the$mycred_log_tableusing aLIKEclause on the serializeddatacolumn (Line 163).
4. Nonce Acquisition Strategy
The transfer actions are protected by a WordPress nonce, usually localized for the transfer form or history scripts.
- Identify Shortcode: The transfer functionality is triggered by the
[mycred_transfer]shortcode. - Setup Page: Create a public page containing this shortcode to force the plugin to enqueue its scripts and nonces.
- Extraction:
- Navigate to the page as the Subscriber user.
- Use
browser_evalto extract the nonce from the global JavaScript objectmycred_transferor similar. - JS Variable:
window.mycred_transfer?.nonce(orwindow.mycred_payout?.noncefor payout-specific actions). - Fallback: If not in a global object, check
window.mycred?.noncewhich is a common catch-all in the plugin.
5. Exploitation Strategy
We will demonstrate the vulnerability by having a Subscriber user "payout" (complete) a transfer they did not initiate.
- Preparation:
- Enable the "Transfers" add-on.
- Generate a "Pending" or "Incomplete" transfer (often used in myCred for scheduled transfers or transfers requiring approval).
- Action:
- Obtain a valid
transfer_id. Note thatgenerate_new_transfer_id(Line 132) uses a predictable format:'TXID' . timestamp . sender_id . recipient_id.
- Obtain a valid
- HTTP Request:
POST /wp-admin/admin-ajax.php HTTP/1.1 Content-Type: application/x-www-form-urlencoded action=mycred-payout-transfer&transfer_id=[TARGET_TXID]&_wpnonce=[EXTRACTED_NONCE] - Expected Response: A JSON success message or a
1(typical for successful WordPress AJAX).
6. Test Data Setup
- Activate Add-on:
wp eval "mycred_update_addon_settings('transfers', array('active' => true));"(or use the UI). - Create Users:
Victim(ID: 2)Attacker(ID: 3, Subscriber role)
- Create Content:
wp post create --post_type=page --post_status=publish --post_title="Transfer" --post_content='[mycred_transfer]'
- Create Target Transfer:
- Manually insert or trigger a transfer with an "incomplete" or "pending" status from the Victim to another user. Record the
transfer_id.
- Manually insert or trigger a transfer with an "incomplete" or "pending" status from the Victim to another user. Record the
7. Expected Results
- The AJAX request should return a success status code (200 OK) and a payload indicating the transfer was processed.
- The transfer entry in the myCred log table should change from
incompletetocompleted. - The Attacker (Subscriber) should be able to trigger this for a transfer they were not involved in.
8. Verification Steps
- Database Check:
wp db query "SELECT * FROM wp_mycred_log WHERE data LIKE '%TXID...%'" --format=yaml
Verify theref_idor status in thedatacolumn has updated to reflect completion. - Balance Check:
wp mycred points get [Recipient_ID]
Verify that points were actually moved to the recipient after the unauthorized payout.
9. Alternative Approaches
- Transfer Refund: If
mycred-payout-transferis unavailable, attemptmycred-refund-transfer. This is even more critical as it would restore points to a sender's balance without authorization. - Direct Object Manipulation: If the plugin uses a generic AJAX action like
mycred-action, try passingtype=transfer&subaction=payout. - Guessing IDs: If no
transfer_idis known, iterate through timestamps around the time a transfer was known to have occurred, as theTXIDformat is deterministic.
Summary
The myCred plugin for WordPress is vulnerable to unauthorized point transfers due to a missing capability check in the transfer object's request processing. Authenticated attackers with subscriber-level access can exploit this by overriding the sender ID in a transfer request, allowing them to move points from any user's account to themselves or others.
Vulnerable Code
// addons/transfer/includes/mycred-transfer-object.php line 632 // Sender $sender_id = get_current_user_id(); if ( $this->request['user_id'] !== 'current' && absint( $this->request['user_id'] ) > 0 ) { $sender_id = absint( $this->request['user_id'] ); } $this->sender_id = $sender_id;
Security Fix
@@ -632,7 +632,10 @@ // Sender $sender_id = get_current_user_id(); if ( $this->request['user_id'] !== 'current' && absint( $this->request['user_id'] ) > 0 ) { - $sender_id = absint( $this->request['user_id'] ); + // Patch: Restrict sender_id overrides to point administrators to prevent spoofing. + if ( current_user_can( 'manage_options' ) || mycred()->user_is_point_admin() ) { + $sender_id = absint( $this->request['user_id'] ); + } } $this->sender_id = $sender_id;
Exploit Outline
The exploit involves a subscriber-level user spoofing the sender of a point transfer. 1. Enable the Transfers add-on in myCred settings. 2. Obtain a valid WordPress nonce for myCred transfer actions, typically by visiting a page where the [mycred_transfer] shortcode is rendered. 3. Send a POST request to wp-admin/admin-ajax.php with the action for submitting a transfer (e.g., mycred-submit-transfer). 4. Include the parameter 'user_id' in the request payload, setting it to the ID of the victim (the user from whom points should be deducted). 5. Because versions <= 3.0.3 do not check for administrative privileges before honoring the 'user_id' parameter, the plugin will process the transfer as if the victim initiated it, successfully deducting points from their account.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.