内容简介:My ISP recently provided me with a new router, the Zyxel VMG8825-T50. It seems to be a relatively new gigabit router with all kinds of capabilities. Sadly, some of them are locked down behind a somewhat restrictive web interface.This post details my steps
My ISP recently provided me with a new router, the Zyxel VMG8825-T50. It seems to be a relatively new gigabit router with all kinds of capabilities. Sadly, some of them are locked down behind a somewhat restrictive web interface.
This post details my steps towards getting a root shell on this device through software-only means.
TL;DR: using these four simple tricks you can get a root shell on your Zyxel VMG8825-T50 router:
- 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.
Other initial observations about this device:
- 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 everythingthat’s configurable through the web-interface, and some more:
admin
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 least FAT32
, 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
There is an easy workaround to the extension issue: symbolic links!
On the USB drive, make something that looks like a media file:
~$ ln -s /etc/passwd passwd.wav
Then browse to /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, the Backup_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 }
with the following:
{ "Name":"SSH", "Enable":true, "Protocol":6, "Port":22, "Mode":"LAN_ONLY", "TrustAll":true, "BoundInterfaceList":"IP.Interface.4,IP.Interface.7,IP.Interface.10,", }
(the 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); }
Let’s grab the busybox binary from the device using the DLNA symlink trick:
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", ...
Sadly, it is one of the apparently sensitive set of strings in the configuration backup file that is encrypted, as denoted by the _encrypt_
prefix.
The _encrypt_
oracle
After reverse-engineering the mechanism behind these _encrypt_
values () 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" },
Because the _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); }
Hmmm! Again it appears that our busybox binary contains similar code:
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" } ]
So it seems that any IP in this 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 <host> 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_<snip>", "shadow":"root:$6$<snip>:0::::::\n", "smbpasswd":"root:0:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX:<snip>:[U ]:LCT-00000070:\n", ... }, { "Username":"supervisor", "Password":"", "PasswordHash":"", "Privilege":"_encrypt_DTW25ZshjOAAULO3MIcjsi6ysrA793bqPDcDg7KCLiM=", "DefaultPassword":"_encrypt_<snip>", "shadow":"supervisor:$6$<snip>:18343::::::\n", "smbpasswd":"supervisor:12:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX:<snip>:[U ]:LCT-5E7792CF:\n", ... } ], "Level":"high" }, { "GP_Privilege":"_encrypt_IjjVfowKNKExRGaE8kN9oA==", "Account":[ { "Username":"admin", ... }, ], ... }, { ... } ], ... },
I already knew the hashes (both the shadow and smbpasswd versions), but the DefaultPassword
entries for root
and supervisor
are new! Decrypting the _encrypt_
string using the oracle results in the string 2AzX2vWLek
.
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.
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 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'
This is a funny default value, but the actual key is derived from the supervisor password, which is retrieved from flash (specifically 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 the libzcfg_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.
Either way, more reversing to be done… :)
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 your admin
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.
以上所述就是小编给大家介绍的《Getting root on a Zyxel VMG8825-T50 router》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
史蒂夫·乔布斯传
[美] 沃尔特·艾萨克森 / 管延圻、魏群、余倩、赵萌萌、汤崧 / 中信出版社 / 2011-10-24 / 68.00元
这本乔布斯唯一授权的官方传记,在2011年上半年由美国出版商西蒙舒斯特对外发布出版消息以来,备受全球媒体和业界瞩目,这本书的全球出版日期最终确定为2011年11月21日,简体中文版也将同步上市。 两年多的时间,与乔布斯40多次的面对面倾谈,以及与乔布斯一百多个家庭成员、 朋友、竞争对手、同事的不受限的采访,造就了这本独家传记。 尽管乔布斯给予本书的采访和创作全面的配合,但他对内容从不干......一起来看看 《史蒂夫·乔布斯传》 这本书的介绍吧!