QNAP QSA-22-14 : VideoStation - Multiple Vulnerabilities
Timeline (DD/MM/YYYY)
- 31/01/2022 : Bug sent to QNAP security team
- 16/02/2022 : QNAP confirms the report reception and assign CVE
- 23/02/2021 : Fix is published with VideoStation 5.5.9 / 5.3.13 / 5.1.8
- 06/05/2022 : Security advisory published on the QNAP website
CVE-2021-44055 - Filename and Video ID leak
Unauthenticated users can leak movies’ filenames.
The “get_imdb_info” can be used without authentication and allow an attacker to find valid “mediaId” and leak the video name.
This id is generated with this function and can be easily enumerated :
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 - Application Privileges Checking Bypass
A Simple user with no privileges can access a few applications by bypassing the privilege checking system. The behavior occurs at least on Video Station and Photo Station, but other applications may be impacted.
Preparation
We need to create a user without any privileges to exploit the vulnerability.
Exploitation
First, we can try to connect on Video Station with the “dummy user”
Here, the privilege checking denies access.
Then, we can try to get a valid “NAS_SID” for this user by connecting to the QTS dashboard.
Once connected, we got a valid “NAS_SID” and can try to access Video Station by using this token.
The application access is still denied.
During the authentication process with this kind of token. A request is done on the “/cgi-bin/authLogin.cgi” endpoint. Some parameters are added to this request :
//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);
The variable “$NAS_SID” is defined by using this code :
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 = '';
}
The function “simplexml_load_file” load an XML document at the provided URL. The “NAS_SID” is the first parameter and can be used to comment on all next parameters. The privilege checking parameter is the last.
Thus, to bypass the security, we can use the character “#” at the end of our “NAS_ID” token. The URL will be the following:
http://127.0.0.1:8080/cgi-bin/authLogin.cgi?sid=4b4eedkl#&service=103&remote_ip=&check_privilege=VIDEO_STATION
All parameters after the “#” will be ignored and the final authentication URL is :
http://127.0.0.1:8080/cgi-bin/authLogin.cgi?sid=4b4eedkl
We can now use the Video Station application as usual.
Remediation
To fix this issue, the function “rawurlencode” can be used to sanitize the input while using in concatenation with the URL. The fix should be applied to each location where “$NAS_SID” is concatenated to an URL.
//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 = "";
}