QNAP QSA-22-15 : PhotoStation - Contournement du contrôle des privilèges de l'application
Timeline (DD/MM/YYYY)
- 31/01/2022 : Vulnérabilité envoyée à l’équipe de sécurité de QNAP
- 16/02/2022 : QNAP confirme la réception du rapport et assigne les CVE
- 15/02/2021 : Correction disponible avec PhotoStation 6.0.20
- 16/02/2021 : Correction disponible avec PhotoStation 5.7.16 / 5.4.13
- 06/05/2022 : Bulletin de sécurité disponible sur le site de QNAP
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
Cette exploitation est pour l’application VideoStation mais le principe est identique pour PhotoStation
Préparation
Nous avons besoin d’un utilisateur sans aucun privilège pour exploiter cette vulnérabilité.
Exploitation
Tout d’abord, nous vérifions que notre utilisateur n’a pas les droits d’accéder à l’application
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.
Une fois connecté, nous avons un cookie “NAS_SID” valide et nous pouvons essayer d’accéder à l’application.
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
Nous pouvons alors utiliser l’application.
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 = "";
}