- The DLNA server is running as root and follows symlinks.
- Even though they’re hidden in the web UI, SSH and other services can be enabled by setting a few fields in the configuration backup file.
- A local subnet can be set as the remote management IP whitelist through the configuration backup file, enabling (local) SSH access.
- An innocent DDNS configuration setting can be used as a decryption oracle.
Initial investigation
By default, the device does not expose any interesting services besides the web interface.After poking around in its modern Vue-based interface for a bit, I made the following observations:
- The default
admin
user is actually the lowest privilege user. The other users aresupervisor
androot
. - It is needlessly complex with a large client-side blob of Javascript
performing all kinds of processing, including storing the privilege
level (
medium
) in a localStorage variable (yes, you can set it tohigh
to expose more settings), and using some form of homebrew application-layer cryptography in all its asynchronous requests (with the key in localStorage!). - There is no firmware upgrade mechanism present. After digging through the Javascript, it appears that this feature was hidden and/or disabled.
- There is a “Remote Management” section, but it basically only allows toggling HTTP(S) access.
- No firmware image is available on the manufacturer website, in contrast to some of their other models.
- No source code was published.
The mighty “Backup/Restore” feature
The most interesting page is the “Backup / Restore” page. This feature will turn out to be essential in getting a foothold in this device.Clicking “Backup” results in a JSON file called
Backup_Restore
containing the current router configuration. This large file contains everything2 that’s configurable through the web-interface, and some more:- Properties of our
admin
user and its group, including privilege-related informaton, which is encrypted. - A list of enabled and disabled services, containing not just the “HTTP(s)” and “PING” services visible in the web interface, but also “SSH”, “FTP”, “TELNET”, and “SNMP”.
USB filesharing over SMB
The device has a built-in Samba server which can serve files from attached USB drives. From a quick test, at leastFAT32
, NTFS
and ext2
are supported.The folders visible through SMB are either
/home/admin
or /home/admin/usbX_sdaX
(depending on the USB port and partition). Sadly, symlinks to folders and files outside of this /home/admin
are ignored.Besides the USB mount points,
/home/admin
contains two folders: data/
and fw/
.
The former is always empty. The latter appears to be a place to put
firmware upgrade files. As it turns out, there is a process called fwwatcher
that looks for any file placed in this folder and tries to apply it as a configuration file or firmware upgrade.At least now we have a way to perform a firmware upgrade!
DLNA / UPnP
Luckily the device supports another way of sharing files, albeit read-only: UPnP/DLNA. The web interface even makes it easy to change the base path outside of the/mnt
folder (where the USB drive
is also mounted). Using a DLNA client, we can indeed browse the entire
filesystem folder structure if the base path is set to /
. However, there is one catch: only files ending with common media extensions (e.g. .wav
, .mp4
, etc) are shown…Sidenote: in the end, I used
BubbleUPnP
on my phone to download files from the router. For Linux, djmount
appears to be a nice tool, but any file I download ends up being empty.~$ djmount upnp
~$ cd 'upnp/ZyXEL Digital Media Server/Browse Folders'
~$ ls -lh
total 6,5K
dr-xr-xr-x 2 root root 512 1 jan 2000 bin
dr-xr-xr-x 6 root root 512 1 jan 2000 data
dr-xr-xr-x 8 root root 512 1 jan 2000 dev
dr-xr-xr-x 21 root root 512 1 jan 2000 etc
dr-xr-xr-x 8 root root 512 1 jan 2000 home
dr-xr-xr-x 9 root root 512 1 jan 2000 lib
dr-xr-xr-x 4 root root 512 1 jan 2000 misc
dr-xr-xr-x 4 root root 512 1 jan 2000 mnt
dr-xr-xr-x 2 root root 512 1 jan 2000 overlay
dr-xr-xr-x 136 root root 512 1 jan 2000 proc
dr-xr-xr-x 2 root root 512 1 jan 2000 root
dr-xr-xr-x 2 root root 512 1 jan 2000 sbin
dr-xr-xr-x 4 root root 512 1 jan 2000 sys
On the USB drive, make something that looks like a media file:
~$ ln -s /etc/passwd passwd.wav
/home/admin/usbX_sdaX/
using a DLNA client and download passwd.wav
:)The best part is that we can even download
/etc/shadow
using this method, indicating that the DLNA server is running as root! Sadly, only regular files work via this method, so /dev/mem
and /dev/mtd0
won’t work.The
/etc/shadow
file contains our username and hashed password as set through the web interface. Additionally, there are hashes for the root
and supervisor
accounts. I ran john
on them using some common wordlists and rulesets, but no luck. These
passwords are likely device-specific and randomly or procedurally
generated.So now we have arbitrary filesystem read capability with root permissions, but no way to list files, no way to read non-regular files and no way to write. Plus the shadow file is currently uncrackable..
Enabling other services
As mentioned before, theBackup_Restore
file obtained
from the web interface contains some settings for services not mentioned
in the web interface. Most interestingly: SSH, telnet and FTP.All of these services can be enabled by setting the
BoundInterfaceList
and Mode
fields to the values that are set for the HTTP service (LAN_ONLY
and IP.Interface.4,IP.Interface.7,IP.Interface.10,
respectively). For example, for SSH we replace this section: {
"Name":"SSH",
"Enable":true,
"Protocol":6,
"Port":22,
"Mode":"",
"TrustAll":true
}
{
"Name":"SSH",
"Enable":true,
"Protocol":6,
"Port":22,
"Mode":"LAN_ONLY",
"TrustAll":true,
"BoundInterfaceList":"IP.Interface.4,IP.Interface.7,IP.Interface.10,",
}
Enable
field is a lie)Making this change for all services enabled them, but logging in as
admin
is still not allowed. SSH and FTP don’t give any special error messages
(just generic failure or timeout), but telnet is a bit more verbose:Trying 192.168.0.1...
Connected to 192.168.0.1.
Escape character is '^]'.
VMG8825-T50 login: admin
Password:
Account: 'admin' TELNET permission denied.
Connection closed by foreign host.
At this point I wondered if the string
TELNET permission denied.
is part of a standard telnet server. Apparently its a Zyxel-made addition to busybox telnet
, as seen here in a set of patches provided by Zyxel as part of a source code release for a different device:if(zcfgFeObjStructGet(RDM_OID_ZY_LOG_CFG_GP, &logGpObjIid, (void **) &logGpObj) == ZCFG_SUCCESS) {
if (strstr(logGpObj->GP_Privilege, "telnet") == NULL){
snprintf(logStr, sizeof(logStr), "Account: '%s' TELNET permission denied.", username);
puts(logStr);
syslog(LOG_INFO, "Account:'%s' TELNET permission denied.", username);
free(logGpObj);
return EXIT_FAILURE;
}
free(logGpObj);
}
ln -s /bin/busybox busybox.wav
Upon throwing it in Ghidra, it looks like very similar patches were applied to this device:By looking at the Zyxel patches for the other device linked above, we can figure out what this code does: it checks if the string
telnet
is contained in the GP_Privilege
setting of the user who’s trying to log in. Presumably, SSH and FTP perform a similar check.We can find this
GP_Privilege
(group privilege?) setting in our config file (abbreviated):"X_ZYXEL_LoginCfg":{
"LoginGroupConfigurable":true,
"LogGp":[
{
"emptyIns":true
},
{
"GP_Privilege":"_encrypt_IjjVfowKNKExRGaE8kN9oA==",
"Account":[
{
"AutoShowQuickStart":true,
"Enabled":true,
"EnableQuickStart":false,
"Username":"admin",
...
_encrypt_
prefix.
The _encrypt_
oracle
After reverse-engineering the mechanism behind these _encrypt_
values (tangent 1)
and realizing that it is based on the supervisor password, I realized
that we can let the device do the decryption for us. It turns out that exactly one of the _encrypt_
fields in the config file is readable and editable from the web interface!Specifically, it is the
Password
field used for the Dynamic DNS settings:Which is represented like this in the
Backup_Restore
file:"DynamicDNS":{
"Enable":true,
"ServiceProvider":"userdefined",
"DDNSType":"",
"HostName":"foobar",
"UserName":"foobar",
"Password":"_encrypt_EilEEm+PPn+b1XhqTC7W3A==",
"IPAddressPolicy":0,
"UserIPAddress":"0.0.0.0",
"Wildcard":false,
"Offline":false,
"Interface":"",
"UpdateURL":"foobar",
"ConnectionType":"HTTP"
},
_encrypt_
mechanism is not seeded or context-dependent, this means that we have an encryption oracle and a decryption oracle!- encryption: change the plaintext in the web interface, then download the configuration backup containing the ciphertext.
- decryption: set the ciphertext in the configuration backup, restore the backup and then view the plaintext in the web interface.
Changing GP_Privilege
Using the decryption oracle, it turns out that the GP_Privilege
setting contains the string http
. Using the encryption oracle we can set it to http,telnet,ftp,ssh
. However, this didn’t work. For some reason, the GP_Privilege
setting is being reset to the (encrypted) http
value upon uploading the configuration file.Looking again at Zyxel’s patches for busybox on the other device, this comment jumped out:
if (strcmp(addr,"--")){
/*If IP address match SP trust domain, do not Auth GP_Privilege */
while(zcfgFeObjStructGetNext(RDM_OID_SP_TRUST_DOMAIN, &spTrustDomainObjIid, (void **) &spTrustDomainObj) == ZCFG_SUCCESS) {
if (checkCidrBlock(spTrustDomainObj->IPAddress, spTrustDomainObj->SubnetMask, addr)){
authGpPrivilege = false ;
free(spTrustDomainObj);
break ;
}
free(spTrustDomainObj);
}
So what is this “SP trust domain”? Well, a “trust domain” is apparently an optional IP mask whitelist for the “Remote Management” services, being all of the services mentioned above (telnet, ssh, ftp, http). There is a second set of remote management settings prefixed by
SP
. My guess is that it stands for Service Provider. These are the default “trust domain” settings in my Backup_Restore
file: "SPTrustDomain":[
{
"Enable":true,
"IPAddress":"10.1.3.0",
"SubnetMask":"24",
"WebDomainName":""
}
],
"TrustDomain":[
{
"Enable":true,
"IPAddress":"192.168.0.1",
"SubnetMask":"24"
}
]
SPTrustDomain
range bypasses the GP_Privilege
check. Replacing 10.1.3.0
with my local subnet 192.168.0.0
reveals that this is in fact true. We are now able to log into telnet:$ telnet 192.168.0.1
Trying 192.168.0.1...
Connected to 192.168.0.1.
Escape character is '^]'.
VMG8825-T50 login: admin
Password:
ZySH> id
>>>>> id
^ Invalid input!
Besides telnet, SSH also works. Both provide us with a very restricted zysh
shell:ZySH> ?
cfg - DAL command line interface
dns - ZYXEL command line
ethwanctl - ZYXEL command line
exit - Close an active terminal session
history - Display or clear CLI history
ifconfig - Show network interface configuration
ping - Send ICMP ECHO_REQUEST to network hosts
pppoectl - ZYXEL command line
sys - ZYXEL command line
tcpdump - Text based packet capture utility
traceroute - monitor each routed node during whole routing path to
vcautohuntctl - ZYXEL command line
voicedbgcli - ZYXEL command line
wan - ZYXEL command line
wlan - ZYXEL command line
xdslctl - ZYXEL command line
zycli - ZYXEL command line
After playing around for a bit, I was not able to find a way to escape. Looking at /etc/passwd
, the supervisor
and root
users should have a regular busybox /bin/sh
shell instead:nobody:x:99:99:nobody:/nonexistent:/bin/false
root:x:0:0:root:/home/root:/bin/sh
supervisor:x:12:12:supervisor:/home/supervisor:/bin/sh
admin:x:21:21:admin:/home/admin:/usr/bin/zysh
At this point, my goal is to find one of their passwords or perform privilege escalation through another method.Filesystem exploration
FTP now also works. It’s chrooted into/home/admin
but luckily the USB drive is mounted at /home/admin/usbX_sdaX/
. Placing a symlink to /
allows us to properly browse the filesystem, finally. We only have admin
-level permissions, but that’s fine. Any file we want to read can be downloaded as root
using the DLNA .wav
-symlink trick :)After a lot of digging around I found the file
/var/zcfg_config.json
. It’s interesting primarily because it is only readable by root, which is a rare property on this device.At first, it seemed to be a stored version of the
Backup_Restore
file available through the web-interface, however it is much larger. In fact, I now believe that the Backup_Restore
file is a dynamically stripped down version of this file.The most juicy part is a much more detailed
X_ZYXEL_LoginCfg
section, including the entries for the root
and supervisor
users that were not present in the Backup_Restore
file:"X_ZYXEL_LoginCfg":{
"LoginGroupConfigurable":true,
"LogGp":[
{
"GP_Privilege":"_encrypt_IjjVfowKNKExRGaE8kN9oA==",
"Account":[
{
...
"Username":"root",
"Password":"",
"PasswordHash":"",
"Privilege":"_encrypt_hmpHlNKl\/1mx0Nor96s75Q==",
"DefaultPassword":"_encrypt_" ,
"shadow":"root:$6$:0::::::\n" ,
"smbpasswd":"root:0:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX::[U ]:LCT-00000070:\n" ,
...
},
{
"Username":"supervisor",
"Password":"",
"PasswordHash":"",
"Privilege":"_encrypt_DTW25ZshjOAAULO3MIcjsi6ysrA793bqPDcDg7KCLiM=",
"DefaultPassword":"_encrypt_" ,
"shadow":"supervisor:$6$:18343::::::\n" ,
"smbpasswd":"supervisor:12:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX::[U ]:LCT-5E7792CF:\n" ,
...
}
],
"Level":"high"
},
{
"GP_Privilege":"_encrypt_IjjVfowKNKExRGaE8kN9oA==",
"Account":[
{
"Username":"admin",
...
},
],
...
},
{
...
}
],
...
},
DefaultPassword
entries for root
and supervisor
are new! Decrypting the _encrypt_
string using the oracle results in the string 2AzX2vWLek
3.Root shell
And indeed, this is not just the default but also the current password for root!VMG8825-T50 login: root
Password:
# id
uid=0(root) gid=0(root) groups=0(root)
# uname -a
Linux VMG8825-T50 3.18.21 #6 SMP Tue Jul 30 10:35:51 CST 2019 mips GNU/Linux
Now that we have the root password, we can perform the encryption and decryption routines for the _encrypt_
configuration strings offline. See tangent 1.This password is likely device-specific and flashed into each device’s bootloader flash before shipping. However, it might not be random, but procedurally derived from known information such as the serial number and the MAC address. See tangent 2.
Tangent 1: the _encrypt_
algorithm
The algorithm behind the _encrypt_
strings in the JSON configuration is quite simple. Tracing the configuration parsing flows leads us to the zcmd
binary, which contains a aesDecryptCbc256
function (it’s not stripped) whose name is self-explanatory. The key is derived from a password and seed using OpenSSL’s EVP_BytesToKey
method. The seed is hardcoded and the password is taken from the encryptKey
global.This
encryptKey
global is filled during zmcd
's initialization flow, in an interesting manner:>> s = [0x54,0x68,0x69,0x53,0x49,0x53,0x45,0x6e,99,0x72,0x79,0x70,0x74,0x69,0x6f,0x4e,0x4b,0x65,0x59]
>>> ''.join(chr(c) for c in s)
'ThiSISEncryptioNKeY'
mtd0
, the bootloader flash), as we can see a bit later in the initialization flow:Using the root shell we can grab it using the same command used by the
zyUtilGetMrdInfo
function:/sbin/mtd -q -q readflash /tmp/flashdump 256 65280 bootloader
Revealing some interesting stuff (sensitive stuff is [censored]):$ xxd flashdump
00000000: 0010 8893 5a79 7865 6c20 436f 6d6d 756e ....Zyxel Commun
00000010: 6963 6174 696f 6e73 2043 6f72 702e 0000 ications Corp...
00000020: 0000 0000 564d 4738 3832 352d 5435 3000 ....VMG8825-T50.
...
00000040: [root password]
00000050: [default admin password]
...
00000080: 0000 0000 0000 0000 0002 0003 0506 0708 ................
00000090: 0f00 0000 0000 0000 0000 0000 015a 5958 .............ZYX
000000a0: 4541 0123 4500 5041 4745 0000 0000 0053 EA.#E.PAGE.....S
000000b0: [serial nr]
000000c0: 0004 0505 0400 0002 0000 0000 0000 0000 ................
000000d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000e0: [default WiFi password]
000000f0: 0104 0202 2620 1919 1256 0000 0000 0000 ....& ...V......
Tangent 2: key and password derivation mechanisms
One very interesting file on this device is thelibzcfg_be.so
library. This is a large (~2MB) file containing many Zyxel helper functions, including the following ones:zcfgBeCommonGenKeyBySerialNum
zcfgBeCommonGenKeyBySerialNum_CBT
zcfgBeCommonGenKeyBySerialNumMethod2
zcfgBeCommonGenKeyBySerialNumMethod3
All of these take the device serial number and use it derive a specific string.These methods are referenced in contexts like
zcfgBeCommonIsApplyRandomSupervisorPasswordNewAlgorithm
, and zcfgBeCommonIsApplyRandomAdminPassword
, implying that the resulting strings could be passwords for the supervisor
or admin
accounts. Some methods appear to be used to generate the default WPA2 password, as was nicely described in this blog post by Luciano Corsalini.My interest was mostly focussed on the function
zcfgBeCommonGenKeyBySerialNumMethod3
, which is apparently intended to be used to generate a supervisor
password. After implementing this algorithm myself, it indeed results
in a password of the same format as the discovered root password (random
upper/lowercase digits, 10 characters long). Nevertheless, it doesn’t
match the root password nor the supervisor password.So one of the following is true:
- My implementation is flawed. Likely, given the finnicky nature of the algorithm and my lack of MIPS reverse-engineering experience.
- The root password of this router is not derived, and these functions are just left over from other/older models.
Risk of abuse
As all of the above “vulnerabilities” depend upon having access to the router’s web interface, I believe there is no risk of abuse by an external attacker, neither from the WAN nor LAN side; as long as youradmin
account password is secure (I’m not sure if the default is derivable…).Nevertheless, the complexity of this device and the massive amount of custom code running and listening on
0.0.0.0
is worrysome. Given the interesting
design decisions and protection mechanisms, I would not be surprised if
any logic flaws or memory corruption vulnerabilities were found.-
It’s likely easier to go about this from the hardware side: dump the
flash or look for a UART port. But doing it all in software from the
couch was an interesting challenge for me. ↩︎
-
The only things I found to be missing from the
Backup_Restore
file are the SMB sharing settings. These are apparently stored on the respective USB drive partition in a file called.Zyxel/.ZyTemp
. These files contain|
-separated configuration values that are inserted into a Samba configuration file. Some of these fields are not editable from the web interface and editing them might yield interesting results. ↩︎
-
Not the actual password for my device, but the pattern is the same. It is presumably different for each device anyway. ↩︎
Fonte Articolo: Getting root on a Zyxel VMG8825-T50 router
Nessun commento:
Posta un commento