FALCONINTERNET
Remote Code Execution in WordPress: Please Update Your Websites ASAP Details

Contact Form 7 6.2.4 fixes CVE-2026-114432 upload flaw; PoC due Nov 19

WordPress
Contact Form 7 6.2.4 fixes CVE-2026-114432 upload flaw; PoC due Nov 19

Contact Form 7, the WordPress form plugin with more than 10 million active installations, has a file upload flaw tracked as CVE-2026-114432. A visitor with no account can submit a form and leave a file of their choosing on the server. On some common Apache setups, that file can then be requested as PHP and run. The CVE record was published on November 8. The fix shipped earlier, in version 6.2.4 on October 29, so many sites with automatic plugin updates are already patched. WPScan, which coordinated the disclosure, says it will publish its proof of concept on November 19. Sites that still run 6.2 through 6.2.3 have about a week before working exploit code is public.

Versions affected and fixed

  • Affected: Contact Form 7 6.2, 6.2.1, 6.2.2 and 6.2.3. The 6.2 branch came out on October 6 and requires WordPress 7.1 and PHP 8.3.
  • Fixed: 6.2.4 (October 29). A 6.2.4.1 build followed the same day with a library update, and the current release on WordPress.org is 6.2.5, published November 10.
  • The CVE record does not list the 6.1 branch, which sites on WordPress older than 7.1 are held to. It does use the same old extension denylist described below, so the server rules in this post apply to it too.
  • Only forms that contain a file field ([file] or [file*]) are exposed. The plugin skips upload handling entirely for forms without one.

The CVE record credits Shinya Murakami. As of November 9, CISA rated exploitation "none" and the CVE was not in its Known Exploited Vulnerabilities catalog.

Three fixes in the 6.2.4 code

We diffed the 6.2.3 and 6.2.4 packages from WordPress.org. The patch changes the upload path in three places.

  • File names containing invalid UTF-8 bytes could slip through the plugin's file name sanitizer. Version 6.2.4 scrubs invalid UTF-8 before any other check.
  • Older versions renamed dangerous extensions using a denylist: php, phtml, pl, py, rb, cgi, asp and aspx, with an optional trailing digit. That list did not include phar. Version 6.2.4 replaces it with a safelist of audio, video and image extensions plus pdf and txt. Any other extension gets _.txt appended.
  • If a multi-file field failed validation partway through, files already moved into place were never registered for deletion. They stayed in wp-content/uploads/wpcf7_uploads/ under a random 10-digit subdirectory until the plugin's background sweep removed them. The sweep deletes items older than 60 seconds, up to 100 per page load.

Server setups where the upload becomes code execution

Stock Apache on Debian and Ubuntu meets the conditions for remote code execution described in the CVE. The PHP module and PHP-FPM configs that those distributions ship route any file matching .+\.ph(?:ar|p|tml)$ to PHP, so a stored .phar file runs when requested. The default /etc/apache2/apache2.conf also sets Options Indexes and AllowOverride None for /var/www. Directory listing lets an attacker find the random subdirectory, and AllowOverride None means Apache ignores the .htaccess file with Require all denied that Contact Form 7 writes into wpcf7_uploads.

If AllowOverride is set to All (common on WordPress hosts, because permalinks need it), the plugin's .htaccess blocks all web access to that directory and the code execution path closes. Nginx ignores .htaccess, but it does not list directories by default and most WordPress configs pass only .php to PHP-FPM. There, the flaw still lets an outsider write an arbitrary file, such as HTML or SVG, that the site may serve from its own domain.

The two published scores reflect this split. WPScan, the CNA, rates the bug 7.5 on its own site and put no score in the CVE record. CISA's enrichment scores it 9.8 (AV:N/AC:L/PR:N/UI:N) on the assumption of full code execution.

Checking and updating sites with WP-CLI

wp plugin get contact-form-7 --field=version
wp plugin update contact-form-7

To list which forms on a site accept uploads:

P=$(wp db prefix)
wp db query "SELECT p.ID, p.post_title FROM ${P}posts p
  JOIN ${P}postmeta m ON m.post_id = p.ID
  WHERE p.post_type = 'wpcf7_contact_form'
  AND m.meta_key = '_form' AND m.meta_value LIKE '%[file%'"

After updating, submit a test entry with an attachment on each of those forms to confirm the email still arrives.

Apache and nginx rules for the uploads directory

Contact Form 7 attaches uploaded files to the outgoing email and then deletes them. Nothing in wpcf7_uploads ever needs to be served over HTTP, so deny the directory outright and stop PHP from running anywhere under uploads. On Apache, put this in the virtual host so it applies whatever AllowOverride says:

<Directory /var/www/example.com/wp-content/uploads>
    Options -Indexes
    <FilesMatch "(?i)\.(php\d?|phar|phtml|pht)$">
        Require all denied
    </FilesMatch>
</Directory>
<Directory /var/www/example.com/wp-content/uploads/wpcf7_uploads>
    Require all denied
</Directory>

Server-wide, narrow the handler pattern in /etc/apache2/mods-enabled/php8.3.conf or /etc/apache2/conf-enabled/php8.3-fpm.conf from .+\.ph(?:ar|p|tml)$ to .+\.php$ unless an application on the server needs the others. Run apachectl configtest before reloading.

On nginx, add these inside the server block. The ^~ prefix stops nginx from checking regex locations, and the regex block must appear above your location ~ \.php$ block because nginx uses the first regex that matches.

location ^~ /wp-content/uploads/wpcf7_uploads/ { deny all; }
location ~* ^/wp-content/uploads/.*\.(php\d?|phar|phtml|pht)$ { deny all; }

Test with nginx -t, reload, then confirm that a request for a test file in that path returns 403.

Finding files written before the patch

Updating does not remove anything an attacker already stored. Search for executable file types under uploads created since 6.2 shipped, and for anything left in the Contact Form 7 temporary directory:

find /var/www -path '*/wp-content/uploads/*' -type f \
  \( -iname '*.php*' -o -iname '*.phar' -o -iname '*.pht*' -o -name '.htaccess' \) \
  -newermt 2026-10-06 -ls

find /var/www -path '*/uploads/wpcf7_uploads/*' -type f ! -name .htaccess -mmin +10 -ls

Then search web server logs for any request to that directory. Legitimate visitors never request files there, so treat every hit as suspect:

zgrep -h 'wpcf7_uploads' /var/log/apache2/access.log* /var/log/nginx/access.log* 2>/dev/null

On the WordPress servers Falcon Internet manages, PHP execution is denied under uploads and directory listing is off, which closes the execution path for this class of bug whatever the plugin version.

Need this handled instead of explained?

We do this for a living — talk to an engineer about your setup.