QNAP QSA-22-14 : VideoStation - Plusieurs vulnérabilités

CVE CVE-2021-44055 CVE CVE-2021-44056 QNAP QSA-22-14

QTS vQTS 4.4.3.1444 VideoStation 5.5.8

Timeline (DD/MM/YYYY)

CVE-2021-44055 - Fuite du nom de fichier et de l’identifiant des vidéos.

Les utilisateurs non authentifiés peuvent récupérer les noms de fichiers des films.

La fonction “get_imdb_info” peut être utilisée sans authentification et permettre à un attaquant de trouver un “mediaId” valide et de récupérer le nom de la vidéo.

image-20211102174830134

Cet id est généré avec cette fonction et peut être facilement énuméré :

public static function hash($num, $len = 6) {
    $ceil = bcpow(62, $len);
    $primes = array_keys(self::$golden_primes);
    $prime = $primes[$len];
    $dec = bcmod(bcmul($num, $prime), $ceil);
    $hash = self::base62($dec);
    return str_pad($hash, $len, "0", STR_PAD_LEFT);
}

CVE-2021-44056 - Contournement du contrôle des privilèges de l’application

Un utilisateur sans privilèges d’accès peut accéder à plusieurs applications en contournant le système de vérification d’accès. Ce contournement est présent au moins sur les application VideoStation et PhotoStation

Préparation

Nous avons besoin d’un utilisateur sans aucun privilège pour exploiter cette vulnérabilité.

image-20220130181828547

Exploitation

Tout d’abord, nous vérifions que notre utilisateur n’a pas les droits d’accéder à l’application

image-20220130182126516

Ici, nous constatons le refus d’accès par l’application

Ensuite, nous avons besoin d’un cookie “NAS_SID” valide. Pour cela, il faut se connecter sur QTS.

image-20220130182428605

Une fois connecté, nous avons un cookie “NAS_SID” valide et nous pouvons essayer d’accéder à l’application.

image-20220130182543791

L’application nous refuse toujours l’accès

Pendant le processus d’authentification avec ce cookie “NAS_SID”, une requête est effectuée sur l’URL interne “/cgi-bin/authLogin.cgi”. Certains paramètres sont ajouté dont un permettant d’activer la vérification des privilèges d’accès.

//File: api/libs/inc_common.php
if(!$IS_LOGIN && empty($SID) && !empty($NAS_SID)){
            //try to validate with NAS SID
            $port = exec('/bin/cat /var/lock/._thttpd_.port');
            $baseURL = "http://127.0.0.1:".$port."/cgi-bin/authLogin.cgi?sid=".$NAS_SID."&service=103&remote_ip=".getClientIP();
            if($_SESSION['NASVARS']['application_privilege'] == 'TRUE')
                $loginURL = $baseURL."&check_privilege=VIDEO_STATION";

            $xml = simplexml_load_file($loginURL);

La variable “$NAS_SID” est définie ici :

if (isset($_COOKIE['NAS_SID']) || isset($_COOKIE['QTS_SSID']) || isset($_COOKIE['QTS_SSL_SSID'])) {
    if (empty($_SERVER['HTTPS'])) {
        $NAS_SID = $_COOKIE['QTS_SSID'] ?  $_COOKIE['QTS_SSID'] : $_COOKIE['NAS_SID'];
    }
    //https connection
    else {
        $NAS_SID = $_COOKIE['QTS_SSL_SSID'] ?  $_COOKIE['QTS_SSL_SSID'] : $_COOKIE['NAS_SID'];
    }
}
else {
    $NAS_SID = '';
}

La fonction “simplexml_load_file” charge la réponse en XML de la requête. Le “NAS_SID” est le premier paramètre et peut être modifié pour commenter tous les autres paramètres. La vérification des privilèges étant le dernier, celui-ci sera ignoré.

Ainsi, pour contourner cette sécurité, il est possible d’utiliser le caractère “#” à la fin de notre cookie “NAS_ID”.

La requête interne sera la suivante :

http://127.0.0.1:8080/cgi-bin/authLogin.cgi?sid=4b4eedkl#&service=103&remote_ip=&check_privilege=VIDEO_STATION

Tous les paramètres après le caractère “#” seront ignorés et la requête d’authentification finale sera :

http://127.0.0.1:8080/cgi-bin/authLogin.cgi?sid=4b4eedkl

image-20220130183541479

Nous pouvons alors utiliser l’application.

image-20220130183634840

Correction

Pour corriger cette vulnérabilité, la fonction “rawurlencode” peut être utilisées pour filtrer l’entrée utilisateur concaténée à l’URL.

Cette correction doit être appliquée à chaque endroit ou cette valeur “$NAS_SID” est ajoutée à l’URL de connexion.

//File: api/libs/inc_common.php
if(!$IS_LOGIN && empty($SID) && !empty($NAS_SID)){
            //try to validate with NAS SID
            $port = exec('/bin/cat /var/lock/._thttpd_.port');
            $baseURL = "http://127.0.0.1:".$port."/cgi-bin/authLogin.cgi?sid=".rawurlencode($NAS_SID)."&service=103&remote_ip=".getClientIP();
            if($_SESSION['NASVARS']['application_privilege'] == 'TRUE')
                $loginURL = $baseURL."&check_privilege=VIDEO_STATION";

            $xml = simplexml_load_file($loginURL);
// File: api/libs/user.php
}else if(!empty($NAS_SID)) {

                    CHECK_CSRF();

                    $auth_by = 'qts';
                    $baseURL = "$protocol://127.0.0.1:".$port."/cgi-bin/authLogin.cgi?sid=".rawurlencode($NAS_SID);
                    $loginURL = $baseURL."&service=103&remote_ip=".getClientIP();
                }else{
                    $auth_by = 'nobody';
                    $loginURL = "";
                }