Perfmatters <= 2.5.9 - Authenticated (Subscriber+) Arbitrary File Overwrite via 'snippets' Parameter
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:HTechnical Details
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 triggersadmin_initorinitwhere the handler is registered). - Hook: Likely
admin_initorinit. - Authentication: Authenticated, Subscriber-level or higher.
- Vulnerable Parameter:
$_GET['snippets'](array of strings). - Action Parameter:
$_GET['action']or a bulk action parameter set toactivateordeactivate. - Payload: Path traversal sequence (e.g.,
../../../../index.php).
3. Code Flow
- Entry Point: The plugin registers
PMCS::action_handler()to a common WordPress hook (e.g.,admin_init). - Trigger: An authenticated user (even a Subscriber) accesses a URL with the specific
actionandsnippetsparameters. - Lack of Guardrails:
PMCS::action_handler()checks for theaction(e.g.,activateordeactivate) but fails to verify nonces or user capabilities. - Traversal: The code iterates through the
$_GET['snippets']array. Each value is passed toSnippet::activate($snippet_id)orSnippet::deactivate($snippet_id). - Sink:
Snippet::activate()callsSnippet::update(). InsideSnippet::update(), the$snippet_id(the raw traversal string) is used to construct a file path. - Overwrite:
file_put_contents($traversed_path, $fixed_content)is called, overwriting the target file. The$fixed_contentis 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:
- Navigate to the Perfmatters snippets page (if accessible) or the profile page.
- Search the HTML source for localized scripts related to
perfmatters. - 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.
- Login: Log in as a Subscriber user via the
browser_navigateandbrowser_typetools. - Identify Base Path: The plugin typically stores snippets in
wp-content/uploads/perfmatters/orwp-content/plugins/perfmatters/. Assuming the latter, the traversal required to reach the rootindex.phpis approximately../../../../index.php. - Construct Request:
- Method:
GET(as the description mentions$_GET). - URL:
http://localhost:8080/wp-admin/admin.php - Params:
page:perfmatters(inferred slug)action:activatesnippets[]:../../../../index.php
- Method:
- Send Request: Use the
http_requesttool 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
- Install Plugin: Ensure Perfmatters version <= 2.5.9 is installed and activated.
- Create User: Create a Subscriber user.
wp user create attacker attacker@example.com --role=subscriber --user_pass=password - 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 OKor a302 Redirect(standard for admin actions). - The file at
/var/www/html/index.phpwill be modified. - The original WordPress
index.phpcontent 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
- Check File Content: Use the execution agent's shell to inspect the target file.
grep "Perfmatters" /var/www/html/index.php - Confirm DoS: Attempt to fetch the home page and check for the absence of standard WordPress HTML.
(Look for a drastically different Content-Length or a broken site).curl -I http://localhost:8080/
9. Alternative Approaches
- Deactivate Action: If
action=activatefails, attemptaction=deactivate. - Parameter variations: If
snippets[]fails, trysnippetsas a string (though the code likely expects an array for bulk actions). - Alternative Target: Attempt to overwrite
.htaccessto cause a 500 Internal Server Error.snippets[]=../../../../.htaccess
- Path Depth: If the plugin directory is nested differently, adjust traversal depth:
../../../../../index.php. Usewp-content/plugins/perfmatters/as the starting reference point.
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
@@ -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.