Checkout Field Manager (Checkout Manager) for WooCommerce <= 7.8.1 - Unauthenticated Limited File Upload
Description
The Checkout Field Manager (Checkout Manager) for WooCommerce plugin for WordPress is vulnerable to unauthenticated limited file upload in all versions up to, and including, 7.8.1. This is due to the plugin not properly verifying that a user is authorized to perform file upload actions via the "ajax_checkout_attachment_upload" function. This makes it possible for unauthenticated attackers to upload files to the server, though file types are limited to WordPress's default allowed MIME types (images, documents, etc.).
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=7.8.1What Changed in the Fix
Changes introduced in v7.8.2
Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2025-12500 ## 1. Vulnerability Summary The **Checkout Field Manager (Checkout Manager) for WooCommerce** plugin (versions <= 7.8.1) contains an unauthenticated limited file upload vulnerability. The vulnerability exists in the `ajax_checkout_attachment_upload` fun…
Show full research plan
Exploitation Research Plan - CVE-2025-12500
1. Vulnerability Summary
The Checkout Field Manager (Checkout Manager) for WooCommerce plugin (versions <= 7.8.1) contains an unauthenticated limited file upload vulnerability. The vulnerability exists in the ajax_checkout_attachment_upload function within the QuadLayers\WOOCCM\Upload class.
The plugin registers an AJAX action wp_ajax_nopriv_wooccm_checkout_attachment_upload, which allows unauthenticated users to trigger the upload handler. While the function performs a nonce check, it fails to implement any capability checks (like current_user_can) or verify that the request originates from a legitimate checkout session. Uploaded files are processed via media_handle_upload, which restricts file types to WordPress's default allowed list (e.g., images, PDFs, office documents), preventing direct RCE via PHP but allowing unauthorized storage consumption and potential hosting of malicious non-executable content.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - AJAX Action:
wooccm_checkout_attachment_upload(bothwp_ajax_andwp_ajax_nopriv_registered) - HTTP Method: POST
- Payload Format:
multipart/form-data - Required Parameters:
action:wooccm_checkout_attachment_uploadnonce: A valid WordPress nonce for the actionwooccm_upload.wooccm_checkout_attachment_upload[]: The file(s) to be uploaded.
- Authentication: None (Unauthenticated).
- Preconditions:
- WooCommerce must be active.
- The attacker must obtain a valid
wooccm_uploadnonce.
3. Code Flow
- Entry Point: A POST request is sent to
admin-ajax.phpwithaction=wooccm_checkout_attachment_upload. - Hook Registration: In
lib/class-upload.php, the constructor ofQuadLayers\WOOCCM\Uploadregisters the hooks:add_action( 'wp_ajax_wooccm_checkout_attachment_upload', array( $this, 'ajax_checkout_attachment_upload' ) ); add_action( 'wp_ajax_nopriv_wooccm_checkout_attachment_upload', array( $this, 'ajax_checkout_attachment_upload' ) ); - Nonce Verification: The function
ajax_checkout_attachment_uploadis called:public function ajax_checkout_attachment_upload() { if ( check_admin_referer( 'wooccm_upload', 'nonce' ) && isset( $_FILES['wooccm_checkout_attachment_upload'] ) ) { // ... $attachment_ids = $this->process_uploads( $files, 'wooccm_checkout_attachment_upload' );check_admin_referer('wooccm_upload', 'nonce')verifies that$_REQUEST['nonce']matches the action'wooccm_upload'. - Processing:
process_uploadsis called with the file data and the key'wooccm_checkout_attachment_upload'. - Upload Path Filter: The function adds a filter to
upload_dirto change the destination:add_filter('upload_dir', function ( $param ) { $param['path'] = sprintf( '%s/wooccm_uploads', $param['basedir'] ); $param['url'] = sprintf( '%s/wooccm_uploads', $param['baseurl'] ); return $param; }, 10); - Sink: The code iterates through the files and calls
media_handle_upload:
This function handles the actual move to the filesystem and registration in the Media Library.$attachment_id = media_handle_upload( $key, $post_id );
4. Nonce Acquisition Strategy
The wooccm_upload nonce is generated for unauthenticated users and is localized into the frontend scripts, specifically on the WooCommerce Checkout page.
- Triggering Enqueue: The plugin enqueues its scripts when a Checkout Field of type
fileis active. - Preparation:
- Ensure a product is in the cart (required to access the checkout page).
- Navigate to the
/checkout/page.
- Extraction:
- The plugin (based on QuadLayers' standard patterns) likely localizes the nonce in a global JavaScript object.
- Search for
wooccm_uploadin the page source usinggrep. - Based on similar QuadLayers plugins, the variable is likely
wooccm_upload_vars. - Command:
browser_eval("window.wooccm_upload_vars?.nonce")orbrowser_eval("window.wooccm_params?.nonce").
5. Exploitation Strategy
- Setup:
- Create a simple product and add it to the cart.
- Ensure a "File Upload" field is added to the checkout (use WP-CLI to check plugin options if necessary, though it is often enabled by default).
- Nonce Retrieval:
- Navigate to
/checkout/. - Extract the nonce for
wooccm_uploadfrom the JavaScript context.
- Navigate to
- Execution:
- Construct a
multipart/form-dataPOST request. - Use
http_requestto send the payload towp-admin/admin-ajax.php. - Payload:
action:wooccm_checkout_attachment_uploadnonce:[extracted_nonce]wooccm_checkout_attachment_upload[0]: A file namedpoc.txtwith content "CVE-2025-12500".
- Construct a
- Verification:
- The server should return a JSON success response containing an attachment ID.
- Verify the file exists in the specific upload directory.
6. Test Data Setup
- Activate Dependencies:
wp plugin activate woocommercewp plugin activate woocommerce-checkout-manager
- Create Product:
wp post create --post_type=product --post_title="Exploit Target" --post_status=publish
- Add to Cart:
- Identify the product ID and visit
/?add-to-cart=[ID].
- Identify the product ID and visit
- Ensure File Field:
- The plugin usually allows adding a file field via its settings. If none exists, one must be created via the admin UI or by updating the
wooccm_billingorwooccm_shippingoption in the database to include a field with'type' => 'file'.
- The plugin usually allows adding a file field via its settings. If none exists, one must be created via the admin UI or by updating the
7. Expected Results
- HTTP Response: Status 200 OK.
- Body:
{"success":true,"data":[ATTACHMENT_ID]}(where ATTACHMENT_ID is an integer). - Filesystem: A file should be created at
wp-content/uploads/wooccm_uploads/poc.txt.
8. Verification Steps
- Check Directory:
ls -l wp-content/uploads/wooccm_uploads/ - Check Media Library:
wp post list --post_type=attachment --post_parent=0 - Check Content:
cat wp-content/uploads/wooccm_uploads/poc.txt
9. Alternative Approaches
- Missing File Field: If the nonce isn't localized on the checkout page because no file fields are active, the agent should attempt to activate a file field using:
wp option get wooccm_billing(then modify the JSON to add a file type field andwp option update). - Different Nonce Names: If
wooccm_upload_vars.nonceis not found, usegrep -r "wp_localize_script" .in the plugin directory to find exactly which JS object and key carries thewooccm_uploadnonce. - Attachment IDs: If the success response is received but the file isn't found, check the default
wp-content/uploads/directory, as the filter might have failed or been overridden.
Summary
The Checkout Field Manager for WooCommerce plugin is vulnerable to unauthenticated limited file upload because the 'ajax_checkout_attachment_upload' function only verifies a nonce without implementing any capability or session-based authorization checks. Attackers can upload files allowed by WordPress's default MIME type list (like images or documents) to the server's upload directory.
Vulnerable Code
// lib/class-upload.php line 16 public function __construct() { add_action( 'wp_ajax_wooccm_order_attachment_update', array( $this, 'ajax_delete_attachment' ) ); add_action( 'wp_ajax_nopriv_wooccm_order_attachment_update', array( $this, 'ajax_delete_attachment' ) ); // Checkout // -----------------------------------------------------------------------. add_action( 'wp_ajax_wooccm_checkout_attachment_upload', array( $this, 'ajax_checkout_attachment_upload' ) ); add_action( 'wp_ajax_nopriv_wooccm_checkout_attachment_upload', array( $this, 'ajax_checkout_attachment_upload' ) ); add_action( 'woocommerce_checkout_update_order_meta', array( $this, 'update_attachment_ids' ), 99 ); } --- // lib/class-upload.php line 143 public function ajax_checkout_attachment_upload() { if ( check_admin_referer( 'wooccm_upload', 'nonce' ) && isset( $_FILES['wooccm_checkout_attachment_upload'] ) ) { // It cannot be wp_unslash becouse it has images paths // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash $files = wc_clean( $_FILES['wooccm_checkout_attachment_upload'] ); if ( empty( $files ) ) { wc_add_notice( esc_html__( 'No uploads were recognized. Files were not uploaded.', 'woocommerce-checkout-manager' ), 'error' ); wp_send_json_error(); } $attachment_ids = $this->process_uploads( $files, 'wooccm_checkout_attachment_upload' ); if ( count( $attachment_ids ) ) { wp_send_json_success( $attachment_ids ); } wc_add_notice( esc_html__( 'Unknown error.', 'woocommerce-checkout-manager' ), 'error' ); wp_send_json_error(); } }
Security Fix
@@ -11,7 +11,6 @@ public function __construct() { add_action( 'wp_ajax_wooccm_order_attachment_update', array( $this, 'ajax_delete_attachment' ) ); - add_action( 'wp_ajax_nopriv_wooccm_order_attachment_update', array( $this, 'ajax_delete_attachment' ) ); // Checkout // -----------------------------------------------------------------------. @@ -34,6 +33,20 @@ require_once ABSPATH . 'wp-admin/includes/image.php'; } + // Security Fix: CVE-2025-12500 - Add upload limits + $max_files_per_upload = apply_filters( 'wooccm_max_files_per_upload', 10 ); + if ( count( $files['name'] ) > $max_files_per_upload ) { + wc_add_notice( + sprintf( + /* translators: %d: maximum number of files */ + esc_html__( 'You can only upload a maximum of %d files at once.', 'woocommerce-checkout-manager' ), + $max_files_per_upload + ), + 'error' + ); + return array(); + } + $attachment_ids = array(); add_filter( @@ -141,25 +154,63 @@ } public function ajax_checkout_attachment_upload() { - if ( check_admin_referer( 'wooccm_upload', 'nonce' ) && isset( $_FILES['wooccm_checkout_attachment_upload'] ) ) { + // Security Fix: CVE-2025-12500 - Added proper authorization checks - // It cannot be wp_unslash becouse it has images paths - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash - $files = wc_clean( $_FILES['wooccm_checkout_attachment_upload'] ); + // Step 1: Verify nonce for CSRF protection + if ( ! check_admin_referer( 'wooccm_upload', 'nonce' ) ) { + wp_send_json_error( array( 'message' => esc_html__( 'Security check failed.', 'woocommerce-checkout-manager' ) ) ); + } - if ( empty( $files ) ) { - wc_add_notice( esc_html__( 'No uploads were recognized. Files were not uploaded.', 'woocommerce-checkout-manager' ), 'error' ); - wp_send_json_error(); + // Step 2: Verify files are present + if ( ! isset( $_FILES['wooccm_checkout_attachment_upload'] ) ) { + wp_send_json_error( array( 'message' => esc_html__( 'No files provided.', 'woocommerce-checkout-manager' ) ) ); + } + + // Step 3: Authorization check - Allow if user is logged in OR has valid WooCommerce session + $current_user = wp_get_current_user(); + $is_user_logged_in = $current_user->ID > 0; + + if ( $is_user_logged_in ) { + // For logged-in users, verify they have permission to make purchases + if ( ! current_user_can( 'read' ) ) { + wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to upload files.', 'woocommerce-checkout-manager' ) ) ); + } + } else { + // For guest users, verify they have an active WooCommerce session (checkout in progress) + if ( ! class_exists( 'WC' ) || ! WC()->session ) { + wp_send_json_error( array( 'message' => esc_html__( 'Session expired. Please refresh the page.', 'woocommerce-checkout-manager' ) ) ); } - $attachment_ids = $this->process_uploads( $files, 'wooccm_checkout_attachment_upload' ); + // Verify WooCommerce session is initialized and valid + $session_handler = WC()->session; + if ( ! $session_handler || ! $session_handler->get_session_cookie() ) { + wp_send_json_error( array( 'message' => esc_html__( 'Invalid session. Please refresh the page.', 'woocommerce-checkout-manager' ) ) ); + } - if ( count( $attachment_ids ) ) { - wp_send_json_success( $attachment_ids ); + // Additional security: Check if customer data exists in session (indicates checkout process) + $customer = $session_handler->get( 'customer' ); + if ( empty( $customer ) ) { + wp_send_json_error( array( 'message' => esc_html__( 'Please start checkout process before uploading files.', 'woocommerce-checkout-manager' ) ) ); + } } - wc_add_notice( esc_html__( 'Unknown error.', 'woocommerce-checkout-manager' ), 'error' ); - wp_send_json_error(); - } + + // It cannot be wp_unslash becouse it has images paths + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash + $files = wc_clean( $_FILES['wooccm_checkout_attachment_upload'] ); + + if ( empty( $files ) ) { + wc_add_notice( esc_html__( 'No uploads were recognized. Files were not uploaded.', 'woocommerce-checkout-manager' ), 'error' ); + wp_send_json_error( array( 'message' => esc_html__( 'No uploads were recognized. Files were not uploaded.', 'woocommerce-checkout-manager' ) ) ); + } + + $attachment_ids = $this->process_uploads( $files, 'wooccm_checkout_attachment_upload' ); + + if ( count( $attachment_ids ) ) { + wp_send_json_success( $attachment_ids ); + } + + wc_add_notice( esc_html__( 'Unknown error.', 'woocommerce-checkout-manager' ), 'error' ); + wp_send_json_error( array( 'message' => esc_html__( 'Unknown error.', 'woocommerce-checkout-manager' ) ) ); }
Exploit Outline
To exploit this vulnerability, an unauthenticated attacker first adds a product to the cart and navigates to the WooCommerce checkout page. From the page's source code or localized scripts, they extract the 'wooccm_upload' nonce (typically found in variables like 'wooccm_params'). The attacker then constructs a multipart POST request to '/wp-admin/admin-ajax.php' with the 'action' set to 'wooccm_checkout_attachment_upload', the extracted nonce, and the target file assigned to the 'wooccm_checkout_attachment_upload[]' parameter. Upon successful execution, the plugin processes the file using WordPress's 'media_handle_upload' and stores it in the 'wp-content/uploads/wooccm_uploads/' directory.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.