CVE-2026-1582

WP All Export <= 1.4.14 - Unauthenticated Sensitive Information Exposure via PHP Type Juggling

lowExposure of Sensitive Information to an Unauthorized Actor
3.7
CVSS Score
3.7
CVSS Score
low
Severity
1.4.15
Patched in
2d
Time to patch

Description

The WP All Export plugin for WordPress is vulnerable to Sensitive Information Exposure in all versions up to, and including, 1.4.14 via the export download endpoint. This is due to a PHP type juggling vulnerability in the security token comparison which uses loose comparison (==) instead of strict comparison (===). This makes it possible for unauthenticated attackers to bypass authentication using "magic hash" values when the expected MD5 hash prefix happens to be numeric-looking (matching pattern ^0e\d+$), allowing download of sensitive export files containing PII, business data, or database information.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=1.4.14
PublishedFebruary 17, 2026
Last updatedFebruary 18, 2026
Affected pluginwp-all-export

What Changed in the Fix

Changes introduced in v1.4.15

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan - CVE-2026-1582 ## 1. Vulnerability Summary The **WP All Export** plugin (up to version 1.4.14) is vulnerable to **Sensitive Information Exposure** due to a PHP type juggling vulnerability in its export download authentication logic. The plugin uses a loose comparison (…

Show full research plan

Exploitation Research Plan - CVE-2026-1582

1. Vulnerability Summary

The WP All Export plugin (up to version 1.4.14) is vulnerable to Sensitive Information Exposure due to a PHP type juggling vulnerability in its export download authentication logic. The plugin uses a loose comparison (==) instead of a strict comparison (===) when validating a security token provided in the URL against a generated MD5 hash.

In actions/wp_loaded.php, the plugin validates the security_token or export_hash by comparing it to the first 16 characters of md5($cron_job_key . $export_id). If the resulting MD5 hash starts with "0e" followed only by numbers (a "magic hash"), PHP interprets both the hash and an attacker-provided magic hash (e.g., 0e12345...) as the float 0.0, bypassing the authentication check. This allows unauthenticated attackers to download export files containing sensitive site data.

2. Attack Vector Analysis

  • Endpoint: The main WordPress root (/) triggers the wp_loaded hook.
  • Parameters:
    • action: Must be get_data or get_bundle.
    • export_id: The ID of the export to download (e.g., 1).
    • security_token (or export_hash): The attacker-provided payload.
  • Authentication: Unauthenticated.
  • Preconditions:
    • An export must exist.
    • The cron_job_key concatenated with the export_id must result in an MD5 hash that starts with 0e followed by digits (within the first 16 characters).

3. Code Flow

  1. Entry Point: actions/wp_loaded.php defines pmxe_wp_loaded(), which is hooked to wp_loaded.
  2. Input Capture:
    • The function checks if ( ! empty($_GET['action']) && ! empty($_GET['export_id']) && (!empty($_GET['export_hash']) || !empty($_GET['security_token']))).
    • It retrieves $securityToken from $_GET['security_token'] or $_GET['export_hash'].
  3. Vulnerable Logic (Line ~16):
    $cron_job_key = PMXE_Plugin::getInstance()->getOption('cron_job_key');
    if ( $securityToken == substr(md5($cron_job_key . $_GET['export_id']), 0, 16) )
    
  4. Sink: If the comparison returns true, the code proceeds to locate the export file path and either redirects the user to the file (wp_redirect($fileurl)) or serves it via readfile($filepath).

4. Nonce Acquisition Strategy

No nonce is required. The pmxe_wp_loaded function executes early in the WordPress lifecycle and does not perform any nonce or capability checks, relying solely on the vulnerable hash comparison for "security."

5. Exploitation Strategy

To demonstrate the vulnerability in an automated test environment, we must ensure the cron_job_key and export_id result in a magic hash.

Step-by-Step Plan:

  1. Identify/Set cron_job_key: Use WP-CLI to find a key that, when combined with export_id=1, produces an MD5 starting with 0e followed by digits.
    • Example: md5("7313010" . "1") = 0e71630....
    • We will set the plugin's cron_job_key to 7313010.
  2. Create an Export: Use WP-CLI to create at least one export record so ID 1 exists and has a file associated with it.
  3. Execute Attack: Send a GET request to the site root with action=get_data, export_id=1, and security_token=0e000000000000000000000000000000.
  4. Verify Bypass: The server should respond with a 302 Redirect to the export file or a 200 OK containing the file data, rather than a 403 "Export hash is not valid."

6. Test Data Setup

  1. Install Plugin: Ensure wp-all-export v1.4.14 is active.
  2. Configure Secret Key:
    # Set a key that produces a magic hash for ID 1
    # md5("7313010" . "1") starts with 0e71... (digits only)
    wp eval 'PMXE_Plugin::getInstance()->updateOption("cron_job_key", "7313010");'
    
  3. Create Export Data:
    • Create a dummy post or user to export.
    • Create an export record in the database.
    # Use wp eval to create an export record for ID 1
    wp eval '
    $export = new PMXE_Export_Record();
    $export->set(array(
        "id" => 1,
        "options" => array(
            "filepath" => "wp-content/uploads/wp-all-export/exports/test.csv",
            "export_to" => "csv"
        ),
        "attch_id" => 0
    ))->save();
    mkdir(ABSPATH . "wp-content/uploads/wp-all-export/exports/", 0777, true);
    file_put_contents(ABSPATH . "wp-content/uploads/wp-all-export/exports/test.csv", "id,secret_data\n1,vulnerable_info");
    '
    

7. Expected Results

  • Request: GET /?action=get_data&export_id=1&security_token=0e11111111111111
  • Response: HTTP 302 (Redirect to the CSV file) or HTTP 200 (File content).
  • Failure Case (Strict Comparison): If the comparison were ===, the response would be HTTP 403 with the message {"status":403,"message":"Export hash is not valid."}.

8. Verification Steps

  1. Check Response Code: Ensure the response is not a 403 Forbidden.
  2. Check Content: If the response is a redirect, follow it and verify the CSV content matches the dummy data created in Step 6.
  3. Confirm PHP version: Type juggling behavior is most prominent in PHP 7.x, though it persists in PHP 8.x for string-to-string comparisons like this one.

9. Alternative Approaches

If setting the cron_job_key via updateOption fails, the agent can:

  1. Manually insert the option into the wp_options table using wp db query.
  2. Brute-force the export_id (e.g., from 1 to 5000) while keeping a fixed security_token=0e123... until one hits a magic hash collision with the existing cron_job_key. This is more "realistic" but slower.
  3. If the export file is served via wp_redirect, the agent must ensure it captures the Location header to verify access to the actual file.
Research Findings
Static analysis — not yet PoC-verified

Summary

The WP All Export plugin for WordPress is vulnerable to Sensitive Information Exposure due to a PHP type juggling flaw in its export download authentication logic. By using a loose comparison (==) to validate security tokens, the plugin allows unauthenticated attackers to bypass authentication using 'magic hash' values (e.g., 0e123...) when the server-side MD5 hash prefix also matches a numeric scientific notation pattern.

Vulnerable Code

// actions/wp_loaded.php line 19
		$cron_job_key = PMXE_Plugin::getInstance()->getOption('cron_job_key');

		if ( $securityToken == substr(md5($cron_job_key . $_GET['export_id']), 0, 16) )
		{

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wp-all-export/1.4.14/actions/pmxe_after_export.php /home/deploy/wp-safety.org/data/plugin-versions/wp-all-export/1.4.15/actions/pmxe_after_export.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-all-export/1.4.14/actions/pmxe_after_export.php	2022-12-08 07:43:36.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-all-export/1.4.15/actions/pmxe_after_export.php	2026-02-07 04:59:38.000000000 +0000
@@ -71,12 +71,12 @@
             $in  = fopen($tmp_file, 'r');
             $out = fopen($filepath, 'w');
 
-            $headers = fgetcsv($in, 0, XmlExportEngine::$exportOptions['delimiter']);
+            $headers = fgetcsv($in, 0, XmlExportEngine::$exportOptions['delimiter'], '"', '\\');
 
             if (is_resource($in)) {
                 $lineNumber = 0;
                 while ( ! feof($in) ) {
-                    $data = fgetcsv($in, 0, XmlExportEngine::$exportOptions['delimiter']);
+                    $data = fgetcsv($in, 0, XmlExportEngine::$exportOptions['delimiter'], '"', '\\');
                     if ( empty($data) ) continue;
                     $data_assoc = array_combine($headers, array_values($data));
                     $line = array();
@@ -85,10 +85,10 @@
                     }
                     if ( ! $lineNumber && XmlExportEngine::$exportOptions['include_bom']){
                         fwrite($out, chr(0xEF).chr(0xBB).chr(0xBF));
-                        fputcsv($out, $line, XmlExportEngine::$exportOptions['delimiter']);
+                        fputcsv($out, $line, XmlExportEngine::$exportOptions['delimiter'], '"', '\\');
                     }
                     else{
-                        fputcsv($out, $line, XmlExportEngine::$exportOptions['delimiter']);
+                        fputcsv($out, $line, XmlExportEngine::$exportOptions['delimiter'], '"', '\\');
                     }
                     apply_filters('wp_all_export_after_csv_line', $out, XmlExportEngine::$exportID);
                     $lineNumber++;
@@ -197,9 +197,9 @@
 
 						$rowCount  = 0;
 						$fileCount = 1;
-						$headers = fgetcsv($in);
+						$headers = fgetcsv($in, 0, ',', '"', '\\');
 						while (!feof($in)) {
-						    $data = fgetcsv($in);
+						    $data = fgetcsv($in, 0, ',', '"', '\\');
 						    if (empty($data)) continue;
 						    if (($rowCount % $splitSize) == 0) {
 						        if ($rowCount > 0) {
@@ -213,9 +213,9 @@
 						    }
 						    if ($data){
 						    	if (($rowCount % $splitSize) == 0) {
-						    		fputcsv($out, $headers);
+						    		fputcsv($out, $headers, ',', '"', '\\');
 						    	}
-						        fputcsv($out, $data);
+						        fputcsv($out, $data, ',', '"', '\\');
 						    }
 						    $rowCount++;
 						}
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wp-all-export/1.4.14/actions/wp_ajax_wpae_preview.php /home/deploy/wp-safety.org/data/plugin-versions/wp-all-export/1.4.15/actions/wp_ajax_wpae_preview.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-all-export/1.4.14/actions/wp_ajax_wpae_preview.php	2024-07-24 11:50:12.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-all-export/1.4.15/actions/wp_ajax_wpae_preview.php	2026-02-07 04:59:38.000000000 +0000
@@ -416,7 +416,7 @@
 							<table class="pmxe_preview" cellpadding="0" cellspacing="0">
 							<?php
 							foreach ($csv_rows as $rkey => $row) {
-								$cells = str_getcsv($row, $exportOptions['delimiter']);
+								$cells = str_getcsv($row, $exportOptions['delimiter'], '"', '\\');
 								if ($cells){
 									?>
 									<tr>
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wp-all-export/1.4.14/actions/wp_loaded.php /home/deploy/wp-safety.org/data/plugin-versions/wp-all-export/1.4.15/actions/wp_loaded.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-all-export/1.4.14/actions/wp_loaded.php	2021-10-18 12:29:48.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-all-export/1.4.15/actions/wp_loaded.php	2026-02-07 04:59:38.000000000 +0000
@@ -16,7 +16,7 @@
 
 		$cron_job_key = PMXE_Plugin::getInstance()->getOption('cron_job_key');
 
-		if ( $securityToken == substr(md5($cron_job_key . $_GET['export_id']), 0, 16) )
+		if ( hash_equals( substr( md5( $cron_job_key . $_GET['export_id'] ), 0, 16 ), $securityToken ) )
 		{
 			$export = new PMXE_Export_Record();

Exploit Outline

To exploit this vulnerability, an unauthenticated attacker identifies a numeric export ID and targets the WordPress root endpoint using the `action=get_data` parameter. The attacker provides a 'magic hash' value (such as `0e12345678901234`) via the `security_token` or `export_hash` GET parameter. If the server-side generated token (the first 16 characters of the MD5 hash of the secret `cron_job_key` and the `export_id`) also begins with `0e` followed by digits, PHP's loose comparison evaluates both sides as `0.0`, resulting in a match. This bypasses authentication and causes the plugin to either redirect the attacker to the sensitive export file or serve its contents directly.

Check if your site is affected.

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