How I hacked smart lights: the story behind CVE-2022-47758
Introduction
In this blogpost, we take a closer look at our research regarding CVE-2022-47758: a critical vulnerability impacting a very large number of Internet of Things smart devices. We could leverage this vulnerability in the lamp's firmware for unauthenticated remote code execution on the entire device with the highest privileges and hence abuse it for information gathering (and for haunting someone in their own house). Additionally, we could pivot to the management devices using a vulnerability in the smart lamps' desktop management software (CVE-2022-46640). To make matters more interesting: the vulnerable traffic flowed through an encrypted outbound connection which means that it typically isn't blocked by a firewall. This blogpost serves as a cautionary tale for both vendors and consumers, highlighting the importance of IoT security. Join us as we dive into the technical details and lessons learned from our research.
Proof of Concept exploit
The goal of our proof of concept (PoC) exploit is proving that we can remotely execute code on our own smart lamps. For the PoC exploit we're redirecting local traffic to the vendors MQTT(S) broker to our own machine via malicious DNS records. In practice, an attacker could perform this redirect by committing either a rogue DHCP server attack, hacking a router, hacking a DNS server, et cetera. Once we have control over the MQTT traffic, we send a debugging command to a debugging endpoint on our smart lamp. Finally, we activate a persistent OpenSSH server in order to easily access the lamp.
Methodology
We use the following methodology in this blogpost:
- *.acme.org - the vendor domain names
- mqtt.acme.org - the vendor MQTT broker domain name
- 192.168.128.0/24 - our controlled network environment
- 192.168.128.10 - our attacker machine
- 192.168.128.20 - our vulnerability smart device
Spoofing DNS
In order to spoof DNS we need to set up a rogue DHCP server. The Dynamic Host Configuration Protocol (DHCP) is primarily used by network administrators to set the private ip addreses of devices on the network dynamically. However, DHCP packets also have a few more interesting parameters: domain name servers IP addresses, hostnames, and even gateway IP addresses. In order to MitM MQTT traffic to mqtt.acme.org
, we are setting the domain name of the smart lamp by creating a malicious DHCP offer - using our rogue DHCP server - which sets the domain name server to 192.168.128.10
.
By installing isc-dhcp-server
on our Linux install and configuring it to run maliciously on our local network environment (192.168.128.0/24
). We want to make the smart lamp use our own DNS resolver over at 192.168.128.10
. The configuration we use is as following:
In order to change the IP address to which mqtt.acme.org
points, we need to setup our own DNS resolver by installing bind9
and setting a custom DNS record for the zonemqtt.acme.org
which points to our own MQTT broker:
Setting up a malicious MQTT broker
Since our traffic to mqtt.acme.org
now points to our own IP address (192.168.128.10
), we can eavesdrop the traffic. However, in order to interact with this traffic, we need to set an MQTT broker up on 192.168.128.10
. We do this so we can publish to a custom debugging MQTT channel devoted to debugging (custom made by Acme). By publishing on this MQTT channel, we can execute commands. It's important that the server listens on port 443, has TLS encryption and allows anonymous logins. Hence, if the smart lamp tries to connect to mqtts://nobody@mqtt.acme.org:443
it should succeed. We configured it by using the following configuration:
As you might have noticed, we are dealing with MQTTS. Like HTTPS, the S in MQTTS stands for Secure. In order to make such a protocol secure, we need to create TLS certifications so we can encrypt the MQTT trafifc coming from our own MQTT broker. We can create such TLS certifications by running the following command:
Performing the exploit
Now we have our infrastructure set up, we need to reboot the lamp such that it will trigger a DHCP discover request as part of the Discover Offer Request Accept (DORA) sequence. The next part of the DORA sequence would be 'Offer', where the server offers a new IP address (and our domain name server IP address) to our smart lamp. That offer will set the lamps DNS records of mqtt.acme.org
to 192.168.128.10
.
We can confirm that the vulnerable smart lamp is using our own MQTT broker by inspecting the local traffic using Wireshark on 192.168.128.10
. After the victim device has connected to our server, we want to activate an OpenSSH server. In order to do this, we create the /acme/ssh_enabled
file which enables persistent SSH access after the device reboots. We could probably do it without rebooting, be it would be a lot more unnecessary effort. After that, we stop the debugging of the touch command, and instead debug passwd -d root
which deletes the password for the root user. This is convenient, because the default password is unknown and this way we can set the password without a TTY. Additionally the SSH server allows passwordless logins. In order to pull it off, we execute the following commands using mosquitto_pub
(publishes messages to the Mosquitto broker):
Once we started the OpenSSH server on the smart lamp, we can log into our smart lamp by simply executing ssh root@192.168.128.20
.
$ ssh root@192.168.128.20
root@192.168.128.20:~ $ uname -a
Linux AcmeProduct-MAC 4.14.195 #0 Sun Sep 6 16:19:39 2020 mips GNU/Linux
Analyzing the smart device firmware
Since we have access to the firmware, we can analyze the firmware by extracting it using Binwalk - a tool for analyzing and extracting firmware. By running it with the -e
(--extract
) parameter, we can extract the firmware partitions. In our case, we can see that we have 3 partitions: a bootloader, a kernel, and an OpenWRT install (interestingly enough).
Enumerating the OpenWRT installation
The output of Binexp is a SquashFS filesystem instance which got carved out of the extracted partition. SquashFS performs heavy compressions and hence it probably was used by the smart lamp developers because it saves storage costs. Since SquashFS doesn't have different layers such as OverlayFS, we do not have any hassle regarding fixing the FS.
One of the first things we did was verifying with what OS we were working and checking which users existed on the device. After we established that the lamp was running OpenWRT - a router OS interestingly enough - and we couldn't find any custom users in /etc/passwd
, we decided to look into the next interesting directory: /acme_config/
.
We started searching in /acme_config/
for interesting keywords such as grep -iRPe '(ssh)|(mqtt)|(ftp)|(api)'
to find possible exposed services as an attack surface. As we researched the binaries containing the specified keywords, we found out that a particular binary called ColorCC.bin
contained the entire smart lamp API accessible via HTTP (built using the OpenAPI C++ SDK). We tried searching for memory corruption bugs for easy RCE but could not find any. Next, a binary called cloud_daemon
caught our attention because it contained an MQTT client...
Investigating the MQTT handler
In order to grasp the internal logic of the cloud_daemon
, we can open it in Ghidra. Ghidra is a software reverse engineering suite developed by the National Security Agency (NSA). We can use Ghidra to decompile Assembly instructions (the raw instructions that go into the CPU) into normal C, which is relatively readable by code monkeys like us.
We can see that cloud_pipe_start()
(libcloudpipe.so
) is called in main()
, which registers several callback functions: cloud_pipe_start(..., ..., register_channels, on_disconnect_cb, on_tick_cb, ...)
. The function register_channels
is a wrapper for registering handlers for the MQTT channels discussed above.
The most interesting handler function sounds like debug
, which handles messages on the channel /acme/device/serialno/exec/server
. This function handles debug requests: it can execute a binary (debug a process) based on the MQTT requests parameters, or kill the process (stop the debugging). In order to start debugging a binary, we can publish the following the the server exec channel: debug /bin/echo "Hello World!"
, of which "Hello World!" should be nicely returned in an MQTT message on the channel /acme/device/serialno/exec/client
. When we want to execute another binary or generally stop debugging, we can simply issue a stop
command.
So far, I hope that the following part of the MQTT payload in the PoC exploit makes sense:
Investigating the communication protocol
Now we have a primitive for our exploit: a debugging endpoint which could be abused if we could send messages on the /acme/device/serialno/exec/server
channel of the MQTT broker. Mind you, it would cause CHAOS if this MQTT broker could be hacked to allow an attacker to send messages to all devices connected to the MQTT broker. Since we don't want to try to hack the vendor since it would be cybercrime, we aren't going to test the official MQTT broker, so we tried to find ways to MitM the traffic going to mqtt.acme.org
, however we couldn't succeed since it used TLS... But - we asked ourselves - what if the TLS configuration was insecure? E.g. an insecure version?
In order to find the TLS configuration, we dug into the functions that were called to setup the MQTT client: cloud_pipe_subscribe
and cloud_pipe_start
. By running a simple grep -iRe 'cloud_pipe_subscribe'
query again, we can see that our function is originating from /acme_config/acme_programs/libcloudpipe.so
.
An interesting part of the cloud_pipe_start()
function is the subsystem where a TLS network connection gets initiated by ConnectNetwork()
and the MQTTClient gets initiated by MQTTClient()
. We can find the TLS configuration in ConnectNetwork()
and I quickly identified the used TLS library as mbedtls. Whilst searching for documentation of the used functions in the mbedtls library, I found out that the parameter MBEDTLS_SSL_VERIFY_NONE
gets passed to the configuration function mbedtls_ssl_conf_authmode
. This means that TLS certifications are not validated...
We have the final piece.
Creating a Proof of Concept exploit
The primitives in our exploit are complete: we have a dangerous debugging endpoint listening to a server which can be eavesdropped. Now it's a matter of performing a Man-in-the-Middle (MitM) attack on the MQTT broker and creating a payload to send.
We have plenty of options to MitM network traffic when the TLS certifications aren't verified, but our favorite approach is using a rogue DHCP server to serve fake DNS records. We picked the isc-dhcp-server
DHCP service because it works on Linux and because it's very customizable. We're using option domain-name-server
to set the DNS server to 192.168.128.10
on the smart lamp. This means that if the lamp requests mqtt.acme.org
, it will be resolved by our own DNS resolver over at 192.168.128.10
We used bind9
as a DNS resolver in order to create fake DNS zones/records. We created a basic type A (IPv4) DNS record for mqtt.acme.org
which redirects to our own MQTT broker 192.168.128.10
. Usually these kind of attacks are prevented by verifying the TLS certifications of the broker as a client, but the smart lamp did not perform those verification checks.
For the final serice we needed an MQTT broker, for which we chose mosquitto
. We didn't configure it at all and just made sure that it was possible to publish and subscribe to any MQTT channels. However, we had to make sure that our service was running on port 443 (which is typically used for HTTPS), that it supported TLS, and that anonymous logins were allowed (anonymous login means that any username/password is allowed to login).
Now we have our entire infrastructure up and running, we need to send the payload commands to our own MQTT broker. We can easily use the mosquitto_pub
utility for this to publish our own messages to specific channels. Additionally, we can use the mosquitto_sub
utility for subscribing to other channels so that we can receive stdout from the smart lamp. In order to easily get our very own OpenSSH server we need to create a file called /acme_config/ssh_enabled
and reboot. However, root
is the only user with a default shell (/bin/ash
) but we don't know its password.
We can overwrite the root password using passwd -d
which resets the password to be empty, and the OpenSSH will gladly accept that. This means that we can essentially start an OpenSSH server using touch /acme_config/ssh_enabled && passwd -d && reboot
. However, in practice our commands get executed using execv(char* filepath, char** argv)
. This means that we need to execute the commands seperately with the full path. Hence, our payload is as follows:
$ mosquitto_pub -L mqtts://127.0.0.1:443/acme/device/serialno/exec/server -m "debug /bin/touch /acme/ssh_enabled" --insecure --cafile /etc/mosquitto/certs/ca.crt
$ mosquitto_pub -L mqtts://127.0.0.1:443/acme/device/serialno/exec/server -m "stop" --insecure --cafile /etc/mosquitto/certs/ca.crt
$ mosquitto_pub -L mqtts://127.0.0.1:443/acme/device/serialno/exec/server -m "debug /bin/passwd -d root" --insecure --cafile /etc/mosquitto/certs/ca.crt
$ mosquitto_pub -L mqtts://127.0.0.1:443/acme/device/serialno/exec/server -m "stop" --insecure --cafile /etc/mosquitto/certs/ca.crt
$ mosquitto_pub -L mqtts://127.0.0.1:443/acme/device/serialno/exec/server -m "debug /sbin/reboot" --insecure --cafile /etc/mosquitto/certs/ca.crt
When we execute this, we start the OpenSSH server and we can log in as root:
$ ssh root@192.168.128.20
root@192.168.128.20:~$ whoami
root
Conclusion
As we have discovered in this article, a critical vulnerability was found in many, many IoT smart lighting devices, allowing attackers to gain control over the entire device and access sensitive information. This serves as a reminder of the importance of IoT security for both vendors and consumers.
As consumers, we can follow these best practices to enhance the security of our home network:
- Keep devices' software up-to-date to prevent vulnerabilities from being exploited.
- Keep smart devices on a separate sub-network to reduce privacy concerns.
- Use long passwords (even pass-sentences) and two-factor authentication where possible.
- Disable unused or unnecessary services and ports on devices.
As developers, we can implement the following best practices to ensure the security of our IoT devices:
- Conduct thorough security assessments and penetration testing to identify and fix vulnerabilities before deploying devices.
- Implement encryption and authentication mechanisms to secure data transmitted between the device and the server.
- Use secure coding practices and avoid insecure software libraries.
- Regularly update and patch devices to fix security vulnerabilities (and do it fast :-) ).
By following these best practices, we can reduce the risk of security breaches and ensure the safety and security of our connected devices and home networks.
Furthermore, the vulnerabilities in said smart lamps were patched by the vendor in early January 2023, about a month after coordinated vulnerability disclosure. The vendor gave us explicit permission to publish this blogpost - under the agreement we wouldn't mention the vendors name nor product name - and gave us permission to publish CVE-2022-47758
.
We hope this blogpost has been as interesting to read for you as it was for us to write, and thank you for taking the time to read this blogpost.
Notselwyn, March 2023