
Um exploit confiável + write-up para elevar privilégios a root. (Testado no Ubuntu 22.04)
CVE-2022-37706

Olá pessoal, desta vez vou falar sobre um 0-day recente que encontrei em um dos
principais gerenciadores de janelas do Linux chamado Enlightenment (https://www.enlightenment.org/).
Este 0-day leva qualquer usuário a privilégios de root com muita facilidade e instantaneamente.
O exploit foi testado no Ubuntu 22.04, mas deve funcionar perfeitamente em qualquer distro.
Antes de tudo, Enlightenment é um Window Manager, Compositor e Desktop Mínimo
para Linux (a plataforma principal), BSD e qualquer outro sistema UNIX compatível.
Instalei este gerenciador de janelas para experimentar um pouco com ele. Foi interessante
para mim, pois contém muitas ferramentas e, sinceramente, parece bem elegante.
Depois de instalar o pacote usando apt install enlightenment, examinei os
arquivos e diretórios instalados no meu sistema, muitos módulos e muitos binários
auxiliares, mas o mais interessante é:
➜ enlightenment cd /usr/lib/x86_64-linux-gnu/enlightenment/
➜ enlightenment find . -perm -4000
./utils/enlightenment_ckpasswd
./utils/enlightenment_system
./utils/enlightenment_sys
Ele instala alguns binários SUID, então fiquei pensando se poderia usar um deles
para escalar para root; os binários aparentavam ser seguros e bem codificados.
O binário sobre o qual vamos falar é o enlightenment_sys.
Como em qualquer outro alvo, escolhemos uma estratégia a aplicar após fazer uma pré-avaliação;
veja meu blog aqui se ainda não viu (https://pwn-maher.blogspot.com/2020/10/vulnerability-assessment.html)
Auditei o código com uma abordagem Top-Down.
E como este gerenciador de janelas é open source, o código-fonte estará disponível
para todos esses binários e módulos.
Então a primeira coisa que fiz foi apt source enlightenment para obter todo o código-fonte,
e com um pouco de investigação podemos chegar ao código do binário alvo.
Mas para depurar o binário, carreguei-o no Ghidra para análise e para ter endereços
para definir breakpoints e tudo mais.
Nenhum símbolo foi encontrado na primeira tentativa, mas sim, não precisamos deles, pois acabou
sendo um binário relativamente pequeno.
Surpreendentemente, achei muito agradável olhar o pseudo-código descompilado do
Ghidra do que olhar diretamente o src (evita macros, evita também aquelas verificações
contra o sistema operacional usado para compilar um bloco de código específico).
Então vamos começar a análise.
1- Brincar com o binário.
Vamos executar o arquivo para ver algumas informações sobre nosso alvo:

Executar o binário não gera nenhuma saída:

Passar o argumento --help gerou esta saída:

Desculpe, vou usá-lo para obter root.
Em seguida, vamos apenas usar strace e ver se ele usará syscalls suspeitas, como
execve ou openat:
strace ./enlightenment_sys 2>&1 | grep open

Ele apenas abre bibliotecas conhecidas em locais onde não temos permissão para alterar.
strace ./enlightenment_sys 2>&1 | grep exec

2- Vamos fazer engenharia reversa do binário e depois explorá-lo.
Criei um novo projeto no Ghidra e carreguei esse binário específico.
Como os símbolos não foram encontrados, podemos identificar a função main usando a entry.
O primeiro argumento para a função entry é a própria main.
Renomeei-a para main para referências futuras.
Rolando um pouco para baixo, já consigo ver a função system() sendo usada.
Como pwner, passo dias em desafios para invocar essa função específica x)
Inverti o binário procurando por um bug de corrupção de memória ou algum problema de heap,
, mas na verdade era uma estranha Injeção de Comando.
O binário toma todas as precauções de segurança antes de executar system, mas infelizmente nós
sempre podemos injetar nossa entrada nele.

Ok, agora vamos percorrer o binário do início até nossa função system, tentando
injetar nossa entrada nela.
Primeiro o binário apenas verifica se o primeiro argumento é --help ou -h e mostra aquela
mensagem que vimos antes.

Segundo, ele eleva seus privilégios para root.

Em seguida, ele remove (unset) quase todas as variáveis de ambiente (precauções de segurança) para não
invocar outro binário não pretendido.

Então, se o primeiro argumento que inserimos for "mount", ele entrará neste ramo, verificará alguns
flags fornecidas; essas flags serão definidas na pilha.
Em seguida, verifica se o próximo parâmetro depois de mount é UUID=; não queremos entrar
aqui, então fornecemos "/dev/../tmp/;/tmp/exploit".

Assim passamos pela verificação na linha 410, a verificação strncmp.
Porque se não começar com /dev/, o binário será encerrado.
Em seguida, há uma chamada a stat64 nesse arquivo que fornecemos; note que podemos
criar uma pasta chamada ";" e isso causará a injeção de comando.
Até agora, o exploit já criou este arquivo /dev/../tmp/;/tmp/exploit,
mas este não é o exploit que será chamado.


Estamos nos aproximando de system() agora.
Agora p (ponteiro) é atualizado para o último argumento fornecido ao nosso binário SUID,
/tmp///net.
Por que fornecer /tmp///net se podemos passar /tmp/net?
Vamos burlar esta verificação:
if (((next_next == (char *)0x0) || (next_next[1] == '\0')) || ((long)next_next - (long)p != 6))
Precisávamos que /tmp/net existisse e que /tmp/// tivesse comprimento 6.
Agora, o último stat64 verificará a existência de "/dev/net"
__snprintf_chk(cmd,0x1000,1,0x1000,"/dev%s",next_next);
E ele o encontrará, então passamos por essa última verificação.
Agora ele verificará a disponibilidade de alguns arquivos, mas isso não é importante
neste ponto, porque está tudo pronto e muito perto de disparar Execução Arbitrária de
Comandos.
Agora eina_strbuf_new() apenas inicializará o comando que será passado para
system; o problema aqui é que nós o inserimos assim:
/bin/mount -o noexec,nosuid,utf8,nodev,iocharset=utf8,utf8=0,utf8=1,uid=$(id -u), "/dev/../tmp/;/tmp/exploit" /tmp///net
Mas o binário chama eina_strbuf_append_printf() várias vezes e o comando se torna
/bin/mount -o noexec,nosuid,utf8,nodev,iocharset=utf8,utf8=0,utf8=1,uid=$(id -u), /dev/../tmp/;/tmp/exploit /tmp///net
Note que as aspas duplas são removidas, e poderemos chamar /tmp/exploit
como root.

O binário fez o possível para mitigar qualquer comportamento não intencional, mas como sempre
qualquer coisa pode ser pwned. Eu não esperava explorar isso usando um bug lógico
como este.
Quero que o próximo CVE seja uma corrupção de memória que leve a LPE root.
Divulgação no Twitter: https://twitter.com/maherazz2/status/1569665311707734023