## Anatomy of an IoT Exploit, from Hands-On to RCE

> originally at [https://www.klogixsecurity.com/scorpion-labs-blog/anatomy-of-an-iot-exploit-from-hands-on-to-rce](https://www.klogixsecurity.com/scorpion-labs-blog/anatomy-of-an-iot-exploit-from-hands-on-to-rce)

>> by David E. Baker, published on June 1, 2023

### Foreword
This study concerns the firmware of the Wavlink Wireless-AC1200 Gigabit
router as of June 2020. The vulnerabilities discussed here may or may
not have been patched by the vendor, but this is a case of vulnerability
research that will show the journey as the reward rather than its
conclusion. The author conducted this research before the public
disclosure of the vulnerabilities but after they had been independently
discovered by other researchers and reported to the vendor.

The vendor makes firmware for their products available in the support
section of their website; this is a common way to obtain IoT firmware
and a helpful alternative from extracting it from the device memory. The
firmware is unencrypted, so it can be easily extracted with binwalk.
Dynamic analysis was done with access to a physical exemplar of the
device, and static analysis was done through [Ghidra](https://ghidra-sre.org/).

### tl;dr

The web interface of the Wavlink Wireless-AC1200 Gigabit router has
several vulnerable endpoints that allow for the unrestricted copy of
user-provided data onto the application stack or even directly to the
command line to achieve arbitrary command execution. 

### Hands-On and Local

Initial scans of the device suggest that the only exposed resource was
the administrative web console, accessible to authenticated users on
the LAN interface over HTTP on TCP port 80. The device can offer more
services, but these are not enabled out-of-the-box and by default. As
such, this investigation focuses on the web interface alone.

<p align="center"><img src="./media/image1.png" />
<br><em><sub>An nmap scan of the exemplar device, showing only the web
interface listening.</sub></em></p>

The usual tests — such as the typical command injections found on device
diagnostic panels that allow a command injection in the parameters to a
ping or `traceroute` command — did not yield any immediately interesting
results, which was disappointing. 

<p align="center"><img src="./media/image3.png" />
<br><em><sub>Management options available after authenticating to the
administrative web panel. “USB Storage” can be seen as the second option.
</sub></em></p>

The first (ultimately exploitable) interface that was examined here was
found on the “USB storage” panel, which can be seen as the second option
in the screenshot above. The device has a USB port adjacent to its 802.2
Ethernet plugs, suggesting that it could offer network-attached storage
(NAS) functionality.

<p align="center"><img src="./media/image2.png" />
<br><em><sub>A photograph of the back of the actual exemplar, showing USB
availability.</sub></em></p>

A simple principle in vulnerability research is that the more components
a piece of code interacts with and the more moving parts it has, the more
likely there will be exploitable code lurking nearby. The presence of NAS
capabilities is promising because it indicates the presence of code that
simultaneously interacts with the device’s software layer, hardware layer,
and plugged-in periphery (the USB storage itself). 


The management interface for the USB Storage console is shown in below.
The presence of the “Workgroup” field alone is promising, as this would
suggest that this WiFi router may even attempt to interact over Server
Message Block (SMB) — a big lift for an IoT router. I can’t count the number
of times I’ve seen user-supplied input sent directly to the command line
as an argument to the Unix `smbpasswd` function.

<p align="center"><img src="./media/image4.png" />
<br><em><sub>USB storage options available to authenticated users.
</sub></em></p>

Initial attempts to manipulate these settings failed due to the device
not detecting a USB drive, as seen below.

<p align="center"><img src="./media/image5.png" />
<br><em><sub>Configuration changes to USB Storage options will not be
saved unless an adequately formatted drive is manually plugged into the
device’s USB port.</sub></em></p>

However, once a correctly formatted drive is plugged in, the device
allowed an FTP username and password to be set. As suspected, it placed
this user-provided input on the command line:

<p align="center"><img src="./media/image6.png" />
<br><em><sub>A command injection in the ‘password’ field nets the first
shell access directly to the device’s operating system.</sub></em></p>

Though interesting, this vulnerability is challenging to get
overwhelmingly excited about: it requires not only credentialed access
to the administrative interface of the device but physical access to
the device to manipulate its USB drive. The above exploit allows a
researcher to interact with the individual operating system components
(and exfiltrate them for reverse engineering purposes).

### We Can Do Better

The device was a busy box-centric Linux system with a web interface
powered by Lighttpd. The Common Gateway Interface (CGI) functionality
was provided by individual binaries in `/etc\_ro/lighttpd/www/cgi-bin/`,
with web requests to CGI URIs launching these binaries directly. A quick
look at `nas.cgi` in Ghidra shows the command injection on line 38 below,
which sends a user-supplied password directly to the `do\_system` function
(itself simply a wrapper around the standard libc system call).

<p align="center"><img src="./media/image7.png" />
<br><em><sub>User input is placed on the command line as an argument to
the chpasswd.sh script on Line 38, resulting in a command injection 
and shell access directly to the device’s operating system.</sub></em></p>

Looking through the `/cgi-bin/` directory reduces the task of finding a
more interesting exploit to enumerating the CGI interfaces available to the
user, shown below:.

<p align="center"><img src="./media/image8.png" />
<br><em><sub>An exhaustive list of the CGI binaries made available on the
device, taken live from the shell established by the exploit described in
this section.User input is placed on the command line as an argument to
the chpasswd.sh script on Line 38, resulting in a command injection
and shell access directly to the device’s operating system.</sub></em></p>

Several things stand out on the initial examination. The first important
thing to note is that the CGI binaries often call the `check_valid_user`
function. This method looks to see if the IP address making the request is
stored in a particular temporary file on the file system. Minimal testing
shows that a client’s authentication status is not verified until a call to
this method has been made, so the entirety of the code surface in each CGI
binary before the invocation of this function is accessible unauthenticated.

<p align="center"><img src="./media/image9.png" />
<br><em><sub>The decompilation of adm.cgi showing the check\_valid\_user
method. All of the code prior to this call is executed prior to verifying
the requestor’s authentication status.</sub></em></p>

Another interesting observation is the large number of methods that copy
user-provided input directly onto the stack. For instance, the
decompilation of `wireless.cgi` shows the `NewName` parameter argument taken
from the body of a web request on Lines 14 and 15 followed by an unguarded
`strcpy` of this user input onto the stack on Line 34, shown below:

<p align="center"><img src="./media/image10.png" />
<br><em><sub>Lines 14 and 34 demonstrate an unguarded strcpy of the NewName
parameter from the request body directly onto the program stack. The
decompilation of adm.cgi showing the check\_valid\_user method. All of the
code prior to this call is executed prior to verifying the client's
authentication status.</sub></em></p>

This copy of user input onto the stack alone suggests a memory corruption
exploit, which the following command can verify:
```
curl –XPOST --data "page=SetName&NewName=\`python3 –c 'print(\\"A\\"\*(512)'\`" http://target-ip/cgi-bin/wireless.cgi
```

While promising, a return-oriented attack isn’t optimal here. Using the
shell access to the operating system already obtained, the following command
displays ‘1’, showing that Address Space Layout Randomization (ASLR) is
weakly-enforcing, leaving a ROP chain a 1-in-256 chance of landing on the desired gadget:
```
# cat /proc/sys/kernel/randomize\_va\_space
1
```

Furthermore, the command below shows that the little-endian binaries are
compiled with the stack-guard byte set, so only the last gadget in a chain
can land in a predetermined location within the binary. If a watchdog process
is present to restart the web server after a crash then it’s not out of the
question to repeatedly throw a return-oriented exploit and expect success
eventually, but it’s also possible that there are better bugs lurking
elsewhere in the code.
```
xxd /etc\_ro/lighttpd/www/cgi-bin/wireless.cgi | head -n 10
```

### Better Bugs Are Lurking Elsewhere In The Code

Continuing to look through the CGI functions one will eventually examine
`live\_api.cgi`. This binary makes no call to `check\_valid\_user`, so any
web requests to the URI `/cgi-bin/live\_api.cgi` run this CGI application
unauthenticated. Line 9 in Figure 11 shows that the `QUERY\_STRING`
environment variable, which (according to the Apache CGI specification) is
the portion of the request URI immediately following a question mark and
therefore user-provided, is stored in `pcVar1` and on Line 19 of Figure 11
is sent to the `satellite_status` method. 

<p align="center"><img src="./media/image11.png" />
<br><em><sub>A decompilation of live\_api.cgi showing user input taken from
the URI on Line 9 and being sent to satellite\_status on Line 19. The
decompilation of adm.cgi showing the check\_valid\_user method. All of the
code prior to this call is executed prior to verifying the client's
authentication status.</sub></em></p>

Decompilation of the `satellite_status` method, shown in Figure 12, shows
that the query string itself (now `param\_1`) is parsed for the `page`,
`id`, and `ip` parameters. The `ip` parameter is copied via the `sprintf`
function to a local variable on Line 38 of Figure 12 and, on Line 39, is
passed to the `do\_system` function. The absence of a call to
`check\_user\_auth` indicates that arbitrary client input from an
unauthenticated user in the URI will be placed directly onto the command
line in the `ip` URI parameter, confirmed following:

<p align="center"><img src="./media/image12.png" />
<br><em><sub>A decompilation of the satellite\_status function, which shows
the query string (now param\_1) parsed for the ip parameter on Lines 22 and
23, then to a call to do\_system on lines 38 and 39. </sub></em></p>

<p align="center"><img src="./media/image13.png" />
<br><em><sub>The proof is in the pudding, showing the exploit being used to
effect a remote takeover of the device. </sub></em></p>

### Conclusions

Finding exploits in a fresh-to-the-market IoT device may seem like low-hanging
fruit, as was mentioned at the beginning of this post, but the value of this
investigation was in its journey rather than its destination. 

An understanding of the circumvention of authentication and the location of
the command injection would have been unlikely, or even impossible, without
static analysis of the firmware. Were it not available online, it would have
required dynamic access to the device’s operating system to obtain them, for
which there would have been dim hopes dynamic access to the device. This level
of access itself relied on both physical access to the device, and either
specialized hardware or the guided guess as to the likely places in code that
mistakes might have been made. 

We hope you enjoyed this journey, and that you come back for more.
Happy hacking!

Author:

David Baker, Senior Security Consultant, Testing, K logix

### References

*   [**https://nvd.nist.gov/vuln/detail/CVE-2020-12266**](https://nvd.nist.gov/vuln/detail/CVE-2020-12266) 
*   [**https://nvd.nist.gov/vuln/detail/CVE-2020-15489**](https://nvd.nist.gov/vuln/detail/CVE-2020-15489) 
*   [**https://nvd.nist.gov/vuln/detail/CVE-2020-15490**](https://nvd.nist.gov/vuln/detail/CVE-2020-15490) 
*   [**https://nvd.nist.gov/vuln/detail/CVE-2020-12124**](https://nvd.nist.gov/vuln/detail/CVE-2020-12124%C2%A0)
