CVE-2026-4351

Perfmatters <= 2.5.9 - Authenticated (Subscriber+) Arbitrary File Overwrite via 'snippets' Parameter

highImproper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
8.1
CVSS Score
8.1
CVSS Score
high
Severity
2.6.0
Patched in
1d
Time to patch

Description

The Perfmatters plugin for WordPress is vulnerable to arbitrary file overwrite via path traversal in all versions up to, and including, 2.5.9. This is due to the `PMCS::action_handler()` method processing the bulk action `activate`/`deactivate` handlers without any authorization check or nonce verification. The `$_GET['snippets'][]` values are passed unsanitized to `Snippet::activate()`/`Snippet::deactivate()` which call `Snippet::update()` then `file_put_contents()` with the traversed path. This makes it possible for authenticated attackers, with Subscriber-level access and above, to overwrite arbitrary files on the server with a fixed PHP docblock content, potentially causing denial of service by corrupting critical files like `.htaccess` or `index.php`.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
None
Confidentiality
High
Integrity
High
Availability

Technical Details

Affected versions<=2.5.9
PublishedApril 9, 2026
Last updatedApril 10, 2026
Affected pluginperfmatters
Research Plan
Unverified

This research plan outlines the steps required to exploit **CVE-2026-4351**, an arbitrary file overwrite vulnerability in the Perfmatters plugin (<= 2.5.9). --- ### 1. Vulnerability Summary The vulnerability exists in the `PMCS::action_handler()` method of the Perfmatters plugin. This method is re…

Show full research plan

This research plan outlines the steps required to exploit CVE-2026-4351, an arbitrary file overwrite vulnerability in the Perfmatters plugin (<= 2.5.9).


1. Vulnerability Summary

The vulnerability exists in the PMCS::action_handler() method of the Perfmatters plugin. This method is responsible for processing bulk actions related to "Snippets" (custom code snippets managed by the plugin). The method fails to implement any authorization checks (e.g., current_user_can) or nonce verification (CSRF protection). Furthermore, it accepts a snippets array parameter containing file paths that are not sanitized against path traversal. These paths are eventually passed to file_put_contents(), allowing an authenticated user with Subscriber-level permissions to overwrite arbitrary files on the server with a static PHP docblock.

2. Attack Vector Analysis

  • Endpoint: wp-admin/admin.php (or any admin-context request that triggers admin_init or init where the handler is registered).
  • Hook: Likely admin_init or init.
  • Authentication: Authenticated, Subscriber-level or higher.
  • Vulnerable Parameter: $_GET['snippets'] (array of strings).
  • Action Parameter: $_GET['action'] or a bulk action parameter set to activate or deactivate.
  • Payload: Path traversal sequence (e.g., ../../../../index.php).

3. Code Flow

  1. Entry Point: The plugin registers PMCS::action_handler() to a common WordPress hook (e.g., admin_init).
  2. Trigger: An authenticated user (even a Subscriber) accesses a URL with the specific action and snippets parameters.
  3. Lack of Guardrails: PMCS::action_handler() checks for the action (e.g., activate or deactivate) but fails to verify nonces or user capabilities.
  4. Traversal: The code iterates through the $_GET['snippets'] array. Each value is passed to Snippet::activate($snippet_id) or Snippet::deactivate($snippet_id).
  5. Sink: Snippet::activate() calls Snippet::update(). Inside Snippet::update(), the $snippet_id (the raw traversal string) is used to construct a file path.
  6. Overwrite: file_put_contents($traversed_path, $fixed_content) is called, overwriting the target file. The $fixed_content is a standard PHP docblock generated by the plugin.

4. Nonce Acquisition Strategy

According to the vulnerability description, the PMCS::action_handler() method processes requests without any nonce verification. Therefore, no nonce acquisition is required for this exploit.

If testing proves a nonce is required for the specific page parameter used in the request, the agent should:

  1. Navigate to the Perfmatters snippets page (if accessible) or the profile page.
  2. Search the HTML source for localized scripts related to perfmatters.
  3. Execute browser_eval("window.perfmatters?.nonce") (inferred JS key) to retrieve it.
    However, for this specific CVE, the primary flaw is the absence of this check.

5. Exploitation Strategy

The goal is to overwrite index.php in the WordPress root directory to demonstrate a Denial of Service (DoS) and arbitrary file modification.

  1. Login: Log in as a Subscriber user via the browser_navigate and browser_type tools.
  2. Identify Base Path: The plugin typically stores snippets in wp-content/uploads/perfmatters/ or wp-content/plugins/perfmatters/. Assuming the latter, the traversal required to reach the root index.php is approximately ../../../../index.php.
  3. Construct Request:
    • Method: GET (as the description mentions $_GET).
    • URL: http://localhost:8080/wp-admin/admin.php
    • Params:
      • page: perfmatters (inferred slug)
      • action: activate
      • snippets[]: ../../../../index.php
  4. Send Request: Use the http_request tool to send the authenticated GET request.

Example HTTP Request:

GET /wp-admin/admin.php?page=perfmatters&action=activate&snippets[]=../../../../index.php HTTP/1.1
Host: localhost:8080
Cookie: [Subscriber Cookies]

6. Test Data Setup

  1. Install Plugin: Ensure Perfmatters version <= 2.5.9 is installed and activated.
  2. Create User: Create a Subscriber user.
    wp user create attacker attacker@example.com --role=subscriber --user_pass=password
    
  3. Target Verification: Confirm the existence and content of the target file.
    cat /var/www/html/index.php
    

7. Expected Results

  • The server should return a 200 OK or a 302 Redirect (standard for admin actions).
  • The file at /var/www/html/index.php will be modified.
  • The original WordPress index.php content will be replaced with a PHP comment/docblock similar to:
    <?php
    /**
     * Perfmatters Custom Script
     * ...
     */
    
  • Accessing the site's homepage will result in a blank page or just the docblock content, effectively breaking the site.

8. Verification Steps

  1. Check File Content: Use the execution agent's shell to inspect the target file.
    grep "Perfmatters" /var/www/html/index.php
    
  2. Confirm DoS: Attempt to fetch the home page and check for the absence of standard WordPress HTML.
    curl -I http://localhost:8080/
    
    (Look for a drastically different Content-Length or a broken site).

9. Alternative Approaches

  • Deactivate Action: If action=activate fails, attempt action=deactivate.
  • Parameter variations: If snippets[] fails, try snippets as a string (though the code likely expects an array for bulk actions).
  • Alternative Target: Attempt to overwrite .htaccess to cause a 500 Internal Server Error.
    • snippets[]=../../../../.htaccess
  • Path Depth: If the plugin directory is nested differently, adjust traversal depth: ../../../../../index.php. Use wp-content/plugins/perfmatters/ as the starting reference point.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Perfmatters plugin for WordPress fails to perform authorization or nonce checks in its snippet bulk action handler. Authenticated attackers with Subscriber-level permissions can provide a path traversal string in the 'snippets' parameter, causing the plugin to overwrite arbitrary files on the server with a static PHP docblock.

Vulnerable Code

// perfmatters/inc/classes/PMCS.php
public function action_handler() {
    if (isset($_GET['action']) && isset($_GET['snippets'])) {
        $action = $_GET['action'];
        $snippets = $_GET['snippets']; // Unsanitized array from GET
        foreach ($snippets as $snippet_id) {
            if ($action === 'activate') {
                Snippet::activate($snippet_id);
            }
            // ... other actions
        }
    }
}

---

// perfmatters/inc/classes/Snippet.php
public static function activate($id) {
    // ... 
    self::update($id);
}

public static function update($id) {
    // $id contains traversal sequence like ../../../../index.php
    $file_path = PERFMATTERS_SNIPPETS_DIR . '/' . $id . '.php';
    $content = "<?php\n/**\n * Perfmatters Custom Script\n */";
    file_put_contents($file_path, $content); // Arbitrary file overwrite
}

Security Fix

--- a/perfmatters/inc/classes/PMCS.php
+++ b/perfmatters/inc/classes/PMCS.php
@@ -10,6 +10,14 @@
 public function action_handler() {
-    if (isset($_GET['action']) && isset($_GET['snippets'])) {
+    if (isset($_GET['action']) && isset($_GET['snippets']) && isset($_GET['_wpnonce'])) {
+        if (!current_user_can('manage_options')) {
+            wp_die(__('You do not have sufficient permissions to access this page.'));
+        }
+
+        if (!wp_verify_nonce($_GET['_wpnonce'], 'perfmatters_snippets_action')) {
+            wp_die(__('Security check failed.'));
+        }
+
         $action = $_GET['action'];
-        $snippets = $_GET['snippets'];
+        $snippets = array_map('sanitize_file_name', (array)$_GET['snippets']);
         foreach ($snippets as $snippet_id) {
             if ($action === 'activate') {

Exploit Outline

The exploit targets the PMCS::action_handler method which runs during admin initialization without sufficient security guards. 1. **Authentication:** The attacker must log in to the WordPress site with at least Subscriber-level credentials to access the admin context where the handler is registered. 2. **Request Construction:** The attacker sends a GET request to `/wp-admin/admin.php` (or any admin endpoint that triggers the handler) with specific parameters: - `page`: Set to the plugin's slug (e.g., `perfmatters`). - `action`: Set to `activate` or `deactivate`. - `snippets[]`: An array containing a path traversal payload such as `../../../../index.php` (note: the plugin appends '.php' so the target should account for this or use a null byte if applicable, though typically targeting .php files is sufficient). 3. **Execution:** Because the plugin lacks `current_user_can()` and `check_admin_referer()` checks, it processes the request and passes the traversal string to `file_put_contents()`. 4. **Result:** The targeted file (e.g., the root `index.php`) is overwritten with a fixed PHP comment block, rendering the site unusable (Denial of Service).

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.