
Análisis técnico y desarrollo de exploits para CVE-2021-3156, un desbordamiento de búfer basado en montón en Sudo, incluyendo tres exploits funcionales para escalada local de privilegios en las principales distribuciones de Linux.
Aviso de seguridad de Qualys
Baron Samedit: Desbordamiento de búfer basado en montón en Sudo (CVE-2021-3156)
Resumen Análisis Explotación Agradecimientos Cronología
Descubrimos un desbordamiento de búfer basado en montón en Sudo (https://www.sudo.ws/). Esta vulnerabilidad:
es explotable por cualquier usuario local (usuarios normales y usuarios del sistema, sudoers y no sudoers), sin autenticación (es decir, el atacante no necesita conocer la contraseña del usuario);
se introdujo en julio de 2011 (commit 8255ed69) y afecta a todas las versiones heredadas de 1.8.2 a 1.8.31p2 y a todas las versiones estables de 1.9.0 a 1.9.5p1, en su configuración predeterminada.
Desarrollamos tres exploits diferentes para esta vulnerabilidad y obtuvimos privilegios de root completos en Ubuntu 20.04 (Sudo 1.8.31), Debian 10 (Sudo 1.8.27) y Fedora 33 (Sudo 1.9.2). Otros sistemas operativos y distribuciones probablemente también sean explotables.
Si se ejecuta Sudo para lanzar un comando en modo "shell" (shell -c comando):
ya sea mediante la opción -s, que establece el flag MODE_SHELL de Sudo;
o mediante la opción -i, que establece los flags MODE_SHELL y MODE_LOGIN_SHELL de Sudo;
entonces, al comienzo de main() de Sudo, parse_args() reescribe argv (líneas 609-617), concatenando todos los argumentos de la línea de comandos (líneas 587-595) y escapando todos los metacaracteres con barras invertidas (líneas 590-591):
Más adelante, en sudoers_policy_main(), set_cmnd() concatena los argumentos de la línea de comandos en un búfer basado en montón "user_args" (líneas 864-871) y desescapa los metacaracteres (líneas 866-867), "para fines de coincidencia y registro de sudoers":
Desafortunadamente, si un argumento de la línea de comandos termina con un solo carácter de barra invertida, entonces:
en la línea 866, "from[0]" es el carácter de barra invertida, y "from[1]" es el terminador nulo del argumento (es decir, no es un carácter de espacio);
en la línea 867, "from" se incrementa y apunta al terminador nulo;
en la línea 868, el terminador nulo se copia al búfer "user_args", y "from" se incrementa de nuevo y apunta al primer carácter después del terminador nulo (es decir, fuera de los límites del argumento);
el bucle "while" en las líneas 865-869 lee y copia caracteres fuera de los límites al búfer "user_args".
En otras palabras, set_cmnd() es vulnerable a un desbordamiento de búfer basado en montón, porque los caracteres fuera de los límites que se copian al búfer "user_args" no se incluyeron en su tamaño (calculado en las líneas 852-853).
En teoría, sin embargo, ningún argumento de la línea de comandos puede terminar con un solo carácter de barra invertida: si MODE_SHELL o MODE_LOGIN_SHELL está establecido (línea 858, una condición necesaria para alcanzar el código vulnerable), entonces MODE_SHELL se establece (línea 571) y parse_args() ya escapó todos los metacaracteres, incluidas las barras invertidas (es decir, escapó cada barra invertida 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:
versus:
Nuestra pregunta, entonces, es: ¿podemos establecer MODE_SHELL y ya sea MODE_EDIT o MODE_CHECK (para alcanzar el código vulnerable) pero no el MODE_RUN predeterminado (para evitar el código de escape)?
La respuesta, al parecer, es no: si establecemos MODE_EDIT (opción -e, línea 361) o MODE_CHECK (opción -l, líneas 423 y 519), entonces parse_args() elimina MODE_SHELL de los "valid_flags" (líneas 363 y 424) y sale con un error si especificamos un flag no válido como MODE_SHELL (líneas 532-533):
Pero encontramos una escapatoria: si ejecutamos Sudo como "sudoedit" en lugar de "sudo", entonces parse_args() establece automáticamente MODE_EDIT (línea 270) pero no restablece los "valid_flags", y los "valid_flags" incluyen MODE_SHELL de forma predeterminada (líneas 127 y 249):