QNAP QSA-22-14 : VideoStation - Multiple Vulnerabilities

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

QTS vQTS 4.4.3.1444 VideoStation 5.5.8

Timeline (DD/MM/YYYY)

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.

image-20211102174830134

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.

image-20220130181828547

Exploitation

First, we can try to connect on Video Station with the “dummy user

image-20220130182126516

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.

image-20220130182428605

Once connected, we got a valid “NAS_SID” and can try to access Video Station by using this token.

image-20220130182543791

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

image-20220130183541479

We can now use the Video Station application as usual.

image-20220130183634840

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 = "";
				}