CVE-2025-12500

Checkout Field Manager (Checkout Manager) for WooCommerce <= 7.8.1 - Unauthenticated Limited File Upload

mediumUnrestricted Upload of File with Dangerous Type
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
7.8.2
Patched in
1d
Time to patch

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

Technical Details

Affected versions<=7.8.1
PublishedFebruary 18, 2026
Last updatedFebruary 19, 2026

What Changed in the Fix

Changes introduced in v7.8.2

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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 (both wp_ajax_ and wp_ajax_nopriv_ registered)
  • HTTP Method: POST
  • Payload Format: multipart/form-data
  • Required Parameters:
    • action: wooccm_checkout_attachment_upload
    • nonce: A valid WordPress nonce for the action wooccm_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_upload nonce.

3. Code Flow

  1. Entry Point: A POST request is sent to admin-ajax.php with action=wooccm_checkout_attachment_upload.
  2. Hook Registration: In lib/class-upload.php, the constructor of QuadLayers\WOOCCM\Upload registers 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' ) );
    
  3. Nonce Verification: The function ajax_checkout_attachment_upload is 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'.
  4. Processing: process_uploads is called with the file data and the key 'wooccm_checkout_attachment_upload'.
  5. Upload Path Filter: The function adds a filter to upload_dir to 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);
    
  6. Sink: The code iterates through the files and calls media_handle_upload:
    $attachment_id = media_handle_upload( $key, $post_id );
    
    This function handles the actual move to the filesystem and registration in the Media Library.

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.

  1. Triggering Enqueue: The plugin enqueues its scripts when a Checkout Field of type file is active.
  2. Preparation:
    • Ensure a product is in the cart (required to access the checkout page).
    • Navigate to the /checkout/ page.
  3. Extraction:
    • The plugin (based on QuadLayers' standard patterns) likely localizes the nonce in a global JavaScript object.
    • Search for wooccm_upload in the page source using grep.
    • Based on similar QuadLayers plugins, the variable is likely wooccm_upload_vars.
    • Command: browser_eval("window.wooccm_upload_vars?.nonce") or browser_eval("window.wooccm_params?.nonce").

5. Exploitation Strategy

  1. 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).
  2. Nonce Retrieval:
    • Navigate to /checkout/.
    • Extract the nonce for wooccm_upload from the JavaScript context.
  3. Execution:
    • Construct a multipart/form-data POST request.
    • Use http_request to send the payload to wp-admin/admin-ajax.php.
    • Payload:
      • action: wooccm_checkout_attachment_upload
      • nonce: [extracted_nonce]
      • wooccm_checkout_attachment_upload[0]: A file named poc.txt with content "CVE-2025-12500".
  4. 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

  1. Activate Dependencies:
    • wp plugin activate woocommerce
    • wp plugin activate woocommerce-checkout-manager
  2. Create Product:
    • wp post create --post_type=product --post_title="Exploit Target" --post_status=publish
  3. Add to Cart:
    • Identify the product ID and visit /?add-to-cart=[ID].
  4. 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_billing or wooccm_shipping option in the database to include a field with 'type' => 'file'.

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

  1. Check Directory: ls -l wp-content/uploads/wooccm_uploads/
  2. Check Media Library: wp post list --post_type=attachment --post_parent=0
  3. 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 and wp option update).
  • Different Nonce Names: If wooccm_upload_vars.nonce is not found, use grep -r "wp_localize_script" . in the plugin directory to find exactly which JS object and key carries the wooccm_upload nonce.
  • 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.
Research Findings
Static analysis — not yet PoC-verified

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

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-checkout-manager/7.8.1/lib/class-upload.php /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-checkout-manager/7.8.2/lib/class-upload.php
--- /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-checkout-manager/7.8.1/lib/class-upload.php	2024-06-01 08:35:32.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-checkout-manager/7.8.2/lib/class-upload.php	2025-11-27 21:16:10.000000000 +0000
@@ -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.