# Vulnerabilities in Tenda's W15Ev2 AC1200 Router
Reverse engineering Tenda's W15Ev2 AC1200 SOHO router discovering 10 0-days
(10 CVEs)
#### [Olivier Laflamme](/author/olivier/)
Oct 19, 2022 • 33 min read
Lately, after work, I've really enjoyed hacking and reverse engineering funky
IoT devices. In early May of 2022, I went on a little Amazon/AliExpress
shopping spree and bought ~15 cheap IoT devices. Among them was this compact
SOHO router by [Tenda](https://www.tendacn.com/).
![](https://images.seebug.org/1673319007429-w331s)
Shenzhen Tenda Technology Co., Ltd. hereafter referred to as Tenda is one of
the leading suppliers of networking devices and equipment. Tenda has committed
to delivering easy-to-install and affordable networking solutions, offering
innovative, cutting-edge products to realize people's intelligent life. They
are a Chinese company based in Shenzhen, China with R&D centers, in Shenzhen
and Chengdu. Reportedly with over ~1000 employees they're fairly well
established in the industry and offer a wide variety of products. The
company's official website is [www.tendacn.com](https://www.tendacn.com/).
The router I purchased was the AC1200 SOHO router model _W15Ev2_ with the
[firmware
V15.11.0.10(1576)](https://www.tendacn.com/download/detail-3762.html). The
chip model for the device is `qca9531` (qca9531_wifi) by Qualcomm which is a
2.4 GHz System-on-a-Chip (SoC) that leverages a MIPS 24Kc processor.
Performing a `GET` request on `/goform/version` or `/common/macro_config.js`
identifies the firmware version. Out-of-the-box mine was
`CONFIG_FIRWARE_VERION: "V15.11.0.10(1576)"` which was released **≤
**`2019-12-17`
![](https://images.seebug.org/1673319008300-w331s)
Going to Tenda's firmware
[downloads](https://www.tenda.com.cn/product/download/W15E.html) page confirms
we're on their latest Chinese deployment. Due to the seeming lack of interest,
I will only be testing the `V15.11.0.10(1576)` firmware.
![](https://images.seebug.org/1673319009104-w331s)
* * *
Multiple attempts were made through numerous communication channels to contact
this vendor, but all failed. I try my best to act in a transparent,
responsible, and consistent manner, with the goal of building a safer online
space. In order to achieve this, I adhere to a 90-day time frame from the
initial contact to public disclosure. If you have any ethical dilemmas or
moral qualms as a result of this publication, I urge you to read the section
entitled "Thoughts On Responsible Disclosure" situated towards the end of the
blog to better understand the events, my perspective, and reasons for
disclosure.
### Disclosure Timeline
**July 18, 2022** : Vulnerability discovered and first reported
**August 6, 2022** : Second attempt to make contact, further informing the
vendor of the severity of the vulnerability
**August 13, 2022** : WeChat & QQ attempt to contact the vendor
**September 2, 2022** : A Third attempt to contact the vendor
**September 13, 2022** : File for CVE assignment through Mitre.org
**September 19, 2022** : File for additional CVE assignment through Mitre.org
**September 26, 2022** : File for additional CVE assignment through Mitre.org
**September 26, 2022** : Forth attempt to contact vendor, informing them of
assigned 5 CVEs
**October 3, 2022** : Fifth attempt to make contact, further informing vendors
of the severity & number of vulnerabilities
**October 10, 2022** : Sixth attempt to contact the vendor
**October 17, 2022** : Final contact attempted & warning of public disclosure
& confirming 10 CVEs had been assigned to their product
**October 19, 2022** : Public disclosure
* * *
### Vulnerabilities List
A total of 11 vulnerabilities were identified in Tenda's AC1200 W15Ev2 router
and IoT cloud gateway peripheral:
1. Improper Authorization (CVE-2022-40843)
2. Password Disclosure (CVE-2022-40845)
3. Improper Access Control
4. OS Command Injection #1 (CVE-2022-40847)
5. OS Command Injection #2 (CVE-2022-41396)
6. OS Command Injection #3 (CVE-2022-41395)
7. OS Command Injection #4 (CVE-2022-42053)
8. Stack-based Buffer Overflow #1 (CVE-2022-42058)
9. Stack-based Buffer Overflow #2 (CVE-2022-42060)
10. Stored XSS in Website Filtering (CVE-2022-40844)
11. Stored XSS in Online Device Names (CVE-2022-40846)
* * *
## **Web Application**
### Improper Authorization
The Tenda AC1200 V-W15Ev2 router is affected by improper
authorization/improper session management. The software does not perform or
incorrectly perform an authorization check when a user attempts to access a
resource or perform an action. This allows the router's login page to be
bypassed. The improper validation of user sessions/authorization can lead to
unauthenticated attackers having the ability to read the router's `syslog.log`
file, which contains the MD5 password of the Administrator's user account.
This vulnerability exists within the local web and hosted remote management
console. The vulnerability affects version V15.11.0.10(1576).
### Vulnerability Details
CVE ID: CVE-2022-40843
Access Vector: Remote/Local
Security Risk: Critical
Vulnerability: CWE-287 / CWE-285 / CWE-228
CVSS Base Score: 9.9
CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:L
This vulnerability is present locally out-of-the-box without any
user/administrator configuration and remotely once an administrator user has
enabled the "Remote Web Management" configuration as seen below.
![](https://images.seebug.org/1673319009785-w331s)
Enabling this setting would allow the router's owner to manage the device from
anywhere in the world via the provided public URL. Enabling this configuration
creates a [ngrok](https://github.com/inconshreveable/ngrok) tunnel to a
predefined `could.tendacn.net` subdomain.
ngrokc -Ser host:cloud.tendacn.net,port:4443 -AddTun type:http,lhost:192.168.0.1,lport:80,subdomain:6nyz4sf4 -Inf vlan2
![](https://images.seebug.org/1673319010921-w331s)
Once enable the router can be accessed through the public web as seen below.
![](https://images.seebug.org/1673319011599-w331s)
If you're wondering how many of these devices are configured like this, the
answer is, thankfully not many. With some inefficient Google dorking
`"*.cloud.tendacn.net:8080"` I was able to find `~18` devices configured with
the cloud gateway. There is no way of ethically knowing what these devices
are.
> _DISCLAIMER_
>
> If you go hunting for these hosts with any malice and start testing what you
> see below, I'd consider your activities as active intrusion. Leveraging
> vulnerabilities against hosts you do not own is NOT something I endorse & is
> unethical.
> Subsequently, all traffic that passed through 6nyz4sf4.cloud.tendacn.net
> did not target anything other than my own device. I was not directly
> attacking or affecting the availability & integrity of the public/reserved
> gateway.
> *.cloud.tendacn.net is only a public-facing tunnel to the router I own.
> Traffic passing through this public-facing proxy is simply passed as regular
> traffic/requests. Additionally, I never attempted or performed any
> reconnaissance or automated tests against cloud.tendacn.net.
The vulnerability in & of itself stems from a complete lack of proper session
token generation & the presence of authentication levels that should be
expected from a cookie. The following images will help demonstrate this.
After logging into the device, the user is provided with the following
cookies.
Cookie: bLanguage=cn; _:USERNAME:_=admin; W15Ev2_user=admin
However, both the `bLanguage=cn;` and the `_:USERNAME _:_ =admin;` cookies are
not required. Additionally `W15Ev2_user=` can simply be left blank.
![](https://images.seebug.org/1673319012327-w331s)
As seen below you do need the `W15Ev2_user=` cookie or else you'll be
redirected to the login as seen below, which is quite interesting.
![](https://images.seebug.org/1673319021886-w331s)
This obviously isn't good. As seen in 2 images above, I was able to fetch the
`/syslog.log` which should only be viewed/accessible once authenticated.
![](https://images.seebug.org/1673319030999-w331s)
This endpoint can leak the admin login event containing the base64 password of
the administrator user. This can easily be leveraged to gain access to the
device. As seen below we have the ability to perform this both locally or
remotely via our assigned Remote Web Management endpoint. To demo, we'll
perform a GET request on `/goform/downloadSyslog/syslog.log` with the cookie
set to `W15Ev2_user=`.
![](https://images.seebug.org/1673319033501-w331s)
Scrolling down (this is a big response) we'll see any failed/successful login
attempts. Successful attempts will contain the base64 encoded admin password.
![](https://images.seebug.org/1673319034842-w331s)![](https://images.seebug.org/1673319035923-w331s)
The curl of exactly the same request is seen below. Some people don't seem to
always trust Burp Suite/might think my target is pointed at my localhost. (Not
that it would invalidate the vulnerability)
![](https://images.seebug.org/1673319036618-w331s)
If you take the time to decompile the `httpd` binary the culprit is the
`authSecurityHandler` function that I won't be going into here & will leave as
an exercise to the reader.
You might be thinking _"yeah, but who cares"_? __ With access to the router,
you're able to perform internal network scans, obtain the wifi's PSK, set up
your own port forwarding, etc,. Other attacks can be chained with this
"improper session management" to perform command injections and obtain a shell
on the device. Although unlikely, it would suck to see a small-medium-sized
company use one of these devices as a random & cheap AP/Switch for a small
cluster of employees, thinking it was inherently safe.
* * *
### Password Exposure
The Tenda AC1200 V-W15Ev2 router is affected by a password exposure
vulnerability. When combined with the improper authorization/improper session
management vulnerability, an attacker with access to the router may be able to
expose sensitive information which they're not explicitly authorized to have.
This is performed via a direct request to `/cgi-bin/DownloadCfg/RouterCfm.cfg`
without authorization. This vulnerability exists within the local web
interface and the hosted remote web management console. An attacker would be
able to retrieve a subset of passwords via this technique. The vulnerability
affects version V15.11.0.10(1576).
### Vulnerability Details
CVE ID: CVE-2022-40845
Access Vector: Remote/Local
Security Risk: High
Vulnerability: CWE-200
CVSS Base Score: 8.8
CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Like the previous vulnerability, this can be performed both locally or
remotely through the `cloud.tendacn.net` proxy tunnel. Generally, downloading
backups could only be performed once authenticated.
![](https://images.seebug.org/1673319037647-w331s)
However, there isn't a need to have the device initiated to perform this. At
its core, a backup file containing hashes of users' passwords, PSK's, etc. can
easily be downloaded. This is generally considered bad hygiene/practice.
Realistically, the impact on organizations depends on many factors that are
unique to each organization. You can decide this for yourself when presented
with the information a little further down.
I was a little confused at first, I didn't think this router had a CGI
interface. Taking a step back, I dug up this endpoint when scouring through
the firmware dump. It's a weird subset that is mainly defined to make
executables on the server respond to web requests.
![](https://images.seebug.org/1673319038604-w331s)
For all three requests seen in the images below this is more-or-less the
action's performed in the backend.
![](https://images.seebug.org/1673319039618-w331s)
Here is the attack being performed locally with Burp Suite.
![](https://images.seebug.org/1673319040410-w331s)
Remotely with Burp Suite.
![](https://images.seebug.org/1673319041205-w331s)
Remotely with the curl command.
![](https://images.seebug.org/1673319042080-w331s)
The crucial information obtained is listed below.
sys.rzadmin.username= rzadmin
sys.rzadmin.password= cnphZG1pbg== (rzadmin)
sys.rzadmin.password.decoded= r***n
wlan0.0_bss_wpapsk_key= this_is_my_super_secret_ssid_password
sys.admin.username= admin
sys.admin.password.decoded= t***d
sys.admin.password= = base64 PSK
usb.share.gst0.user= guest
usb.share.gst0.pwd= guest
sys.guest.password.decoded= g***t
sys.guest.password= Z3Vlc3Q= (guest)
* * *
### Improper Access Control
The Tenda AC1200 V-W15Ev2 router is affected by improper access control. An
attacker can start telnet without authorization by performing a `GET` request
to the `/goform/telnet` endpoint, spawning the `telnetd` service on the
device. This service is password protected; however, the default account
credentials were identified as `root:Fireitup` which can be used to
successfully login. The vulnerability affects version V15.11.0.10(1576).
### Vulnerability Details
CVE ID: N/A (Already assigned to CVE-2018-5770)
Access Vector: Remote/Local
Security Risk: High
Vulnerability: CWE-287 / CWE-284
CVSS Base Score: 8.4
CVSS Vector: CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
There's not much to explain here. As seen below the device doesn't ship with
telnet natively being spawned nor is it defined in `init.d`.
![](https://images.seebug.org/1673319050972-w331s)
A simple `POST` request to `/goform/telnet` will start telnet as seen in the
requests below.
![](https://images.seebug.org/1673319051618-w331s)![](https://images.seebug.org/1673319053012-w331s)
Interestingly, this can be performed through the ngrok tunnel. However, I've
deemed that this endpoint is out of scope as I'm not 100% confident this
relays back to my device due to the unfamiliar motd.
![](https://images.seebug.org/1673319053694-w331s)
As seen below telnet is now up and accessible.
![](https://images.seebug.org/1673319054735-w331s)
I did some digging to identify the telnet password. The earliest mention I
could find dates back to 2017 from
[this](https://www.linksysinfo.org/index.php?threads/tenda-ac15-ac1900-tomato-
firmware-support.71709/page-7) forum by user Chaos99. This was for the AC15
and we've got an AC1200.
![](https://images.seebug.org/1673319056078-w331s)
This doesn't seem to matter. As seen below, Chaos99 was right; the password is
still `Fireitup`.
![](https://images.seebug.org/1673319065078-w331s)
I did some digging to identify anything else that can be invoked via
`/goform/*` but there doesn't appear to be anything of value.
![](https://images.seebug.org/1673319069144-w331s)
* * *
### **OS Command Injection #1**
The Tenda AC1200 V-W15Ev2 router is affected by an OS Command Injection
vulnerability that allows an authenticated attacker to run arbitrary commands
on the affected system as the application user. This vulnerability exists
within the ping function `formSetFixTools`. This is the result of improper
input validation passed through the `hostName` parameter. This vulnerability
exists within the local web interface and the hosted remote web management
console. The vulnerability affects version V15.11.0.10(1576).
### Vulnerability Details
CVE ID: CVE-2022-40847
Access Vector: Remote/Local
Security Risk: High
Vulnerability: CWE-78
CVSS Base Score: 7.8
CVSS Vector: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
This command injection vulnerability was discovered through the serial
logging. These logs were viewable by simply connecting to the router's UART
interface.
![](https://images.seebug.org/1673319069910-w331s)
If you're wondering why the serial interface is sending out all of our logs
and actions, I'm not 100% sure. I'm assuming it's so that application-specific
errors can be sent to the log using some API methods. Without this serial
logging, I'm not sure I would've discovered this command injection
vulnerability.
I somewhat got lucky and have a methodology that leads to these discoveries. I
initially fizzed the `hostName` parameter with Burp Suite intruder using a mix
of custom and common command injections mainly found in this
[GitHub](https://github.com/omurugur/OS_Command_Payload_List/blob/master/OS-
Command-Fuzzing.txt) repository. It just so happened that I was paying
attention to my serial connection terminal at the right time.
![](https://images.seebug.org/1673319079714-w331s)
As seen below, the `hostName` parameter of the `setFixTools` is responsible
for performing ping diagnostics and is vulnerable to OS command injection. The
following payload was injected into the vulnerable `hostName` parameter
`$(`cat /etc/passwd`)`. Once the request was sent, the contents of the
`/etc/passwd` file was returned via the serial console.
![](https://images.seebug.org/1673319080534-w331s)
Digging deeper was quite amusing. The `$PATH` variable was blank but ~10% of
native UNIX commands still appeared to be working.
![](https://images.seebug.org/1673319089529-w331s)
I sent the two following `POST` requests to fix this issue.
$(`PATH=/sbin:/bin:/usr/sbin:/usr/bin/`)
$(`export PATH`)
As seen below we now have proper path variables.
![](https://images.seebug.org/1673319090547-w331s)
How will we get a shell? If we look at all the commands at our disposal, we
don't have `nc`, `netcat` and `wget` is in fact a broken binary in the system.
Additionally, this is not a standard shell `enable` and `compgen -b` which
always exist aren't there (if you know, you know).
We have `/bin/busybox` and by listing its capabilities, we discover that we
have `telnetd`!
![](https://images.seebug.org/1673319091566-w331s)
We can leverage our command injection to start `telnetd` with the `-l` flag
pointing at `/bin/sh` to obtain a root shell without needing the telnet
username and password.
Our attack path consists of the following 5 steps:
Step #1 - identify telnet pid - we can't use `|` or `&&` or `;`
Payload 1: 'ps >/tmp/a'
Step #2 - move it to webroot so we can get it
Payload 2: 'cp /tmp/a /var/webroot'
Step #3 - kill telnetd if its alive
Payload 3: 'kill -9 PID'
Step #4 - copy sh executable in pwd
Payload 4: 'cp /bin/sh .'
Step #5 - restart telnetd binded on sh
Payload 5: 'telnetd -l sh'
First, we'll pipe the contents of the systems processes into a file in `/tmp`.
![](https://images.seebug.org/1673319092537-w331s)
We'll then move our process listing into the webroot so we grab and analyze
its contents.
![](https://images.seebug.org/1673319101346-w331s)
Go to `192.168.0.1/a` to retrieve the file.
![](https://images.seebug.org/1673319102050-w331s)![](https://images.seebug.org/1673319102840-w331s)
Telnet is currently running with a PID of 3718. However, this telnet is no
good because we need to know the credentials for it. So we'll kill this
process.
![](https://images.seebug.org/1673319103471-w331s)
We'll then move the `sh` binary to our current working directory.
![](https://images.seebug.org/1673319113497-w331s)
Lastly, we'll invoke `telnetd` with the `-l` flag set to execute `/bin/sh` at
the login/connection attempt.
![](https://images.seebug.org/1673319114254-w331s)
Now we should just be able to telnet without requiring a password.
![](https://images.seebug.org/1673319123436-w331s)
Tada.
* * *
### **OS Command Injection #2**
The Tenda AC1200 V-W15Ev2 router is affected by an OS Command Injection
vulnerability that allows authenticated attackers to run arbitrary commands on
the affected system as the application's user. This vulnerability exists
within the IPsec router configuration, specifically via the `/goform/module`
function `setIPsecTunnelList`. This vulnerability in part requires the
router's VPN server to be enabled (which it is by default but can be
disabled). At its core, the `httpd` binary contains the vulnerability and can
occur when a malicious `formSetIpSecTunnel` parameter gets processed. During
this process, inner process communication is established through the `netctrl`
binary. This vulnerability is the result of improper input validation passed
through the `IPsecLocalNet` or `IPsecRemoteNet` parameter. This vulnerability
exists within the local web interface and the hosted remote web management
console. The vulnerability affects version V15.11.0.10(1576).
### Vulnerability Details
CVE ID: CVE-2022-41396
Access Vector: Remote/Local
Security Risk: High
Vulnerability: CWE-78
CVSS Base Score: 7.8
CVSS Vector: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
In the `formSetIpSecTunnel` function, both the `IPsecLocalNet` and
`IPsecRemoteNet` parameters are passed directly to the application, which can
be manipulated by the user. The _Local_ (`IPsecLocalNet`) and _Remote_
(`IPsecRemoteNet`) network parameters can be maliciously injected into.
To understand what's going on, we need to reverse the application a little.
This vulnerability occurs in the `setIpSecTunnel` function. Below, we enter
the function `getIpSecTunnelSettings` and the rabbit hole begins.
![](https://images.seebug.org/1673319132175-w331s)
The parameters that receive our JSON user input are in the function
`getIpSecTunnelSettings`.
![](https://images.seebug.org/1673319132845-w331s)
In the `setIpSecTunnelSettings` function, the input has not been checked. And
then a call to the function `prod_cfm_set_init_val` is made to store this
input.
![](https://images.seebug.org/1673319133757-w331s)
These are the function parameters passed through the `POST` command. But
what's actually happening in `setIpSecTunnelSetting`? It isn't immediately
apparent, but at the end of this decompilation there's a pretty significant
block of code related to, and required for inter-process communication. This
all happens in another binary that handles the passed input, leveraging the
contents through other functions that write this new data and changes to the
flash. Therefore, to see how we can inject, we need to look at that binary.
After a little digging, it was discovered that the vulnerability would be
triggered in `netctrl`.
If we want our `IPsecLocalNet` or `IPsecRemoteNet` input to be extracted, we
have to have one of the following protocols initiated.
![](https://images.seebug.org/1673319134509-w331s)
I've had success with triggering this command injection by setting the
`vpnServerType` to `l2tp`. However, this VPN stuff is all initiated by default
and should just work out of the box with PPTP.
![](https://images.seebug.org/1673319135198-w331s)
When you successfully write to the flash and `racoonmtc` gets initiated with
the valid injection contents, then you know you've performed this
successfully. Over the serial log, this looks like the following.
![](https://images.seebug.org/1673319135917-w331s)
Inside the `netctrl` binary, looking at the `vpn_ipsec_ini` function, the
initial input will be extracted and injected into `%s`, which is how we will
achieve our code execution. We can further use command separators to escape
the `iptabes` command.
![](https://images.seebug.org/1673319136602-w331s)
Let's visualize this by moving to a GUI. The front-end vulnerable input is
tagged as "Remote Internet" and "Local Internet" which maps to `IPsecLocalNet`
and `IPsecRemoteNet` respectively.
![](https://images.seebug.org/1673319137407-w331s)
To test that we have command injection let's enter a random string and see
`/bin/sh` attempt to execute it.
![](https://images.seebug.org/1673319138390-w331s)
As we can from the serial log, "badcommand" is not a valid binary.
![](https://images.seebug.org/1673319139214-w331s)
If you mess up you'll have varying verbose output. Here are a few examples you
can use to better craft a more sophisticated payload.
![](https://images.seebug.org/1673319140012-w331s)![](https://images.seebug.org/1673319140662-w331s)
Let's try starting telnet using this command injection.
![](https://images.seebug.org/1673319141244-w331s)
Viewing the serial log for this request shouldn't contain `/bin/sh` errors. As
seen above and below, `telnetd` has been successfully started.
![](https://images.seebug.org/1673319142555-w331s)
Tada.
![](https://images.seebug.org/1673319143492-w331s)
* * *
### **OS Command Injection #3**
The Tenda AC1200 V-W15Ev2 router is affected by an OS Command Injection
vulnerability that allows authenticated attackers to run arbitrary commands on
the affected system as the application's user. This vulnerability exists
within the DMZ router configuration, specifically via the `/goform/module`
function `setDMZ`. The vulnerability was identified in the `netctrl` binary
tracing preprocessing calls from the `httpd` binary. This vulnerability is the
result of improper input validation passed through the `dmzHost` parameter.
This vulnerability exists within the local web interface and the hosted remote
web management console. The vulnerability affects version V15.11.0.10(1576).
### Vulnerability Details
CVE ID: CVE-2022-41395
Access Vector: Remote/Adjacent
Security Risk: High
Vulnerability: CWE-78
CVSS Base Score: 7.8
CVSS Vector: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Continuing the investigation into `netctrl`, the culprit for the injection
above, I ended up finding more injections.
In the `formSetDMZ` function, `dmzHost` can be directly passed by the
attacker. If we can control the `dmzHost` input, presumably we can attack the
system. Looking into the `httpd` binary seen below, the JSON-provided input
doesn't get checked or sanitized. After that, it calls the function
`prod_cfm_set_value` to store this input. So the parameter `dmzIp` is what
we're looking to inject into it.
![](https://images.seebug.org/1673319144236-w331s)
The `netctrl` binary `advance_set_dmz_cfg` function's input will grab the
provided input passed to it by `httpd`.
![](https://images.seebug.org/1673319145549-w331s)
Eventually, in the `advance_set_dmz_cfg` function, this unchecked input could
be leveraged to perform command injection when passed through `%s`. Looking at
this in Burp Suite, we'll modify our injectable `dmzHost` parameter. We're
testing with fake binary to see what gets acted on it/the errors we receive.
![](https://images.seebug.org/1673319146644-w331s)
The `POST` request above will ultimately write these new configs to flash.
Below you'll also see `/bin/sh` try and run the `boschko_is_hacking` binary,
and fail because doesn't exist.
![](https://images.seebug.org/1673319147364-w331s)
This is great information, we more or less know we should be able to spawn
`telnetd`. As seen below telnet wasn't running a few seconds prior and then
was. Disregard the date I'm too lazy to go change it.
![](https://images.seebug.org/1673319148204-w331s)![](https://images.seebug.org/1673319149340-w331s)
* * *
### **OS Command Injection #4**
The Tenda AC1200 V-W15Ev2 router is affected by an OS Command Injection
vulnerability that allows authenticated attackers to run arbitrary commands on
the affected system as the application's user. This vulnerability exists
within the Port Mapping router configuration, specifically via the
`/goform/module` function `setPortMapping`. The vulnerability was identified
in the `netctrl` binary tracing preprocessing calls from the `httpd` binary.
This vulnerability is the result of improper input validation passed through
the `portMappingServer` parameter. This vulnerability exists within the local
web interface and the hosted remote web management console. The vulnerability
affects version V15.11.0.10(1576).
### Vulnerability Details
CVE ID: CVE-2022-42053
Access Vector: Remote/Local
Security Risk: High
Vulnerability: CWE-78
CVSS Base Score: 7.8
CVSS Vector: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
At this point, you get the idea. I just kept looking at the `netctrl` binary
and stumbled onto the `advance_set_port_map_cfg` function which looks a lot
like our previous command injections.
![](https://images.seebug.org/1673319150302-w331s)
The port mapping front-end looks like this, it's nothing crazy, so let's have
a look at this request in Burp Suite.
![](https://images.seebug.org/1673319151424-w331s)
We're going to skip reversing the preprocessing from `httpd` to figure out
which parameters are getting passed. We can use our better judgment to make an
educated guess that the `portMappingServer` might be injectable.
![](https://images.seebug.org/1673319152397-w331s)
In the image above we're starting the `telnetd`, which was previously not
running seconds before. Looking at the serial log, we're presented with
familiar error messages.
![](https://images.seebug.org/1673319153267-w331s)
Fun little fact. If we didn't own the hardware and were emulating `httpd` off
of qemu it would be tedious work to find the proper web request and would
require properly reversing the `httpd` binary. However, I actually found
[this](https://www.zybuluo.com/moshangxc/note/825998) enterprise documentation
which perhaps unintentionally leaks the router functions?
![](https://images.seebug.org/1673319154658-w331s)
Regardless, this is nice to have.
* * *
### **Stack Buffer Overflow #1 **
The Tenda AC1200 V-W15Ev2 router is affected by a stack-based buffer overflow
vulnerability that allows an unauthenticated attacker to run arbitrary
commands on the affected system or cause a denial of service. This
vulnerability exists within the "Remote WEB Management" router application,
specifically via the `/goform/module` function `setRemoteWebManage`. The
vulnerability was identified in the `netctrl` binary tracing preprocessing
calls from the `httpd` binary via the vulnerable `remoteIP` parameter. This
vulnerability is the result of mismanaged memory allocation. This
vulnerability exists within the local web interface and the hosted remote web
management console. The vulnerability affects version V15.11.0.10(1576).
### Vulnerability Details
CVE ID: CVE-2022-42058
Access Vector: Remote/Local
Security Risk: Medium
Vulnerability: CWE-121
CVSS Base Score: 5.3
CVSS Vector: CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:H
The entire remote web management gateway piqued my interest early on. I
decided to dig into it thinking that I might find a command injection
vulnerability.
![](https://images.seebug.org/1673319155385-w331s)
Going back to the `httpd` binary, running through `main_init` we discover
`FUN_0043c7b4`, a function containing which contains the
`formSetRemoteWebManage` which sounds like it might relate to "Remote Web
Management", our target.
![](https://images.seebug.org/1673319156216-w331s)
As seen below, in the `formSetSafeWanWebMan` function there exists the
`remoteIp` parameter which is passing whatever is supplied. At first, I
thought that this was a good start, if we can control the `remoteIp` value
there's a potential to obtain command injection.
![](https://images.seebug.org/1673319157062-w331s)
As you can see above, the input isn't getting checked. After which a call to
the function `SetValue` stores the input into `wans.wanwebip`. Let's figure
out how this is all handled in the `netctrl` binary.
After a bit of digging, I discovered that the `advance_get_wan_access_cfg`
function is where the input will be extracted.
![](https://images.seebug.org/1673319157776-w331s)
After this, the `advance_set_wan_access_cfg` will use the input to write the
contents of the `POST` request to the flash. As you can see below the input
from `remoteIp` should be passed into `%s` copying our command to the stack
buffer.
![](https://images.seebug.org/1673319158502-w331s)
So if we attempted to run `telnetd` we'd run the following `POST` request.
![](https://images.seebug.org/1673319159643-w331s)
Looking at the serial logs, it doesn't quite work. After a lot of failed
attempts, I came to the conclusion that there is no real way of escaping the
`[ ]` brackets no matter what type of delimiters you use.
![](https://images.seebug.org/1673319160339-w331s)
At this point, I quickly glanced at the `advance_set_wan_access_cfg` function
and noticed that the stack buffer was only `256 bytes`. If you go back up to
the previous screenshot you'll notice that the function doesn't check the
value of `iVar2` and simply calls it, which copies the POST parameter
`remoteIp` to stack buffer `acStack520`.
![](https://images.seebug.org/1673319160994-w331s)![](https://images.seebug.org/1673319161609-w331s)
We should be able to crash this. I ended up overwriting the buffer with
`~2000` characters the image below is `629` characters and intended to be more
legible to the reader.
![](https://images.seebug.org/1673319162327-w331s)
This causes a crash as seen below, the banner `****** WeLoveLinux ******` is
displayed every time the device boots up, and crashed immediately after.
![](https://images.seebug.org/1673319163121-w331s)![](https://images.seebug.org/1673319163941-w331s)
I've decided to remove my Golang and Python POCs. This is purely a DoS and my
aim isn't to enable and commoditize DoS attacks.
The router crashes and cannot provide services correctly and persistently. The
real question is how realistic obtaining a stable root shell through a
carefully constructed payload would be. Let's find out. I'll be giving this a
go from my QEMU + GDB debugging environment using the firmware I dumped from
the SPI chip on the board.
![](https://images.seebug.org/1673319164665-w331s)
As you can see above, we have a little problem with `cfms_mib_proc_handle`.
When running the program directly, it will output WeLoveLinux, and then it
will crash/not initiate. I'm guessing that it has entered a state of suspended
animation.
I'll use IDA for this next part because if I have to patch it I don't know how
to do it with Ghidra. After a bit of digging to find the position where the
string is used and set a breakpoint, then stepping through until something
weird happens. I'm speculating that the culprit is this function, which is
connected to a service that should return 1 under normal circumstances.
![](https://images.seebug.org/1673319165917-w331s)
When we use the environment simulated by QEMU, it will return 0 if it cannot
connect to the relevant service. We need to patch manually. I did this in IDA
using the [keypatch](https://github.com/keystone-engine/keypatch) plugin to
patch the return value to 1.
![](https://images.seebug.org/1673319166664-w331s)
However, even with this patch set, it's not working. I'm almost 100% sure it
should. Regardless, although upsetting, I decided not to peruse this any
further.
* * *
### **Stack Buffer Overflow #2**
The Tenda AC1200 V-W15Ev2 router is affected by a stack-based buffer overflow
vulnerability that allows an unauthenticated attacker to run arbitrary
commands on the affected system or cause a denial of service. This
vulnerability exists within the Internet Settings, Interface Types
application. It's leveraged via the `/goform/module` function `setNetwork`.
This vulnerability is the result of mismanaged memory allocation. The
vulnerable parameters are `wanUser` and `wanPwd`. This vulnerability exists
within the local web interface and the hosted remote web management console.
The vulnerability affects version V15.11.0.10(1576).
### Vulnerability Details
CVE ID: CVE-2022-42060
Access Vector: Remote/Local
Security Risk: Medium
Vulnerability: CWE-121
CVSS Base Score: 5.3
CVSS Vector: CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:H
I'd like to start by saying that this overflow was quite tricky to understand
given that I'm doing this statically. It would be much easier to debug this
dynamically in my QEMU + GDB lab. As seen in the previous overflow, I wasn't
"properly" patching `ConnectCfm`, so I'm stuck with this suboptimal method.
This stack buffer overflow is part of the Broadband Dialup configuration,
which is part of the WAN device configuration. Point-to-Point Protocol over
Ethernet (PPPoE) is a type of broadband connection and therefore is referenced
as such. Initially, I thought the overflow stemmed from the screenshot below.
From the `setWanPppoe` function, where the application reads user input into
`uVar1` and `uVar2` without doing any proper length checks.
![](https://images.seebug.org/1673319167801-w331s)
This turned out not to be the root cause. looking at the above, a crash could
happen when it takes the address of `local24` and stores our user-provided
JSON string into it, overflowing the `local_20`, `local_1c`, `local_18`, etc.
I presume that `iVar5-9` are all status code checks. However, if
`prod_cfm_set_val` returns `0` on success, this check seems to be irrelevant.
I later came. to the conclusion that this wasn't the case, and that something
else was responsible for the behavior observed further down.
Going along with this initial presumption, all of this information is called
upon when the configs are pushed to flash, which is performed as a result of
the following `POST` request with the below UI as input.
![](https://images.seebug.org/1673319168963-w331s)
I decided to flood the `wanPwd` parameter `Broadband Password` with a bunch of
`AAAA` characters. Which caused a crash.
![](https://images.seebug.org/1673319169640-w331s)
So, the device crashed and rebooted not long after. What could be happening
here? There are at least two ways the overflow could occur. It could occur if
the password ever gets decoded. It could also happen if the program reads the
user input into a variable without proper length checks, and writes it into
memory.
Again, without the ability to attach GDB to the `httpd` process, it's really
hard to reverse. I'd also like to mention that there are confusing behaviors
in the `add_pppoe_config` function that occurs as part of `formGetNetwork`
that may have obstructed my understanding of the entire BOF chain.
My final verdict is as follows, and it will make more sense when watching the
POC video. The overflow process is:
`user input` => `setWanPppoe` => `getPPPoEConnectStatus` => `crash`
A boiled-down explanation of what happens is:
1. JSON user-provided inputs get passed into the WAN config `setWanPppoe` a function that reads user input into `uVar1` and `uVar2` without doing any proper length checks.
2. At some point `getPPPoEConnectStatus` accepts input from **** (presumably from `setWanPppoe` via `prod_cfm_set_val`) which reads the unchecked user input during which it calls `FUN_00447674` (renamed `CredentialCompareFunction`) that stores the user-provided input with `sprintf` to the char buffer.
3. A check on the validity of the credentials is performed at which point the overflow happens.
![](https://images.seebug.org/1673319170446-w331s)
It actually takes a dword parameter WanID (which is a value wanNum=3) and
stores it into `acStack264` (renamed UserPasswordString) this WanID stems from
another function, `FUN_00447828` which is also invoked by the main credential
check of the `getPPPoEConnectStatus` control flow specific to dialup
initiation/integrity checks from a generated
`/etc/ppp/wan%d_ppoe_auth_code`file at startup.
![](https://images.seebug.org/1673319171211-w331s)
Here's a video POC where you can see this in action. This BOF acts as a DOS
and completely debilitates the router once the function is processed ~10
seconds after boot/login.
I won't be including a Golang or Python POC. This is purely a DoS and my aim
isn't to enable and commoditize DoS attacks. Just like in the video, as soon
as the device is logged in, it crashes and reboots in a perpetual never-ending
cycle.
![](https://images.seebug.org/1673319171806-w331s)
What made reversing this so much harder was not having a way of knowing what
library functions were actually being imported. As you can imagine, this makes
reversing all the more confusing.
![](https://images.seebug.org/1673319172787-w331s)
The blog is getting ridiculously long, so I will stop digging for these. If
you follow `prod_cfm_set_val` you're bound to find another ~10 buffer
overflows. It's honestly a clown fiesta.
* * *
## **Stored Cross-Site Scripting**
The Tenda AC1200 V-W15Ev2 router does not perform proper validation on user-
supplied input and is vulnerable to cross-site scripting attacks via the
homepage's connected application hostname field. If a proper authorization
mechanism was implemented, this vulnerability could be leveraged to perform
actions on behalf of another user or the administrator. This vulnerability
exists within the local web interface and the hosted remote web management
console. The vulnerability affects version V15.11.0.10(1576).
### Vulnerability Details
CVE ID: CVE-2022-40846
Access Vector: Network
Security Risk: Medium
Vulnerability: CWE-79
CVSS Base Score: 6.5
CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
On the homepage, an administrator will have the ability to see the top 5
devices that consume the most network resources. They'll also have the ability
to rename these hosts. I attempted the following payload, which failed.
<script>alert(1)</script>
However, if you look at the page's source, it would be reasonable to assume
that you could trigger a valid XSS by prepending `">` to your payload.
![](https://images.seebug.org/1673319173706-w331s)
Since I'm testing in Firefox ill be using `marquee` tags, my payload can be
seen below.
"><marquee onstart=alert(document.cookie)>
In the page source the `title` string is now "handled" properly and closed
leaving our malicious javascript outside of the element.
![](https://images.seebug.org/1673319174448-w331s)
Here's a little Burp Suite visual differentiating the two requests.
![](https://images.seebug.org/1673319175329-w331s)
The QOS's requests will trigger every ~15 seconds. This is to obtain more
accurate statistics of the hosts such as upload/download rates. This will call
upon the hostname perpetually prompting the XSS triggering.
![](https://images.seebug.org/1673319176280-w331s)
As seen below the XSS successfully triggers returning of the session cookies.
![](https://images.seebug.org/1673319177019-w331s)
* * *
## **Stored Cross-Site Scripting**
The Tenda AC1200 V-W15Ev2 router does not perform proper validation on user-
supplied input and is vulnerable to cross-site scripting attacks through the
web filtering group body. If the proper authorization was implemented, this
vulnerability could be leveraged to perform actions on behalf of another user
or the administrator. This vulnerability exists within the local web interface
and the hosted remote web management console. The vulnerability affects
version V15.11.0.10(1576).
### Vulnerability Details
CVE ID: CVE-2022-40844
Access Vector: Network
Security Risk: Medium
Vulnerability: CWE-79
CVSS Base Score: 6.5
CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
There's another stored XSS in the Website filtering functionality. Navigating
to the panel of "Website Filtering" you'll see the following.
![](https://images.seebug.org/1673319178177-w331s)
Clicking on the "URL management" panel will pop-up the following window.
![](https://images.seebug.org/1673319179406-w331s)
Anything injected within the URL body will reflect once its associated group
name is clicked in the panel before. Pretty much any javascript here will
work.
When I test in Firefox I personally prefer using Firefox-specific payloads
such as `<marquee onstart=alert(1)>` for testing.
Since I don't blog a lot, I'll share my favorite payload which leverages the
movie/video content tags which are never really filtered. Some of my goto
payloads can be seen below and all work within this application.
<video controls oncanplay="alert(document.cookie)"><source src="http://mirrors.standaloneinstaller.com/video-sample/lion-sample.mp4"></video>
<img src="test" OnError=prompt(document.cookie)>
<img src=x onerror=alert(document.cookie)>
Once the popup is saved, one way to trigger it is to single-click the element
as seen below or set it to an IP group config.
![](https://images.seebug.org/1673319180857-w331s)![](https://images.seebug.org/1673319181718-w331s)
* * *
## When Will it be Enough?
If I had to make an educated guess, I'd say there are roughly another ~10-15
CVEs laying around in the application. I won't personally be perusing them.
Why?
1. I'd like to spend my time reversing something a little more entertaining.
2. Due to not having been able to establish a channel of communication with the vendor, I don't see the value in further research & potential additional impact on end-users given the nature of my disclosure.
If you want to land some easy CVEs give this application a look. There are
roughly another 5 command injections at large (mainly in the hotel mode, the
guest SSID initiation). There are also a lot of bad `memset` that I presume
can lead to overflows and crashes. I'm not sure how realistic obtaining a
stable root shell through a carefully constructed payload would be. As a
freebie, I'm 99% sure there is a buffer overflow in `GetWanAddress` via the
`wanDns1` and `wanDns2` parameters that copy the user-provided input to the
stack using `sprintf` where `%s` can't limit copy length, so it's possible to
make the stack overflow there as well.
* * *
## Thoughts on Responsible Disclosure
I genuinely try my hardest to adhere to clear and transparent ethical
principles in responsible vulnerability disclosure (RVD). In every case, I
place top priority on the safety and security of end-users. I've written a
handful of blogs that will never see the light of day because of vendor
negligence, which would cause serious real-life end-user impact.
My blog is not intended to be malicious and isn't set out to damage brands or
their reputations. I genuinely make these to educate, inform, and attempt to
make the internet a safer place.
As mentioned above, I try my best to act in a transparent, responsible, and
consistent manner. In order to achieve this, I adhere to 90 days from the
initial contact disclosure deadline. This can be shortened if the vendor
remediates the vulnerability sooner, and lengthened if the vendor requires
additional time to patch the vulnerability.
* I will make at minimum 5 attempts to establish confidential and detailed communication with the vendor. This includes but is not limited to the vendor's "contact us" email address, key employee LinkedIn, Twitter, company telephone, etc,. I also make an effort to provide the vendor with multiple channels of communication (WeChat, LinkedIn, Email, Telegram, WhatsApp, and Telephone) for their overall convenience.
* Once in contact with the vendor's security team or designated company contact, I provide the details of the vulnerabilities. During the first contact, I also discuss my intentions to work along sides them / and help them with remediation & retesting until the vulnerability is patched. Lastly, I discuss my intentions for public disclosure.
* Once everything has been agreed upon, a secure communications channel will be established. Over this channel, all details and materials needed to reproduce the vulnerability are provided.
* I try my best to provide reasonable assistance to the vendor to ensure the understanding of the significance of the discovered vulnerability.
* Once a patch has been released, I'll retest the patch out of good faith.
* * *
With regard to this blog, there were two decisions I could take. Either
disclose or don't. "Who cares, just post it," you may be thinking. I do. And
my ethical dilemma was as follows:
_Option A_ : I don't disclose. As a consequence, there isn't a chance for the
vendor or affected customers to be made aware of the underlying bugs.
Furthermore, an unethical individual may find the issue first and exploit it,
potentially compromising dozens of utilities.
_Option B:_ I publicly disclose the vulnerability, exposing the flaws in the
product, and this blog-generated visibility prompts the vendor to take action
(hopefully). As a consequence, low-life humans try and leverage these
vulnerabilities against (in this case) a very small potential list of affected
customers. Additionally, this blog could also rush affected customers to take
their vulnerable systems down.
_Underlying issue_ : The vendor has no control over the remote devices, other
than publishing firmware updates and alerting customers the vendor can't do
much.
As mentioned multiple times, the goal/aim of my research has always been to
make the internet a safer place. In this case, after evaluating the vendor's
behavior or lack thereof, achieving my goal would require the vendor to change
their current workflow & work towards establishing fluid channels of
communication with researchers. This may require the publication of blogs such
as these. Having weighed the pros and cons, I've decided to publicly disclose.
Hopefully, this will help the vendor go through the change it needs to better
handle researchers and disclosure.
* * *
It should be _blatantly_ obvious that any past, present, or future employer
cannot be held responsible for any personal research and disclosure conducted
by me under my own name.
I find it unfortunate that a small majority of people remain hyper-sensitive
to vulnerabilities and public disclosure. Therefore, I feel the need to add
this disclaimer. I know my heart is in the right place, and that blogs far
more damning than this go up all the time, without the author being judged and
shamed.
**_DISCLAIMER_**
Any misuse derived from the information contained within this blog will be the
exclusive responsibility of the person who carries it out. The owner of the
blog, all present, past, and future employers, ghost.io, and any of its
associates will not be liable for any consequences or damages resulting from
reckless and malicious use. The vendor is solely responsible for its security
errors, and any damage that may be caused to the user's computer systems
(hardware and software), or to the files & documents stored on them, as a
consequence.
Shenzhen Tenda Technology Co., Ltd., failed to establish a clear channel of
communication despite my thorough and consistent attempts. They were given
ample opportunity to respond to my contact attempts. Responsible disclosure
allows vendors between 60 and 120 business days to patch a vulnerability. 90
days have passed since my initial contact attempts.
* * *
## **Summary:**
I hope you liked the blog post. I apologize for getting political toward the
end.
Follow me on [twitter](https://twitter.com/olivier_boschko) I sometimes post
interesting stuff there too. Personally, I'd strongly recommend going on
Amazon, Alibaba, or Aliexpress & buying a bunch of odd IoT devices & tearing
them down. You never know what you will find :)
If you have any ethical dilemmas, hold anything against me, or disapprove of
the contents of this blog, send me a DM, I'd love to see it from your
perspective. Every day is a school day.
**Thank you for reading!**
暂无评论