Vulnérabilités graves corrigées dans All In One SEO Plugin Version 4.1.5.3

Publié: 2021-12-15

Lors d'un audit interne du plugin All In One SEO, nous avons découvert une vulnérabilité d'injection SQL et un bug d'escalade de privilèges.

Si elle est exploitée, la vulnérabilité SQL Injection pourrait permettre aux attaquants d'accéder à des informations privilégiées de la base de données du site concerné (par exemple, les noms d'utilisateur et les mots de passe hachés).

Le bogue d'escalade de privilèges que nous avons découvert peut accorder aux acteurs malveillants l'accès aux points de terminaison protégés de l'API REST auxquels ils ne devraient pas avoir accès. Cela pourrait finalement permettre aux utilisateurs disposant de comptes à faibles privilèges, comme les abonnés, d'exécuter du code à distance sur les sites concernés.

Nous avons signalé les vulnérabilités à l'auteur du plugin par e-mail, et ils ont récemment publié la version 4.1.5.3 pour y remédier. Nous vous recommandons fortement de mettre à jour vers la dernière version du plug-in et d'avoir une solution de sécurité établie sur votre site, telle que Jetpack Security.

Des détails

Nom du plugin : SEO tout-en-un
URI du plugin : https://wordpress.org/plugins/all-in-one-seo-pack/
Auteur : https://aioseo.com/

Les vulnérabilités

Escalade de privilège authentifiée

Versions concernées : Toutes les versions entre 4.0.0 et 4.1.5.2 inclus.
ID CVE : CVE-2021-25036
CVSSv3.1 : 9.9
CWSS : 92.1

	/**
	 * Validates access from the routes array.
	 *
	 * @since 4.0.0
	 *
	 * @param  \WP_REST_Request $request The REST Request.
	 * @return bool                      True if validated, false if not.
	 */
	public function validateAccess( $request ) {
		$route     = str_replace( '/' . $this->namespace . '/', '', $request->get_route() );
		$routeData = isset( $this->getRoutes()[ $request->get_method() ][ $route ] ) ? $this->getRoutes()[ $request->get_method() ][ $route ] : [];

		// No direct route name, let's try the regexes.
		if ( empty( $routeData ) ) {
			foreach ( $this->getRoutes()[ $request->get_method() ] as $routeRegex => $routeInfo ) {
				$routeRegex = str_replace( '@', '\@', $routeRegex );
				if ( preg_match( "@{$routeRegex}@", $route ) ) {
					$routeData = $routeInfo;
					break;
				}
			}
		}

		if ( empty( $routeData['access'] ) ) {
			return true;
		}

		// We validate with any of the access options.
		if ( ! is_array( $routeData['access'] ) ) {
			$routeData['access'] = [ $routeData['access'] ];
		}
		foreach ( $routeData['access'] as $access ) {
			if ( current_user_can( $access ) ) {
				return true;
			}
		}

		if ( current_user_can( apply_filters( 'aioseo_manage_seo', 'aioseo_manage_seo' ) ) ) {
			return true;
		}

		return false;
	}

Les vérifications de privilèges appliquées par All In One SEO pour sécuriser les points de terminaison de l'API REST contenaient un bogue très subtil qui aurait pu accorder aux utilisateurs disposant de comptes à faibles privilèges (comme les abonnés) un accès à chaque point de terminaison enregistré par le plug-in.

La méthode Api::validateAccess() s'appuie sur la route de l'API REST demandée pour savoir quelles vérifications de privilège appliquer à une requête donnée. Comme il ne tenait pas compte du fait que WordPress traite les routes de l'API REST comme des chaînes insensibles à la casse, changer un seul caractère en majuscule contournerait complètement la routine de vérification des privilèges.

Ceci est particulièrement inquiétant car certains points de terminaison du plugin sont assez sensibles. Par exemple, le point de aioseo/v1/htaccess peut réécrire le .htaccess d'un site avec un contenu arbitraire. Un attaquant pourrait abuser de cette fonctionnalité pour masquer les portes dérobées .htaccess et exécuter un code malveillant sur le serveur.

Injection SQL authentifiée

Versions concernées : Toutes les versions entre 4.1.3.1 et 4.1.5.2 inclus.
ID CVE : CVE-2021-25037
CVSSv3.1 : 7.7
CWSS : 80,4

/**
 * Searches for posts or terms by ID/name.
 *
 * @since 4.0.0
 *
 * @param  \WP_REST_Request  $request The REST Request
 * @return \WP_REST_Response          The response.
 */
public static function searchForObjects( $request ) {
    $body = $request->get_json_params();
 
    if ( empty( $body['query'] ) ) {
        return new \WP_REST_Response( [
            'success' => false,
            'message' => 'No search term was provided.'
        ], 400 );
    }
    if ( empty( $body['type'] ) ) {
        return new \WP_REST_Response( [
            'success' => false,
            'message' => 'No type was provided.'
        ], 400 );
    }
 
    $searchQuery = aioseo()->db->db->esc_like( $body['query'] );
 
    $objects        = [];
    $dynamicOptions = aioseo()->dynamicOptions->noConflict();
    if ( 'posts' === $body['type'] ) {
 
        $postTypes = aioseo()->helpers->getPublicPostTypes( true );
        foreach ( $postTypes as $postType ) {
            // Check if post type isn't noindexed.
            if ( $dynamicOptions->searchAppearance->postTypes->has( $postType ) && ! $dynamicOptions->searchAppearance->postTypes->$postType->show ) {
                $postTypes = aioseo()->helpers->unsetValue( $postTypes, $postType );
            }
        }
 
        $objects = aioseo()->db
            ->start( 'posts' )
            ->select( 'ID, post_type, post_title, post_name' )
            ->whereRaw( "( post_title LIKE '%{$searchQuery}%' OR post_name LIKE '%{$searchQuery}%' OR )" )
            ->whereIn( 'post_type', $postTypes )
            ->whereIn( 'post_status', [ 'publish', 'draft', 'future', 'pending' ] )
            ->orderBy( 'post_title' )
            ->limit( 10 )
            ->run()
            ->result();

La méthode PostsTerms::searchForObjects(), accessible via la route de l'API REST /wp-json/aioseo/v1/objects , n'a échappé que l'entrée utilisateur à l'aide de wpdb::esc_like() avant d'ajouter ladite entrée à une requête SQL. Étant donné que cette méthode n'est pas conçue pour échapper aux guillemets, un attaquant pourrait toujours les injecter et forcer la requête à divulguer des informations sensibles de la base de données, comme les informations d'identification de l'utilisateur.

Bien que ce point de terminaison n'ait pas été conçu pour être accessible aux utilisateurs disposant de comptes à faibles privilèges, le vecteur d'attaque d'escalade de privilèges susmentionné leur a permis d'abuser de cette vulnérabilité.

Chronologie

2021-12-01 – Premier contact avec All In One SEO
2021-12-02 – Nous leur envoyons des détails sur ces vulnérabilités
2021-12-08 - All In One SEO 4.1.5.3 est publié

Conclusion

Nous vous recommandons de vérifier quelle version du plugin All In One SEO votre site utilise, et si elle se trouve dans la plage concernée, mettez-la à jour dès que possible !

Chez Jetpack, nous travaillons dur pour nous assurer que vos sites Web sont protégés contre ces types de vulnérabilités. Nous vous recommandons d'avoir un plan de sécurité pour votre site qui inclut l'analyse et la sauvegarde des fichiers malveillants. Jetpack Security est une excellente option de sécurité WordPress pour garantir la sécurité de votre site et de vos visiteurs.

Crédits

Chercheur original : Marc Montpas

Merci au reste de l'équipe Jetpack Scan pour les commentaires, l'aide et les corrections.