Incident Timeline:

DateEvent
Jan 14, 2021Initial email sent to Adorcam.
Jan 15, 2021Follow up email sent.
Jan 15, 2021Email reply acknowledging the report.
Jan 17, 2021Email indicating "software development" team was reviewing.
Jan 19, 2021Follow up email sent.
Jan 19, 2021Database secured.

Summary:

An ElasticSearch database was discovered that was leaking the customer information related to Adorcam – a power iOS and Android web camera application. The app provides a P2P connection for IP web camera brands such as Zeeporte and Umino. The leaked data includes user email addresses, hashed passwords, wifi network name, and potentially images captured by the web cameras. Thank you to the Adorcam for the help in securing the database once notified.

Background

As I often do, I was browsing BinaryEdge and Shodan when I discovered yet another exposed ElasticSearch database. This database was eventually identified to be owned by Adorcam. The Google Play Store indicates that the Android version of their mobile app has 10,000+ installs. It's unclear how popular the iOS version is. The Zeeporte web camera available on Amazon has 2,300+ reviews.

How much data?

The database was first observed exposes as of Jan 4, 2021 according to Shodan.

  Total Size: 51.0 GB
  Total Docs: 124,169,128

What was exposed?

index name: request_root_cn_2021-01-13 -->

Sensitive details leaked: Client IP, userId, web camera serial number, web camera settings including microphone state, country geo location, SSID / wireless network name.

{
  "content-length": "747",
  "request_ip": "112.XXX.XXX.XXX",
  "x-forwarded-proto": "https",
  "response_status": 200,
  "x-forwarded-port": "443",
  "response_body": "{\"result\":true,\"code\":0,\"msg\":\"success\",\"payload\":null,\"timestamp\":1610519052122}",
  "x-forwarded-for": "112.XXX.XXX.XXX",
  "request_method": "POST",
  "userId": "KDS46100XXXXXXX",
  "request_url": "/v1/device/camera/sync/info",
  "IS_DEV": "DEVICE",
  "authorization": "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJLRFM0NjEwMDIwQjAxXXXXXXXXXXXXXXX",
  "request_host": "10.42.0.1",
  "client_access": "KDS4610020B01824",
  "@timestamp": "2021-01-13T06:24:15.134Z",
  "request_body": "{\"camera_sn\":\"KDS46100XXXXXXX\",\"osd_logo\":1,\"camera_on\":1,\"mic_state\":1,\"speaker_state\":1,\"work_mode\":1,\"clip_length\":10,\"re_trigger\":0,\"time_format\":1,\"zone\":28800,\"countries\":\"Beijing\",\"pir_state\":0,\"pir_sen\":35,\"stay_time\":30,\"vision_state\":1,\"spotlight_bri\":100,\"video_quality\":0,\"scene_mode\":2,\"record_state\":1,\"push_state\":1,\"camera_alarm_state\":0,\"voltage\":-1,\"battery\":48,\"temperature\":0,\"wifi_signal\":-36,\"camera_DTIM\":1000,\"router_beacon\":100,\"router_channel\":8,\"usb_status\":0,\"network_type\":2,\"cur_ssid\":\"MiMesh_9299_XXXX\",\"speaker_vol\":99,\"speaker_alarm_vol\":99,\"wifi_reset_cnt\":0,\"mcu_reset_cnt\":0,\"keep_alive_status\":1,\"snooze_start_time\":82800,\"snooze_end_time\":21600,\"keep_alive_snooze\":[1,2,3,4,5],\"eco_mode\":0,\"cur_link_mode\":0}",
  "host": "rootServer",
  "connection": "close",
  "content-type": "application/json;",
  "userUId": "0",
  "user-agent": "IOT CLINET 1.0"
}

index name: request_adorcam_cn_user -->

Sensitive details leaked: Client IP, server hostname.

Of particular interest – the leaked information included sensitive details regarding their MQTT (a common standard messaging protocol for the Internet of Things (IoT) server. Leaked fields include: hostname, port, password, and username.

  {
  "request_ip": "27.XXX.XXX.XXX",
  "x-forwarded-proto": "https",
  "accept-language": "zh-Hans-US;q=1, en-US;q=0.9, ja-US;q=0.8, de-US;q=0.7, fr-US;q=0.6, es-US;q=0.5",
  "response_status": 200,
  "x-forwarded-port": "443",
  "response_body": "{\"result\":true,\"code\":0,\"msg\":\"success\",\"payload\":{\"mqtt_port\":18XX,\"password\":\"XXXXXXX\",\"user_name\":\"10072XXXXXX\",\"client_id\":\"1007273545666\",\"mqtt_address\":\"tcp://XXXXXXXX\"},\"timestamp\":1610519735172}",
  "x-forwarded-for": "27.XXX.XXX.XXX",
  "request_method": "GET",
  "userId": "XXXX@XXX",
  "request_url": "/v1/app/mqtt/info",
  "accept": "*/*",
  "authorization": "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ4aWVmZW5nQXXXXXXXXXXXXXXX",
  "request_host": "10.42.0.1",
  "client_access": "XXXX@XXXXX",
  "@timestamp": "2021-01-13T06:35:35.190Z",
  "request_body": "{}",
  "host": "XXXXXXXX",
  "connection": "close",
  "lang": "zh",
  "accept-encoding": "gzip, deflate, br",
  "userUId": "10072",
  "user-agent": "iOS/1.0.0/Apple/iPhone X/14.3"
}

index name: request_adorcam_cn_device_2021-01-13 -->

Sensitive details leaked: Client IP, web camera serial number, and server hostname.

{
  "content-length": "178",
  "request_ip": "119.XXX.XXX.XXX",
  "x-forwarded-proto": "https",
  "response_status": 200,
  "x-forwarded-port": "443",
  "response_body": "{\"result\":true,\"code\":0,\"msg\":\"success\",\"payload\":{\"load_log\":1},\"timestamp\":1610576366334}",
  "x-forwarded-for": "119.XXX.XXX.XXX",
  "request_method": "POST",
  "userId": "CUCF110XXXXXXX",
  "request_url": "/v1/device/camera/sync/info",
  "IS_DEV": "DEVICE",
  "accept": "*/*",
  "authorization": "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJDVUNGXXXXXXXXXXXXXXXX,
  "request_host": "10.42.0.1",
  "client_access": "CUCF110220C000D0",
  "@timestamp": "2021-01-13T22:20:39.770Z",
  "request_body": "{\"camera_sn\":\"CUCA81002XXXXXXXX\",\"sync_cmd\":[110],\"voltage\":-1,\"battery\":100,\"wifi_signal\":-32,\"camera_DTIM\":0,\"router_beacon\":0,\"usb_status\":0,\"router_channel\":0,\"power_freq\":50}",
  "host": "XXXXXXXXXX",
  "connection": "close",
  "content-type": "application/json",
  "userUId": "0"
}

index name: service-error-adorcam-2021-01-13 -->

Sensitive details leaked: Client IP, detailed error messages, userId, user email address, email addresses the user has shared web camera access with, web camera serial number, and server name.

{
  "content-length": "225",
  "request_ip": "108.XXX.XXX.XXX",
  "error_msg": "XXXXXXXXXXXXX",
  "x-forwarded-proto": "https",
  "accept-language": "en-US;q=1",
  "x-forwarded-port": "443",
  "x-forwarded-for": "108.XXX.XXX.XXX",
  "request_method": "POST",
  "message": "User does not exist",
  "userId": "[email protected]",
  "request_url": "/v1/app/device/family/to_email",
  "accept": "*/*",
  "authorization": "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJlZGdlbnRyeWpyQGhvdG1haXXXXXXXXXXXXXXX",
  "request_host": "10.42.0.1",
  "client_access": "[email protected]",
  "@timestamp": "2021-01-13T00:31:02.554Z",
  "request_body": "{\"to_user\":\"[email protected]\",\"share_level\":3,\"device_list\":[{\"device_type\":2,\"device_sn\":\"1E002XXXXXXX\",\"device_did\":\"AYIOT-015752-SUUVF\"},{\"device_type\":2,\"device_sn\":\"1E00XXXXXXXX\",\"device_did\":\"AYIOT-015754-KPKKM\"}]}",
  "host": "XXXXXXXXX",
  "connection": "close",
  "content-type": "application/json",
  "lang": "en",
  "accept-encoding": "gzip, deflate, br",
  "userUId": "1342901612282167297",
  "user-agent": "iOS/1.3.0/Apple/iPhone 8 Plus/14.2"
}

index name: request_adorcam_abroad_user_2021-01-13 -->

Sensitive details leaked: Client IP, userId, user email address, mobile device push token, and server name.

{
  "content-length": "140",
  "request_ip": "68.XXX.XXX.XXX",
  "x-forwarded-proto": "https",
  "accept-language": "en-US;q=1",
  "response_status": 200,
  "x-forwarded-port": "443",
  "response_body": "{\"result\":true,\"code\":0,\"msg\":\"success\",\"payload\":null,\"timestamp\":1610496003889}",
  "x-forwarded-for": "68.XXX.XXX.XXX",
  "request_method": "POST",
  "userId": "[email protected]",
  "request_url": "/v1/app/user/push/register",
  "accept": "*/*",
  "authorization": "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJsYXRlZWNldHVybmVyQGdtYWXXXXXXXXXXX",
  "request_host": "10.42.0.1",
  "client_access": "[email protected]",
  "@timestamp": "2021-01-13T00:00:03.979Z",
  "request_body": "{\"release\":1,\"push_token\":\"a2cd10aee62c24fc9998c336XXXXXXXXXXX\",\"method\":\"APNS\",\"app_id\":\"com.adorcam.app.ios\"}",
  "host": "XXXXXXXXX",
  "connection": "close",
  "content-type": "application/json",
  "lang": "en",
  "accept-encoding": "gzip, deflate, br",
  "userUId": "1338176387697029121",
  "user-agent": "iOS/1.3.0/Apple/iPhone XR/14.3"
}

index name: request_adorcam_abroad_device_2021-01-13 -->

Sensitive details leaked: Client IP, userId, web camera serial number, server name, and a link to what appears to be a still image captured by the web camera. I was not able to successfully load one of these images, but it seems like it could have exposed sensitive private images captured by the web camera.

{
  "content-length": "97",
  "request_ip": "73.XXX.XXX.XXX",
  "x-forwarded-proto": "https",
  "response_status": 200,
  "x-forwarded-port": "443",
  "response_body": "{\"result\":true,\"code\":0,\"msg\":\"success\",\"payload\":{\"thumb_url\":\"http://s3-adorcam-us-device.s3-accelerate.amazonaws.com/thumb/20210112/100020B00CDB/XXXXXXXXX.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20210113T031750Z&X-Amz-SignedHeaders=host&X-Amz-Expires=3600&X-Amz-Credential=AKIAI4HLHWKDFDL6WCYXXXXXXXXXXXX%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=273990c1fe6c258b1a1dbd802XXXXXXXXXXX\",\"event_id\":1349193911694594048,\"camera_sn\":\"10002XXXXXXXXX\"},\"timestamp\":1610507870016}",
  "x-forwarded-for": "73.98.209.221",
  "request_method": "POST",
  "userId": "100020B00CDB",
  "request_url": "/v1/device/notify/event",
  "IS_DEV": "DEVICE",
  "authorization": "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMDAwMjBCMDBDREIXXXXXXXXXXXXX",
  "request_host": "10.42.0.1",
  "client_access": "100020B00CDB",
  "@timestamp": "2021-01-13T03:17:50.106Z",
  "request_body": "{\"device_did\":\"AYIOT-017716-EEEED\",\"camera_sn\":\"10002XXXXXXXXX\",\"push\":2,\"timestamp\":1610507869000}",
  "host": "XXXXXXXX",
  "connection": "close",
  "content-type": "application/json;",
  "userUId": "0",
  "user-agent": "IOT CLINET 1.0"
}

index name: adorcam_dev_sync_2021-01-13 -->

Sensitive details leaked: web camera serial number, web camera settings including microphone state, country geo location, SSID / wireless network name.

{
  "spotlight_bri": 100,
  "camera_alarm_state": 0,
  "battery": 83,
  "vision_state": 1,
  "speaker_state": 1,
  "pir_state": 1,
  "push_state": 1,
  "zone": -18000,
  "temperature": 8,
  "cur_ssid": "NewbXXXXXXXX",
  "wifi_reset_cnt": 0,
  "router_beacon": 100,
  "wifi_signal": -36,
  "time_format": 1,
  "camera_on": 1,
  "speaker_vol": 50,
  "response_status": 200,
  "video_quality": 0,
  "record_state": 1,
  "camera_sn": "14002XXXXXXX",
  "camera_DTIM": 1000,
  "osd_logo": 1,
  "countries": "unknown",
  "pir_sen": 80,
  "work_mode": 1,
  "speaker_alarm_vol": 99,
  "voltage": 3932,
  "mcu_reset_cnt": 0,
  "usb_status": 0,
  "@timestamp": "2021-01-13T16:53:48.912Z",
  "clip_length": 10,
  "scene_mode": 2,
  "mic_state": 1,
  "re_trigger": 0,
  "network_type": 2,
  "router_channel": 6
}

Proving this was a production database

Some of the indexes within the database implied that this could be a development database. If that was the case that would indicate the data could be place holder information. To rule  this out I conducted a test to definitively prove that this database was being used for production traffic related to the iOS and Android apps. I signed up. I was able to find my test account within the database, and therefore confirmed this was the live database -- and now a development database as I initially suspected.

This is my sign up information (redacted as needed).

index name: request_adorcam_abroad_user_2021-01-14 -->

{
  "content-length": "140",
  "request_ip": "XXXXX",
  "x-forwarded-proto": "https",
  "accept-language": "en-US;q=1",
  "response_status": 200,
  "x-forwarded-port": "443",
  "response_body": "{\"result\":true,\"code\":0,\"msg\":\"success\",\"payload\":null,\"timestamp\":1610650476850}",
  "x-forwarded-for": "XXXXXX",
  "request_method": "POST",
  "userId": "[email protected]",
  "request_url": "/v1/app/user/push/register",
  "accept": "*/*",
  "authorization": "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "request_host": "10.42.0.1",
  "client_access": "[email protected]",
  "@timestamp": "2021-01-14T18:54:36.937Z",
  "request_body": "{\"release\":1,\"push_token\":\"aef6c5efbe89ceXXXXXXXXXXXXXXXXXXXXXX\",\"method\":\"APNS\",\"app_id\":\"com.adorcam.app.ios\"}",
  "host": "XXXXXXXXX",
  "connection": "close",
  "content-type": "application/json",
  "lang": "en",
  "accept-encoding": "gzip, deflate, br",
  "userUId": "13497920XXXXXXXXXXX",
  "user-agent": "iOS/1.3.0/Apple/iPhone 12 Pro/14.3"
}

What is the risk of this data?

The first risk: social engineering / phishing.

The information leaked in this database could easily be used for a very convincing social engineering attack. Someone could approach any of the customers in this database (their email was included in plaintext), and say something like:

Hi $ACCOUNT_EMAIL_ADDRESS

This is Bob from Adorcam customer support. 

We noticed your $CAMERA_TYPE with $CAMERA_SERIAL_NUMBER seems to be malfunctioning on your wireless network named $WIRELESS_SSID_NAME.

Please login at $PHISHING_URL to resolve this issue.

Thanks,
Adorcam customer support

The malicious actor would have plenty of details to establish trust and credibility with the victim of the phishing attack. The attacker also had geographic information to launch a targeted attack in the user's native language.

The second risk: the exposed MQTT server

The full credentials, hostname, and port for the MQTT server were exposed in the data. This means a malicious actor could have connected and 1) downloaded all stored data, 2) deleted the data, or 3) modified the data. NOTE: I did not attempt to confirm if the leaked credentials would allow me to connect to the database.

Conclusion

One interesting detail about this database was that the user information was split between Chinese users and "abroad" users. For example:  request_adorcam_cn_user  vs. such as request_adorcam_abroad_user.  Adorcam almost certainly has breach disclosure obligations based on what appeared to be a global user base. If they had users within the EU they absolutely have an obligation. Thank you to Adorcam for their efforts to secure the database once notified.