Vulnerabilidad grave corregida en UpdraftPlus 1.22.3

Durante una auditoría interna del plugin UpdraftPlus, descubrimos una vulnerabilidad de descarga arbitraria de copias de seguridad que podía permitir que usuarios con pocos privilegios, como los suscriptores, descargaran las copias de seguridad más recientes de un sitio.

Si se explotara, la vulnerabilidad podría permitir a los atacantes acceder a información privilegiada de la base de datos del sitio afectado (por ejemplo, nombres de usuario y contraseñas con hash).

Informamos de la vulnerabilidad a los autores del plugin, que recientemente publicaron la versión 1.22.3 para corregirla. Debido a la gravedad del problema, también se han distribuido actualizaciones automáticas forzadas. Si tu sitio aún no se ha actualizado, te recomendamos encarecidamente que lo actualices a la versión más reciente (1.22.3) y que cuentes en tu sitio con una solución de seguridad, como Jetpack Security.

Puedes consultar el aviso de seguridad de UpdraftPlus.

Detalles

Nombre del plugin: UpdraftPlus
URI del plugin: https://wordpress.org/plugins/updraftplus/
Autor: https://updraftplus.com/

La vulnerabilidad

Descargas arbitrarias de copias de seguridad

Versiones afectadas: Todas las versiones entre 1.16.7 y 1.22.3 (versión gratuita).
ID de CVE: CVE-2022-0633
ID de WPVDB: d257c28f-3c7e-422b-a5c2-e618ed3c0bf3
CVSSv3.1: 8.5
CWSS: 87.6

El plugin utiliza «nonces» personalizados y marcas de tiempo para identificar las copias de seguridad de forma segura. Como conocer ese nonce y esa marca de tiempo puede dar acceso a bastantes funciones del plugin, es fundamental asegurarse de que esta información solo esté disponible para quienes la necesitan legítimamente.

Por desgracia, como veremos, no era así.

Filtración del nonce

El primer problema se encontraba en el método UpdraftPlus_Admin::process_status_in_heartbeat en cuestión.

	/**
	 * 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;
	}

No comprobaba correctamente que el usuario que enviaba esta solicitud de Heartbeat fuera administrador (por ejemplo, mediante funciones como current_user_can), lo que suponía un problema porque lo primero que intenta hacer esta función es obtener la lista de tareas activas de copia de seguridad mediante el método get_activejobs_list del plugin.

Por tanto, un atacante podía crear una solicitud maliciosa dirigida a este callback de Heartbeat para acceder a información sobre la copia de seguridad más reciente del sitio que, entre otros datos, contendría el nonce de una copia de seguridad.

Descarga de copias de seguridad

Hay varias formas de descargar copias de seguridad en UpdraftPlus, y la mayoría estaban protegidas correctamente.

	/**
	 * 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
		}
	}
}

Por desgracia, el método UpdraftPlus_Admin::maybe_download_backup_from_email, registrado en admin_init, tampoco validaba directamente los perfiles de usuario. 

Aunque aplicaba algunas comprobaciones indirectas, como revisar $pagenow, una variable global, investigaciones anteriores han demostrado que esta variable puede contener entradas arbitrarias del usuario. Los atacantes podían utilizar este endpoint para descargar copias de seguridad de archivos y de la base de datos a partir de la información filtrada mediante el fallo de Heartbeat mencionado anteriormente.

Cronología

2022-02-14 – Contacto inicial con UpdraftPlus
2022-02-15 – Les enviamos información sobre esta vulnerabilidad
2022-02-16 – Se publica UpdraftPlus 1.22.3 y se lanzan actualizaciones automáticas forzadas

Conclusión

¡Te recomendamos que compruebes qué versión del plugin UpdraftPlus utiliza tu sitio y, si está dentro del intervalo afectado, que lo actualices lo antes posible! 

En Jetpack, nos esforzamos para garantizar que tus sitios web estén protegidos frente a este tipo de vulnerabilidades. Te recomendamos que tengas un plan de seguridad para tu sitio que incluya análisis de archivos maliciosos y copias de seguridad. Jetpack Security es una excelente opción de seguridad para WordPress que garantiza la protección de tu sitio y sus visitantes.

Créditos

Investigador original: Marc Montpas

Gracias al resto del equipo de Jetpack Scan por sus comentarios, ayuda y correcciones.

Explora las ventajas de Jetpack

Descubre cómo Jetpack puede ayudarte a proteger, acelerar y hacer crecer tu sitio de WordPress.

Explorar planes

¿Tienes alguna pregunta?

La sección de comentarios está cerrada para este artículo, pero seguimos aquí para ayudarte. ¡Visita el foro de soporte y estaremos encantados de responder a tus preguntas!

Ver foro de soporte