Severe Vulnerability Fixed In UpdraftPlus 1.22.3

During an internal audit of the UpdraftPlus plugin, we uncovered an arbitrary backup download vulnerability that could allow low-privileged users like subscribers to download a site’s latest backups.

If exploited, the vulnerability could grant attackers access to privileged information from the affected site’s database (e.g., usernames and hashed passwords).

We reported the vulnerability to the plugin’s authors, and they recently released version 1.22.3 to address it. Forced auto-updates have also been pushed due to the severity of this issue. If your site hasn’t already, we strongly recommend that you update to the latest version (1.22.3) and have an established security solution on your site, such as Jetpack Security.

You can find UpdraftPlus’ own advisory here.

Details

Plugin Name: UpdraftPlus
Plugin URI: https://wordpress.org/plugins/updraftplus/
Author: https://updraftplus.com/

The Vulnerability

Arbitrary Backup Downloads

Affected versions: Every version between 1.16.7 and 1.22.3 (Free version), and
CVE-ID: CVE-2022-0633
WPVDB ID: d257c28f-3c7e-422b-a5c2-e618ed3c0bf3
CVSSv3.1: 8.5
CWSS: 87.6

The plugin uses custom “nonces” and timestamps to securely identify backups. Given the knowledge of said nonce and timestamp can give someone access to quite a few of the plugin’s features, making sure this info is only accessible to those who legitimately need it is crucial.

Unfortunately, as we’ll demonstrate, it wasn’t the case.

Nonce Leak

The first culprit was located on the UpdraftPlus_Admin::process_status_in_heartbeat method.

	/**
	 * Receive Heartbeat data and respond.
	 *
	 * Processes data received via a Heartbeat request, and returns additional data to pass back to the front end.
	 *
	 * @param array $response - Heartbeat response data to pass back to front end.
	 * @param array $data     - Data received from the front end (unslashed).
	 */
	public function process_status_in_heartbeat($response, $data) {
		if (!is_array($response) || empty($data['updraftplus'])) return $response;
		try {
			$response['updraftplus'] = $this->get_activejobs_list(UpdraftPlus_Manipulation_Functions::wp_unslash($data['updraftplus']));
		} catch (Exception $e) {
			$log_message = 'PHP Fatal Exception error ('.get_class($e).') has occurred during get active job list. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
			error_log($log_message);
			$response['updraftplus'] = array(
				'fatal_error' => true,
				'fatal_error_message' => $log_message
			);
		// @codingStandardsIgnoreLine
		} catch (Error $e) {
			$log_message = 'PHP Fatal error ('.get_class($e).') has occurred during get active job list. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
			error_log($log_message);
			$response['updraftplus'] = array(
				'fatal_error' => true,
				'fatal_error_message' => $log_message
			);
		}

		if (UpdraftPlus_Options::user_can_manage() && isset($data['updraftplus']['updraft_credentialtest_nonce'])) {
			if (!wp_verify_nonce($data['updraftplus']['updraft_credentialtest_nonce'], 'updraftplus-credentialtest-nonce')) {
				$response['updraftplus']['updraft_credentialtest_nonce'] = wp_create_nonce('updraftplus-credentialtest-nonce');
			}
		}

		$response['updraftplus']['time_now'] = get_date_from_gmt(gmdate('Y-m-d H:i:s'), 'D, F j, Y H:i');

		return $response;
	}

It did not properly ensure that the user sending this heartbeat request was an administrator (e.g. via functions like current_user_can), which was a problem since the first thing this function tries to do is grab the list of active backup jobs via the get_activejobs_list method.

An attacker could thus craft a malicious request targeting this heartbeat callback to get access to information about the site’s latest backup to date, which will among other things contain a backup’s nonce.

Backup Download

There are a few ways to download backups on UpdraftPlus, most of which were properly secured.

	/**
	 * Find out if the current request is a backup download request, and proceed with the download if it is
	 */
	public function maybe_download_backup_from_email() {
		global $pagenow;
		if ((!defined('DOING_AJAX') || !DOING_AJAX) && UpdraftPlus_Options::admin_page() === $pagenow && isset($_REQUEST['page']) && 'updraftplus' === $_REQUEST['page'] && isset($_REQUEST['action']) && 'updraft_download_backup' === $_REQUEST['action']) {
			$findexes = empty($_REQUEST['findex']) ? array(0) : $_REQUEST['findex'];
			$timestamp = empty($_REQUEST['timestamp']) ? '' : $_REQUEST['timestamp'];
			$nonce = empty($_REQUEST['nonce']) ? '' : $_REQUEST['nonce'];
			$type = empty($_REQUEST['type']) ? '' : $_REQUEST['type'];
			if (empty($timestamp) || empty($nonce) || empty($type)) wp_die(__('The download link is broken, you may have clicked the link from untrusted source', 'updraftplus'), '', array('back_link' => true));
			$backup_history = UpdraftPlus_Backup_History::get_history();
			if (!isset($backup_history[$timestamp]['nonce']) || $backup_history[$timestamp]['nonce'] !== $nonce) wp_die(__("The download link is broken or the backup file is no longer available", 'updraftplus'), '', array('back_link' => true));
			$this->do_updraft_download_backup($findexes, $type, $timestamp, 2, false, '');
			exit; // we don't need anything else but an exit
		}
	}
}

Unfortunately, the UpdraftPlus_Admin::maybe_download_backup_from_email method, which is hooked to admin_init didn’t directly validate users’ roles either. 

While it did apply some checks indirectly, such as checking the $pagenow global variable, past research has shown that this variable can contain arbitrary user input. Bad actors could use this endpoint to download file & database backups based on the information they leaked from the aforementioned heartbeat bug.

Timeline

2022-02-14 – Initial contact with UpdraftPlus
2022-02-15 – We send them details about this vulnerability
2022-02-16 – UpdraftPlus 1.22.3 is released, forced auto-updates launched

Conclusion

We recommend that you check which version of the UpdraftPlus plugin your site is using, and if it is within the affected range, update it as soon as possible! 

At Jetpack, we work hard to make sure your websites are protected from these types of vulnerabilities. We recommend that you have a security plan for your site that includes malicious file scanning and backups. Jetpack Security is one great WordPress security option to ensure your site and visitors are safe.

Credits

Original researcher: Marc Montpas

Thanks to the rest of the Jetpack Scan team for feedback, help, and corrections.

This entry was posted in Vulnerabilities and tagged , . Bookmark the permalink.

Marc Montpas profile
Marc Montpas

Marc’s interests led him to work in the trenches of cybersecurity for the better part of the last decade, notably at companies like Sucuri and GoDaddy. His journey led him to uncover several high-impact security issues while auditing open-source platforms, like WordPress. He’s an avid Hacker Capture The Flag player and loves to hypothesize new attack vectors.

Explore the benefits of Jetpack

Learn how Jetpack can help you protect, speed up, and grow your WordPress site.

Get up to 60% off your first year.

Compare plans

Have a question?

Comments are closed for this article, but we're still here to help! Visit the support forum and we'll be happy to answer any questions.

View support forum
  • Enter your email address to follow this blog and receive news and updates from Jetpack!

    Join 111,098 other followers
  • Browse by Topic

  • %d bloggers like this: