WP All Export <= 1.4.14 - Unauthenticated Sensitive Information Exposure via PHP Type Juggling
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:NTechnical Details
<=1.4.14What Changed in the Fix
Changes introduced in v1.4.15
Source Code
WordPress.org SVN# 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 thewp_loadedhook. - Parameters:
action: Must beget_dataorget_bundle.export_id: The ID of the export to download (e.g.,1).security_token(orexport_hash): The attacker-provided payload.
- Authentication: Unauthenticated.
- Preconditions:
- An export must exist.
- The
cron_job_keyconcatenated with theexport_idmust result in an MD5 hash that starts with0efollowed by digits (within the first 16 characters).
3. Code Flow
- Entry Point:
actions/wp_loaded.phpdefinespmxe_wp_loaded(), which is hooked towp_loaded. - Input Capture:
- The function checks
if ( ! empty($_GET['action']) && ! empty($_GET['export_id']) && (!empty($_GET['export_hash']) || !empty($_GET['security_token']))). - It retrieves
$securityTokenfrom$_GET['security_token']or$_GET['export_hash'].
- The function checks
- 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) ) - 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 viareadfile($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:
- Identify/Set
cron_job_key: Use WP-CLI to find a key that, when combined withexport_id=1, produces an MD5 starting with0efollowed by digits.- Example:
md5("7313010" . "1")=0e71630.... - We will set the plugin's
cron_job_keyto7313010.
- Example:
- Create an Export: Use WP-CLI to create at least one export record so ID 1 exists and has a file associated with it.
- Execute Attack: Send a GET request to the site root with
action=get_data,export_id=1, andsecurity_token=0e000000000000000000000000000000. - Verify Bypass: The server should respond with a
302 Redirectto the export file or a200 OKcontaining the file data, rather than a403"Export hash is not valid."
6. Test Data Setup
- Install Plugin: Ensure
wp-all-exportv1.4.14 is active. - 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");' - 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) orHTTP 200(File content). - Failure Case (Strict Comparison): If the comparison were
===, the response would beHTTP 403with the message{"status":403,"message":"Export hash is not valid."}.
8. Verification Steps
- Check Response Code: Ensure the response is not a
403 Forbidden. - Check Content: If the response is a redirect, follow it and verify the CSV content matches the dummy data created in Step 6.
- 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:
- Manually insert the option into the
wp_optionstable usingwp db query. - Brute-force the
export_id(e.g., from 1 to 5000) while keeping a fixedsecurity_token=0e123...until one hits a magic hash collision with the existingcron_job_key. This is more "realistic" but slower. - If the export file is served via
wp_redirect, the agent must ensure it captures theLocationheader to verify access to the actual file.
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
@@ -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++; } @@ -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> @@ -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.