
PoC
CVE-2022-37706

Olá pessoal, desta vez vou falar sobre uma 0-day recente que encontrei em um dos
principais gerenciadores de janelas do Linux chamado Enlightenment (https://www.enlightenment.org/).
Essa 0-day vai levar qualquer usuário a privilégios de root de forma muito fácil e instantânea.
O exploit foi testado no Ubuntu 22.04, mas deve funcionar bem em qualquer distro.
Primeiramente, Enlightenment é um Gerenciador de Janelas, 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 muito 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 que é 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 eu poderia usar um deles
para escalar para root; os binários pareciam todos 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 usando 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 mais agradável olhar o pseudo-código descompilado do
Ghidra do que olhar diretamente o src (evitando macros e também aquelas verificações
do sistema operacional usado para compilar um bloco específico de código).
Então vamos começar a análise.
1- Brincar com o binário.
Vamos executar o arquivo para ver algumas informações sobre o nosso alvo:
Screenshot
Executar o binário não dá nenhuma saída:
Screenshot
Passar o argumento --help deu esta saída:
Screenshot
Desculpe, vou usá-lo para obter root.
Em seguida, vamos apenas usar strace e ver se ele usará alguma syscall suspeita, como
execve ou openat:
strace ./enlightenment_sys 2>&1 | grep open
Screenshot
Ele apenas abre bibliotecas conhecidas em locais onde não temos permissão para mexer.
strace ./enlightenment_sys 2>&1 | grep exec
Screenshot
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 entry.
O primeiro argumento para a função entry é a própria main.
Renomeei 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)
Fiz engenharia reversa do binário procurando por um bug de corrupção de memória ou problemas 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
sempre podemos injetar nossa entrada lá.
Screenshot
Ok, agora vamos percorrer o binário do início até a 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.
Screenshot
Segundo, ele eleva seus privilégios para root.
Screenshot
Em seguida, ele desativa (unset) quase todas as variáveis de ambiente (precauções de segurança) para não
invocar outro binário não intencional.
Screenshot
Então, se o primeiro argumento que inserimos for "mount", ele entrará nesse ramo, verificará algumas
flags fornecidas; essas flags serão definidas na pilha.
Em seguida, ele verifica se o próximo parâmetro depois de mount é UUID=. Não queremos entrar
aqui, então fornecemos "/dev/../tmp/;/tmp/exploit".
Screenshot
Assim, passamos na verificação na linha 410, a verificação do strncmp.
Porque, se não começar com /dev/, o binário encerra.
Depois, 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 esse arquivo /dev/../tmp/;/tmp/exploit,
mas não é esse exploit que será chamado.
Screenshot
Screenshot
Estamos chegando mais perto de system() agora.
Agora p (ponteiro) é atualizado para o último argumento passado ao nosso binário SUID,
/tmp///net.
Por que fornecer /tmp///net quando podemos passar /tmp/net?
Vamos contornar 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 encontrará, então passamos nessa ú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 acionar a 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 como:
/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
Observe que as aspas duplas são removidas, e poderemos chamar /tmp/exploit
como root.
Screenshot
O binário fez o seu melhor para mitigar qualquer comportamento não intencional, mas como sempre,
qualquer coisa pode ser comprometida. Eu não esperava explorar isso usando um bug lógico
como este.
Quero que a próxima CVE seja uma corrupção de memória que leve a LPE root.