Introduction
An arbitrary file deletion vulnerability in the Perfmatters WordPress plugin puts approximately 200,000 sites at risk of complete takeover through a trivially exploitable path traversal flaw. Any authenticated user with Subscriber level access can delete critical server files, including wp-config.php, by injecting traversal sequences into a GET parameter that the plugin passes directly to PHP's unlink() function.
Perfmatters is a lightweight web performance plugin developed by forgemedia LLC, designed to improve Google Core Web Vitals scores and optimize asset loading on WordPress sites. With roughly 200,000 active installations, it occupies a meaningful share of the WordPress performance optimization market. Its wide deployment makes this vulnerability particularly relevant to the WordPress ecosystem at large.
Technical Information
The root cause of CVE-2026-4350 is a combination of three independent security failures in the PMCS::action_handler() method of the Perfmatters plugin. Each failure alone would be a notable weakness; together, they create a direct path from low privilege authentication to full site compromise.
Missing Input Sanitization
The action_handler() method reads the $_GET['delete'] parameter and concatenates the raw, unsanitized value directly with the plugin's storage directory path. This combined string is then passed to PHP's unlink() function. Because no filtering is applied, an attacker can supply path traversal sequences like ../ to escape the intended directory and target any file on the server's filesystem that the web server process has permission to delete.
Missing Authorization Check
The vulnerable code performs no capability check before processing the delete request. In WordPress, even the lowest authenticated role (Subscriber) can reach this code path. There is no call to current_user_can() or any equivalent authorization gate, meaning the file deletion functionality is effectively exposed to every logged in user.
Missing Nonce Verification
The endpoint also lacks nonce verification, which means it is additionally susceptible to cross site request forgery. An attacker could craft a malicious link or embed it in a page, and any authenticated WordPress user who visits that page would unknowingly trigger the file deletion.
Attack Flow
The exploitation sequence proceeds as follows:
- The attacker registers or compromises a WordPress account with at least Subscriber level access on a site running Perfmatters version 2.5.9.1 or earlier.
- The attacker crafts an HTTP GET request to the vulnerable endpoint, placing a path traversal payload such as
../../../wp-config.phpin thedeleteparameter. - The
PMCS::action_handler()method receives this value, concatenates it with the storage directory path, and callsunlink()on the resulting path. - The server deletes wp-config.php (or any other targeted file).
- With wp-config.php removed, WordPress can no longer connect to its database and presents the installation wizard to the next visitor.
- The attacker navigates to the site, completes the WordPress installation wizard with their own database credentials, and gains full administrative control.
This attack requires no special tooling and can be executed with a single crafted HTTP request.
Patch Information
The vulnerability was fixed in Perfmatters version 2.6.0, released on March 25, 2026 by forgemedia LLC. The patch addresses all three root cause issues through a layered defense approach.
Input Sanitization via normalize_snippet_file_name()
The most critical change introduces a new helper method that strips path traversal sequences from any filename before it reaches unlink(). The method applies both basename() (to remove all directory components) and WordPress's sanitize_file_name(), then validates that the result ends with .php and is not index.php:
public static function normalize_snippet_file_name($file_name) { if(!is_scalar($file_name)) { return ''; } $file_name = basename(sanitize_file_name((string)$file_name)); if(empty($file_name) || $file_name === 'index.php' || !preg_match('/\.php$/i', $file_name)) { return ''; } return $file_name; }
This is a defense in depth approach: basename() alone would neutralize traversal sequences, but combining it with sanitize_file_name() also strips shell special and otherwise dangerous characters. The .php extension check confines operations to code snippet files that Perfmatters itself manages. A companion normalize_snippet_file_names() method handles arrays of filenames for bulk operations, deduplicating them via array_unique().
Authorization (Capability) Check
The patched action_handler() now gates the entire function behind an administrator level permission check:
public static function action_handler() { if(!empty($_GET['page']) && $_GET['page'] == 'perfmatters') { // permissions and network scope check if(!current_user_can('manage_options') || !perfmatters_network_access()) { return; }
current_user_can('manage_options') is the standard WordPress capability for site administrators, and perfmatters_network_access() provides an additional gate for multisite environments. This ensures that only administrators with proper network level permissions can invoke snippet management actions.
Nonce Verification
The fix now requires a valid nonce on bulk actions through self::verify_action_nonce('pmcs_nonce', 'pmcs-action'), called immediately before processing the bulk action array. This ties each request to a specific authenticated session, preventing CSRF attacks.
Hardened delete() Method
The Snippet::delete() method itself was also updated to independently validate its inputs:
public static function delete($file_names) { $config = PMCS::get_snippet_config(); foreach(PMCS::normalize_snippet_file_names($file_names) as $file_name) { // ... safe deletion logic } }
Even if a code path somehow bypasses the action_handler() gate, the delete() method independently sanitizes its inputs, following a sound layered defense pattern.
In the vendor changelog, the fix is described as "Code snippet security updates to form submission handling."
Affected Systems and Versions
All versions of the Perfmatters WordPress plugin up to and including version 2.5.9.1 are vulnerable. The fix is available in version 2.6.0 and later.
The vulnerability requires:
- An active installation of the Perfmatters plugin (versions up to 2.5.9.1)
- An authenticated WordPress user account with at least Subscriber level access
- The web server process must have filesystem permissions to delete the targeted files
Approximately 200,000 WordPress sites are running the affected plugin.
Vendor Security History
Perfmatters has a documented history of security vulnerabilities involving input handling failures:
| Vulnerability ID | Type | Affected Versions | CVSS Score |
|---|---|---|---|
| CVE-2023-47876 | Reflected Cross Site Scripting | Up to 2.1.6 | 6.1 |
| CVE-2023-47877 | Stored Cross Site Scripting | Before 2.2.0 | 5.4 to 6.5 |
Both prior vulnerabilities were input neutralization failures. CVE-2026-4350 represents a significant escalation in severity, moving from XSS (which requires user interaction and targets the browser) to arbitrary file deletion with a direct path to full server side compromise. The vendor has historically patched reported vulnerabilities, and version 2.6.0 was released before the public disclosure of CVE-2026-4350.
References
- NVD Entry for CVE-2026-4350
- CVE Record: CVE-2026-4350
- Wordfence Threat Intel: CVE-2026-4350
- Wordfence Blog: 200,000 WordPress Sites Affected by Arbitrary File Deletion Vulnerability in Perfmatters
- Perfmatters Changelog
- NVD Entry for CVE-2023-47876
- NVD Entry for CVE-2023-47877
- CISA Known Exploited Vulnerabilities Catalog



