
Análise técnica e desenvolvimento de exploit para CVE-2021-3156, um buffer overflow baseado em heap no Sudo, incluindo três exploits funcionais para escalonamento de privilégios locais nas principais distribuições Linux.
Qualys Security Advisory
Baron Samedit: Estouro de buffer baseado em heap no Sudo (CVE-2021-3156)
Resumo Análise Exploração Agradecimentos Cronologia
Descobrimos um estouro de buffer baseado em heap no Sudo (https://www.sudo.ws/). Esta vulnerabilidade:
é explorável por qualquer usuário local (usuários normais e usuários do sistema, sudoers e não-sudoers), sem autenticação (ou seja, o invasor não precisa saber a senha do usuário);
foi introduzida em julho de 2011 (commit 8255ed69) e afeta todas as versões legadas de 1.8.2 a 1.8.31p2 e todas as versões estáveis de 1.9.0 a 1.9.5p1, em sua configuração padrão.
Desenvolvemos três exploits diferentes para esta vulnerabilidade e obtivemos privilégios totais de root no Ubuntu 20.04 (Sudo 1.8.31), Debian 10 (Sudo 1.8.27) e Fedora 33 (Sudo 1.9.2). Outros sistemas operacionais e distribuições provavelmente também são exploráveis.
Se o Sudo for executado para rodar um comando no modo "shell" (shell -c comando):
seja através da opção -s, que define a flag MODE_SHELL do Sudo;
ou através da opção -i, que define as flags MODE_SHELL e MODE_LOGIN_SHELL do Sudo;
então, no início da main() do Sudo, parse_args() reescreve argv (linhas 609-617), concatenando todos os argumentos da linha de comando (linhas 587-595) e escapando todos os metacaracteres com barras invertidas (linhas 590-591):
Mais tarde, em sudoers_policy_main(), set_cmnd() concatena os argumentos da linha de comando em um buffer baseado em heap "user_args" (linhas 864-871) e desescapa os metacaracteres (linhas 866-867), "para fins de correspondência e registro do sudoers":
Infelizmente, se um argumento da linha de comando termina com um único caractere de barra invertida, então:
na linha 866, "from[0]" é o caractere de barra invertida, e "from[1]" é o terminador nulo do argumento (ou seja, não é um caractere de espaço);
na linha 867, "from" é incrementado e aponta para o terminador nulo;
na linha 868, o terminador nulo é copiado para o buffer "user_args", e "from" é incrementado novamente e aponta para o primeiro caractere após o terminador nulo (ou seja, fora dos limites do argumento);
o loop "while" nas linhas 865-869 lê e copia caracteres fora dos limites para o buffer "user_args".
Em outras palavras, set_cmnd() é vulnerável a um estouro de buffer baseado em heap, porque os caracteres fora dos limites que são copiados para o buffer "user_args" não foram incluídos em seu tamanho (calculado nas linhas 852-853).
Em teoria, no entanto, nenhum argumento da linha de comando pode terminar com um único caractere de barra invertida: se MODE_SHELL ou MODE_LOGIN_SHELL estiver definido (linha 858, uma condição necessária para atingir o código vulnerável), então MODE_SHELL está definido (linha 571) e parse_args() já escapou todos os metacaracteres, incluindo barras invertidas (ou seja, escapou cada barra invertida com uma segunda barra invertida).
Na prática, no entanto, o código vulnerável em set_cmnd() e o código de escape em parse_args() são cercados por condições ligeiramente diferentes:
versus:
Nossa pergunta, então, é: podemos definir MODE_SHELL e MODE_EDIT ou MODE_CHECK (para atingir o código vulnerável) mas não o MODE_RUN padrão (para evitar o código de escape)?
A resposta, ao que parece, é não: se definirmos MODE_EDIT (opção -e, linha 361) ou MODE_CHECK (opção -l, linhas 423 e 519), então parse_args() remove MODE_SHELL das "valid_flags" (linhas 363 e 424) e sai com um erro se especificarmos uma flag inválida como MODE_SHELL (linhas 532-533):
Mas encontramos uma brecha: se executarmos o Sudo como "sudoedit" em vez de "sudo", então parse_args() define automaticamente MODE_EDIT (linha 270) mas não redefine "valid_flags", e as "valid_flags" incluem MODE_SHELL por padrão (linhas 127 e 249):