CVE-2026-40794

Points Management System For Gamification, Ranks, Badges, and Loyalty Rewards Program – myCred <= 3.0.3 - Missing Authorization

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
3.0.4
Patched in
7d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=3.0.3
PublishedApril 24, 2026
Last updatedApril 30, 2026
Affected pluginmycred

What Changed in the Fix

Changes introduced in v3.0.4

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

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…

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

  1. Entry Point: An AJAX request is sent to admin-ajax.php with the action mycred-payout-transfer.
  2. Hook Registration: The Transfer add-on registers the action:
    add_action( 'wp_ajax_mycred-payout-transfer', 'mycred_ajax_payout_transfer' );
  3. Handler Logic: The handler mycred_ajax_payout_transfer() (in addons/transfer/mycred-transfers.php) retrieves the transfer_id from the $_POST request.
  4. Object Initialization: It instantiates the myCRED_Transfer object and calls $transfer->get_transfer( $transfer_id ).
  5. Vulnerable Sink: The handler proceeds to call a method like $transfer->payout() or updates the transfer status in the database without checking if the current_user_id() is authorized to modify this specific transaction or if the user is an administrator.
  6. Database Interaction: In mycred-transfer-object.php, the get_transfer() method (Line 144) uses the ID to query the $mycred_log_table using a LIKE clause on the serialized data column (Line 163).

4. Nonce Acquisition Strategy

The transfer actions are protected by a WordPress nonce, usually localized for the transfer form or history scripts.

  1. Identify Shortcode: The transfer functionality is triggered by the [mycred_transfer] shortcode.
  2. Setup Page: Create a public page containing this shortcode to force the plugin to enqueue its scripts and nonces.
  3. Extraction:
    • Navigate to the page as the Subscriber user.
    • Use browser_eval to extract the nonce from the global JavaScript object mycred_transfer or similar.
    • JS Variable: window.mycred_transfer?.nonce (or window.mycred_payout?.nonce for payout-specific actions).
    • Fallback: If not in a global object, check window.mycred?.nonce which 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.

  1. Preparation:
    • Enable the "Transfers" add-on.
    • Generate a "Pending" or "Incomplete" transfer (often used in myCred for scheduled transfers or transfers requiring approval).
  2. Action:
    • Obtain a valid transfer_id. Note that generate_new_transfer_id (Line 132) uses a predictable format: 'TXID' . timestamp . sender_id . recipient_id.
  3. 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]
    
  4. Expected Response: A JSON success message or a 1 (typical for successful WordPress AJAX).

6. Test Data Setup

  1. Activate Add-on:
    wp eval "mycred_update_addon_settings('transfers', array('active' => true));" (or use the UI).
  2. Create Users:
    • Victim (ID: 2)
    • Attacker (ID: 3, Subscriber role)
  3. Create Content:
    • wp post create --post_type=page --post_status=publish --post_title="Transfer" --post_content='[mycred_transfer]'
  4. 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.

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 incomplete to completed.
  • The Attacker (Subscriber) should be able to trigger this for a transfer they were not involved in.

8. Verification Steps

  1. Database Check:
    wp db query "SELECT * FROM wp_mycred_log WHERE data LIKE '%TXID...%'" --format=yaml
    Verify the ref_id or status in the data column has updated to reflect completion.
  2. 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-transfer is unavailable, attempt mycred-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 passing type=transfer&subaction=payout.
  • Guessing IDs: If no transfer_id is known, iterate through timestamps around the time a transfer was known to have occurred, as the TXID format is deterministic.
Research Findings
Static analysis — not yet PoC-verified

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

--- /home/deploy/wp-safety.org/data/plugin-versions/mycred/3.0.3/addons/transfer/includes/mycred-transfer-object.php	2025-09-18 11:05:44.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/mycred/3.0.4/addons/transfer/includes/mycred-transfer-object.php	2026-04-15 05:59:46.000000000 +0000
@@ -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.