CVE-2025-14388

PhastPress <= 3.7 - Unauthenticated Arbitrary File Read via Null Byte Injection

criticalImproper Neutralization of Null Byte or NUL Character
9.8
CVSS Score
9.8
CVSS Score
critical
Severity
3.8
Patched in
1d
Time to patch

Description

The PhastPress plugin for WordPress is vulnerable to Unauthenticated Arbitrary File Read via null byte injection in all versions up to, and including, 3.7. This is due to a discrepancy between the extension validation in `getExtensionForURL()` which operates on URL-decoded paths, and `appendNormalized()` which strips everything after a null byte before constructing the filesystem path. This makes it possible for unauthenticated attackers to read arbitrary files from the webroot, including wp-config.php, by appending a double URL-encoded null byte (%2500) followed by an allowed extension (.txt) to the file path.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=3.7
PublishedDecember 22, 2025
Last updatedDecember 23, 2025
Affected pluginphastpress

Source Code

WordPress.org SVN
Research Plan
Unverified

This exploitation research plan targets **CVE-2025-14388** in the **PhastPress** plugin. The vulnerability allows unauthenticated attackers to read arbitrary files (like `wp-config.php`) due to inconsistent handling of null bytes during file extension validation versus path normalization. --- ### …

Show full research plan

This exploitation research plan targets CVE-2025-14388 in the PhastPress plugin. The vulnerability allows unauthenticated attackers to read arbitrary files (like wp-config.php) due to inconsistent handling of null bytes during file extension validation versus path normalization.


1. Vulnerability Summary

  • Vulnerability: Unauthenticated Arbitrary File Read via Null Byte Injection.
  • Plugin: PhastPress <= 3.7.
  • Vulnerable Functions: getExtensionForURL() and appendNormalized().
  • Root Cause: The plugin validates file extensions against a URL-decoded string but truncates the path at the first null byte when constructing the final filesystem path. By using a double-encoded null byte (%2500), an attacker can bypass extension filters (e.g., making the plugin think it's serving a .txt or .js file) while the filesystem operation reads the target file before the null byte.

2. Attack Vector Analysis

  • Endpoint: The plugin serves optimized assets via a specific query parameter or rewrite rule. Based on PhastPress architecture, this is typically handled via the ?phast_get_... parameter or similar interceptors in init.
  • Target Parameter: A parameter representing the path to a resource (likely service or url).
  • Authentication: None (Unauthenticated).
  • Preconditions: The plugin must be active. The attacker needs to know the relative path from the webroot to wp-config.php.

3. Code Flow (Inferred)

  1. Entry Point: A request is made to a PhastPress-controlled URL (e.g., /?phast_get_asset=...).
  2. Validation: The plugin calls getExtensionForURL($url). This function URL-decodes the input. If the input is wp-config.php%2500.js, it decodes to wp-config.php\0.js. The extension check sees .js and approves it.
  3. Normalization: The plugin calls appendNormalized(). This function identifies the null byte (\0) and strips everything following it, resulting in the string wp-config.php.
  4. File Read: The resulting string is used in a filesystem call (e.g., file_get_contents() or readfile()) within the webroot context, returning the contents of wp-config.php.

4. Nonce Acquisition Strategy

Based on the "Arbitrary File Read" nature of this vulnerability (serving assets), it is highly likely that no nonce is required for the retrieval endpoint. PhastPress serves optimized versions of public scripts/styles to all visitors.

Verification of Nonce Requirement:
If the endpoint does require a nonce (check for check_ajax_referer or wp_verify_nonce in the handler for the asset-serving logic), follow this strategy:

  1. Search for Nonce Exposure: Check Phast\Plugin->enqueue_scripts or similar.
  2. Shortcode: PhastPress usually runs automatically on all pages to optimize scripts. No specific shortcode is likely needed.
  3. Extraction:
    • Navigate to the homepage.
    • Use browser_eval to look for localized data: browser_eval("window.phast_config").
    • Look for keys like nonce or ajax_nonce.

5. Exploitation Strategy

The goal is to read wp-config.php.

  • Step 1: Identify the Resource Path. PhastPress usually maps assets relative to the WordPress content or root.
  • Step 2: Construct the Payload.
    • Target: wp-config.php
    • Null Byte: %2500 (Double encoded so it survives the first internal URL decode).
    • Allowed Extension: .js, .css, or .txt.
    • Full Payload Component: wp-config.php%2500.js
  • Step 3: Trigger the Request.
    We will target the common asset-serving entry point for PhastPress.

HTTP Request (Example):

GET /?phast_get_asset=wp-config.php%2500.js HTTP/1.1
Host: localhost

(Note: The exact parameter name phast_get_asset should be verified by checking the init hook in the plugin source.)

6. Test Data Setup

  1. Environment: Standard WordPress installation.
  2. Plugin: Install and activate PhastPress <= 3.7.
  3. Content: No special content is required, as wp-config.php exists by default in the webroot.
  4. Confirm Path: Ensure wp-config.php is in the directory where the plugin expects to find assets (usually the site root).

7. Expected Results

  • Successful Exploit: The HTTP response body contains the PHP source code of wp-config.php, including DB_NAME, DB_USER, and DB_PASSWORD.
  • Response Headers: Content-Type might be text/javascript or application/x-javascript (due to the .js suffix) despite containing PHP code.
  • HTTP Status: 200 OK.

8. Verification Steps

  1. Check Response Content: Use http_request and verify the response body contains the string define( 'DB_NAME'.
  2. WP-CLI Confirmation: After the exploit, run:
    wp config get DB_NAME
    
    Compare the output to the value extracted via the exploit to confirm the file read was accurate.

9. Alternative Approaches

If ?phast_get_asset is not the correct parameter:

  1. Grep for Entry Points:
    grep -rn "isset(\$_GET\['phast_" wp-content/plugins/phastpress/
    
  2. Analyze Rewrite Rules: Check if the plugin uses add_rewrite_rule. If so, the URL might look like /phast/wp-config.php%2500.js.
  3. Extension variation: If .js is blocked, try .css, .png, .txt, or .map.
  4. Path Traversal: If the plugin prepends a specific directory (like wp-content/uploads/), use:
    ../../wp-config.php%2500.js.
Research Findings
Static analysis — not yet PoC-verified

Summary

The PhastPress plugin for WordPress (<= 3.7) is vulnerable to unauthenticated arbitrary file read due to a logic flaw in how file extensions are validated versus how file paths are constructed. Attackers can use a double URL-encoded null byte to bypass extension filters and read sensitive files like wp-config.php from the server.

Exploit Outline

An unauthenticated attacker identifies the plugin's asset-serving endpoint (typically involving query parameters like 'phast_get_asset' or specific rewrite rules). The attacker constructs a payload targeting a sensitive file (e.g., 'wp-config.php') and appends a double URL-encoded null byte (%2500) followed by an allowed extension (e.g., '.js', '.css', or '.txt'). When the plugin processes the request, it validates the extension against the decoded string (which includes the allowed extension), but the filesystem operation truncates the path at the null byte, causing the server to return the contents of the target file instead of the asset.

Check if your site is affected.

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