Tenda W18E Security Research
Initial Research
I acquired a Tenda W18E AC1200 Gigabit Wireless Hotspot Router from eBay for $60, running firmware v1625. Two previous CVEs were published for firmware v1576: CVE-2023-46369 (a stack overflow vulnerability) and CVE-2023-46370 (a command injection vulnerability). Notably, Tenda no longer offers firmware v1576 on their support download page. Additionally, these vulnerabilities were originally disclosed by Github user Archerber, but the original disclosure at https://github.com/Archerber/bug_submit has since been deleted.
Firmware Acquisition
I acquired firmware images for versions v1449 (dated 2019-07-31), v1625 (dated 2020-05-30), and v2866 (dated 2021-12-24) directly from Tenda's support site. Through additional research, I was also able to obtain v1576. The firmware was obfuscated, presenting as uImage files with squashfs root partitions but using custom LZMA magic bytes and CRC polynomials. In order to extract them, I modified squashfs-tools (https://github.com/plougher/squashfs-tools) and xz (https://chromium.googlesource.com/chromium/deps/xz) to use the custom LZMA magic bytes and ignore CRC checks altogether, chaining them together with LD_PRELOAD. This successfully gave me the kernel and rootfs partition for all four firmware versions I'd acquired.
Prior Proof of Concept (PoC) Acquisition/Development
I recovered the information deleted from Archerber's git repo, but with it being unlicensed, will refrain from posting their PoCs here. Using the information obtained, I developed a PoC for CVE-2023-46370 and found that it still applied to the firmware on my router (v1625). This vulnerability allowed me to open telnet, gain a root shell, and use the included busybox on-device to transfer files via tftp.
At different stages during my research, in an effort to better the research environment, I deployed a statically-compiled gdbserver for MIPS, as well as httpd binaries for all four firmware versions to the router.
Expanding CVEs 2023-46369 and 2023-46370
CVE-2023-46369 - Stack Overflow in portMirrorMirroredPorts
This is caused by a bad sprintf
in the function that handles setPortMirror
data for the /goform/module
endpoint, which (in this case) requires an authorized user.
Furthermore, I was able to successfully control the return address, meaning that this is guaranteed to be able to cause RCE.
I was able to confirm that this applies to all four firmware versions I obtained, relevant CPEs below:
- Already disclosed
cpe:2.3:h:tenda:w18e:1.0:*:*:*:*:*:*:*
cpe:2.3:o:tenda:w18e_firmware:16.01.0.8(1576):*:*:*:*:*:*:*
- Suggested to be added
cpe:2.3:o:tenda:w18e_firmware:16.01.0.7(1449):*:*:*:*:*:*:*
cpe:2.3:o:tenda:w18e_firmware:16.01.0.8(1625):*:*:*:*:*:*:*
cpe:2.3:o:tenda:w18e_firmware:16.01.0.9(2866):*:*:*:*:*:*:*
CVE-2023-46370 - Command Inject in formSetNetCheckTools
This is caused by moving user data into a popen
via sprintf
in the function that handles setFixTools
data for the /goform/module
endpoint, which (in this case as well) requires an authorized user.
There is an attempt to filter command inject data, but it only guards against ;
, &
, and |
, ignoring command substitution entirely.
I was able to confirm that this applies to all four firmware versions I obtained, relevant CPEs below:
- Already disclosed
- cpe:2.3:h:tenda:w18e:1.0:::::::*
- cpe:2.3⭕tenda:w18e_firmware:16.01.0.8(1576):::::::*
- Suggested to be added
- cpe:2.3⭕tenda:w18e_firmware:16.01.0.7(1449):::::::*
- cpe:2.3⭕tenda:w18e_firmware:16.01.0.8(1625):::::::*
- cpe:2.3⭕tenda:w18e_firmware:16.01.0.9(2866):::::::*
No-Auth
Every request passes through authSecurityHandler
to determine if logon is needed or not. This passes on all /goform/module
requests, which is the API that underpins the entire system, to have auth handled at a later stage.
The way Ghidra disassembles it doesn't make a ton of sense, but it results in the following truth table (0 means no auth needed):
/goform/module 0
/goform/usbTest 0
/lang 0
/goform/getAdStatistics 0
/disable.html 0
/goform/getLoginType 0
/authManage/preTimeout.html 0
/goform/getPreTimeoutNotify 0
/goform/getTimeoutNotify 0
/goform/ackPreTimeoutNotify 0
/goform/checkLoginUser 0
/goform/getQuickCfg 1
/goform/ate 1
/goform/telnet 1
/goform/initAdminUser 1
That later stage, however, first checks if the user is logged in (by way of their IP address and a blank cookie), and if they're not, checks the JSON payload of the request against a hard-coded list of endpoints that don't require auth.
This creates a landslide of different impacts based on which endpoint is used, detailed here:
Proposed CVE: initAdminUser
Authentication Bypass (CWE-306, CWE-287)
An unauthorized actor with access to the web management portal can bypass authentication entirely.
The first two curl
commands are to show that the device doesn't simply remember my IP and have me logged in already; the third is the exploit, and the fourth shows that the device believes we're logged in. This can also be seen in the Ghidra decompilation of the handling function.
Proposed CVE: setQuickCfgWifiAndLogin
Unauthorized Configuration Change (CWE-306, CWE-620, CWE-642)
An unauthorized actor with access to the web management portal can change the WiFi SSID, WiFi auth method and password, and the administrator user password. This also logs the user in, bypassing authentication.
Proposed CVE: getQuickCfgWifiAndLogin
Sensitive Information Disclosure (CWE-306, CWE-261)
An unauthorized actor with access to the web management portal can recover information about the running configuration, including WiFi SSID, WiFi password, and the administrator user credentials (base64-encoded).
Proposed CVE: setLoginPassword
Unauthorized Password Change (CWE-306, CWE-620)
An unauthorized actor with access to the web management portal can change the administrator password.
Default Auth
In addition to auth being missing for many API endpoints, there's also a significant issue with default, easily-guessable credentials.
Proposed CVE: Hardcoded guest
Credentials with Privileged Access (CWE-259)
In all firmware versions, there is a default guest
account with the password guest
. In firmware versions v1449, v1576, and v1625, there is no indicator to the administrator that this account exists and there is no method presented by the software to change the guest password. The guest account, despite its name, is an admin account, and is fully-featured to make changes and read passwords. Patch notes state that Tenda fixed the issue of it not appearing in the GUI in v2866, but what really happened is they removed the guest account from the default configuration.
Proposed CVE: Default rzadmin
Credentials (CWE-1392)
In all firmware versions, there is a default rzadmin
account with the password rzadmin
. The user is not prompted to change this account password during setup, and would only see that it exists via the Password Manager screen. While the GUI for this user is limited, it is actually another admin account with full access to the API, which, among other things, allows it to read the administrator password.
Proposed CVE: Hardcoded telnet
Credentials with Privileged Access (CWE-259)
While telnet is not on by default, once enabled, the root user's password is Fireitup
. These credentials have been used on a number of other Tenda devices, are easily discovered on the internet, and cannot be changed on the device.
Additional Stack-Bashing
CVE-2023-46369 isn't the only memory-unsafe function that takes user input over the network.
Proposed CVE: delWewifiPic
Stack Overflow (CWE-121)
An authorized actor with access to the web management portal can cause a stack overflow, caused by using sprintf
to copy user data into a statically-sized stack buffer.
I wasn't able to control the return address as neatly on this one as I was with CVE-2023-46369, but I don't think it would be a stretch to turn this into RCE.
Proposed CVE: delFacebookPic
Stack Overflow (CWE-121)
An authorized actor with access to the web management portal can cause a stack overflow, caused by using sprintf
to copy user data into a statically-sized stack buffer.
This one is almost identical to the delWewifiPic
, down to how hard it was to control the return address appropriately.