
Un report su Dirty Frag, una catena di vulnerabilità di Linux Local Privilege Escalation (LPE) che consente a un utente non privilegiato di ottenere l'accesso root.
Laboratorio di riproduzione e rilevamento di exploit per una catena di escalation dei privilegi locale del kernel Linux.
Stato: VERIFICATO. Ho completato la riproduzione, la verifica fileless e il rilevamento a livello di syscall nel laboratorio (kernel 6.18.9+kali-amd64). Questo documento è un registro di laboratorio. Ogni affermazione di seguito è stata osservata durante l'esecuzione della riproduzione. Gli screenshot e gli artefatti sono acquisizioni reali dalla VM.
Dirty Frag combina due bug logici deterministici nel kernel Linux. Questi bug consentono a un utente locale non privilegiato di sovrascrivere la cache delle pagine di file di sola lettura (ad esempio /usr/bin/su) e ottenere una shell di root:
Entrambe le varianti utilizzano lo stesso schema di base di Dirty Pipe e Copy Fail. La syscall splice(2) inserisce un riferimento a una pagina della cache delle pagine di un file nello slot frag di uno sk_buff lato mittente. L'attaccante può solo leggere questo file. Il codice del kernel lato ricezione esegue quindi uno STORE crittografico in-place sopra quel frag. Questo modifica la cache delle pagine in RAM. Non avviene alcuna scrittura su disco, quindi il monitoraggio dell'integrità dei file (AIDE, Tripwire) non può rilevarlo. L'attacco è deterministico. Non ha finestra di race né panic del kernel in caso di fallimento.
cac2661c53f3 (2017‑01) a f4c50a4034e6 (corretta 2026‑05‑05)2dc334f1a63a (2023‑06) a aa54b1d27fe0 (corretta 2026‑05‑10)CVE-2026-43284 = 8.8 (Alta), CVE-2026-43500 = 7.8 (Alta)Dirty Frag è un LPE fileless. Corrompe la cache delle pagine in memoria, non il file su disco. Il monitoraggio tradizionale dell'integrità dei file non può rilevarlo. Il rilevamento deve avvenire a livello di syscall. La catena utilizza queste primitive di syscall: socket(AF_ALG)/socket(AF_RXRPC), splice e unshare(CLONE_NEWUSER|CLONE_NEWNET). Il percorso ESP crea anche socket UDP AF_INET e netlink. Questo livello è il fulcro dell'ingegneria del rilevamento in questo repository.
Entrambe le varianti utilizzano lo stesso sink: crittografia in-place che STORE byte su una pagina della cache delle pagine che l'attaccante posiziona con splice(2).
UDP_ENCAP_ESPINUDP.vmsplice, poi 16 byte da /usr/bin/su all'offset del file target con splice.splice spinge la pipe nel socket di invio. splice_to_socket() imposta MSG_SPLICE_PAGES. Questo inserisce la pagina della cache delle pagine di /usr/bin/su direttamente in skb->frags[0].xfrm4_udp_encap_rcv, poi xfrm_input, poi esp_input(). Il ramo vulnerabile skip_cow () bypassa . Esegue con la pagina della cache delle pagine sia come sorgente che come destinazione.L'attaccante controlla sia la posizione (offset di splice) che il valore (4 byte). La verifica dell'autenticazione viene eseguita dopo lo store, quindi il livello crittografico non segnala mai la scrittura. Questa variante richiede CAP_NET_ADMIN e utilizza unshare(CLONE_NEWUSER|CLONE_NEWNET).
rxkad_verify_packet_1() esegue una decrittazione pcbc(fcrypt) a blocco singolo direttamente sul frag skb bloccato da splice. Non copia prima i dati. L'attaccante sceglie una chiave di sessione (add_key("rxrpc", …)) in modo che decrypt(ciphertext) sia uguale a desired_plaintext. Questo produce uno STORE di 8 byte. Questa variante prende di mira /etc/passwd. Non richiede namespace utente. Richiede il modulo rxrpc.ko (caricato per impostazione predefinita su Ubuntu).
Il PoC pubblico prende di mira /usr/bin/su. Scrive 48 store ESP di 4 byte ciascuno (192 byte all'offset del file 0). Sostituisce i primi byte della cache delle pagine con un ELF statico di shell di root. Il punto di ingresso dell'ELF esegue setgid(0); setuid(0); setgroups(0,NULL); execve("/bin/sh", …). Un singolo execve("/usr/bin/su") produce quindi una shell di root.
La patch ESP (mainline f4c50a4034e6) contrassegna i frag di pagina che arrivano tramite splice() con il flag SKBFL_SHARED_FRAG. Il ramo skip_cow in esp_input() ora controlla anche questo flag. Gli skb con frag condivisi passano attraverso skb_cow_data() prima della decrittazione AEAD in-place.
La patch RxRPC (mainline aa54b1d27fe0) aggiunge un controllo skb->data_len accanto al controllo skb_cloned() esistente. Il kernel copia uno skb non lineare con dati impaginati prima della decrittazione pcbc(fcrypt) in-place.
Screenshot della configurazione del laboratorio VirtualBox:

.
├── README.md # questo registro di laboratorio
├── detection/
│ ├── dirtyfrag.rules # regole di rilevamento a livello di syscall auditd
│ ├── ausearch_dirtyfrag_observed.txt # output reale del rilevamento dell'exploit
│ ├── sigma/
│ │ └── dirty_frag_exploit.yml # regola Sigma per il rilevamento SIEM
│ └── yara/
│ └── dirty_frag_exploit.yar # regola YARA per il codice PoC su disco/memoria
├── mitigation/
│ └── dirtyfrag_mitigation.sh # blacklist dei moduli + flush della cache delle pagine
├── poc/
│ └── check_vulnerable.py # checker pre-volo non distruttivo
├── reports/
│ └── incident-dirtyfrag.md # playbook di risposta agli incidenti
└── screenshots/ # acquisizioni reali dalla VM di laboratorio
poc/check_vulnerable.py e confermare kernel/moduli/usernstestuser non privilegiato/usr/bin/su corrotto e ripristinatodrop_caches o riavvio)detection/dirtyfrag.rules e validare gli avvisi auditdcat /etc/os-release | head -3
uname -r
Screenshot della verifica della versione del kernel:

Il kernel deve essere più vecchio di le correzioni di maggio 2026 (f4c50a4034e6 / aa54b1d27fe0). Se hai eseguito apt upgrade dopo quella data, l'exploit fallirà. Vedi Risoluzione dei problemi.
Un checker sicuro segnala se la VM è un target plausibile. Controlla il kernel in esecuzione, la presenza dei moduli esp4/esp6/rxrpc e se i namespace utente non privilegiati sono disponibili. La variante ESP richiede questi namespace.
python3 poc/check_vulnerable.py
Verdetto atteso: [*] potenzialmente vulnerabile -- procedere solo in una VM usa e getta.
Un testuser non privilegiato simula un attaccante senza diritti speciali.
sudo useradd -m testuser
sudo passwd testuser
su - testuser
id
Screenshot: id mostra UID 1001. Questo conferma l'accesso non root.

Dall'account non privilegiato:
git clone https://github.com/V4bel/dirtyfrag.git
cd dirtyfrag
gcc -O0 -Wall -o exp exp.c -lutil
./exp
In caso di successo, l'exploit corregge la cache delle pagine di /usr/bin/su. Scrive 48 store ESP di 4 byte ciascuno (un ELF di shell di root di 192 byte all'offset del file 0). Poi rilascia una shell di root interattiva con forkpty.
Screenshot della disponibilità dei moduli, revisione del sorgente e compilazione pulita:

id
whoami
Screenshot: id mostra uid=0(root) dopo ./exp.

sha256sum legge attraverso la cache delle pagine. Mentre la scrittura dell'exploit è attiva, l'hash è diverso dall'originale. Dopo un flush, l'hash torna all'originale. Questa coppia prima/dopo dimostra che il binario su disco non è mai stato toccato.
sha256sum /usr/bin/su # 1) mentre la cache delle pagine è contaminata -> hash DIVERSO
Screenshot: l'hash è diverso dall'hash del pacchetto (RAM avvelenata, disco intatto).

Hash corrotto osservato (cache delle pagine): 3fc29078bd77150b5d6fbb368f632bf02c1a3704816f03fb694ec2e81e77bde4
Dopo lo sfruttamento, la cache delle pagine contiene i dati corrotti. Flush sempre:
echo 3 | sudo tee /proc/sys/vm/drop_caches
# oppure riavvia la VM
Poi verifica il ripristino (come testuser):
sha256sum /usr/bin/su # ora corrisponde all'hash ORIGINALE del pacchetto
su - # richiede di nuovo la password — nessuna root automatica
Screenshot: hash ripristinato (originale su disco).

Hash originale osservato (su disco): 2b4f8770bd35bba5cdc5cfe292bc1d988e92ec1786bf91cf83e0e86fac056eb6
Dopo un riavvio, dpkg -V util-linux non ha restituito nessun output. Il /usr/bin/su su disco corrisponde esattamente al pacchetto, quindi l'hash corrotto 3fc29078… esisteva solo nella cache delle pagine.
drop_cachespotrebbe non eliminare la pagina avvelenata. Questo accade se un processo in esecuzione mantiene ancora la pagina. In tal caso,sha256sumedpkg -Vcontinuano a mostrare il contenuto corrotto. La soluzione affidabile è un riavvio. Nota che anchedpkg -Vlegge attraverso la cache delle pagine. Mentre la pagina è avvelenata, segnala??5??????(l'MD5 è diverso; dimensione, modalità, proprietario e mtime corrispondono tutti). Torna silenzioso dopo il riavvio. Questo dimostra che il file su disco non è mai stato modificato.
Il flush non disabilita l'exploit. Pulisce solo la cache delle pagine avvelenata. Devi disabilitare i moduli separatamente (vedi Mitigazione). La rimozione dei moduli già caricati su un host sfruttato richiede un riavvio.
Dirty Frag è invisibile al monitoraggio dell'integrità dei file. Il rilevamento si concentra sulle primitive di syscall che la catena deve utilizzare.
detection/dirtyfrag.rules)# /etc/audit/rules.d/dirtyfrag.rules
-a always,exit -F arch=b64 -S socket -F a0=38 -F uid!=0 -k dirtyfrag_af_alg
-a always,exit -F arch=b64 -S socket -F a0=33 -F uid!=0 -k dirtyfrag_rxrpc
-a always,exit -F arch=b64 -S splice -F uid!=0 -k dirtyfrag_splice
-a always,exit -F arch=b64 -S unshare -F uid!=0 -k dirtyfrag_namespace
-w /usr/bin/su -p r -k dirtyfrag_suid_read
Nota:
AF_ALG = 38eAF_RXRPC = 33su Linux (vedi/usr/include/bits/socket.h). Le bozze precedenti usavanoa0=21. Quel valore è errato. AF_RXRPC è 33, non 21.
Distribuzione e verifica:
sudo cp detection/dirtyfrag.rules /etc/audit/rules.d/
sudo systemctl restart auditd # oppure: sudo auditctl -D && sudo auditctl -R /etc/audit/rules.d/dirtyfrag.rules
sudo auditctl -l
Avvisi attesi dopo aver rieseguito l'exploit (correla per PID):
ausearch -k dirtyfrag_af_alg
ausearch -k dirtyfrag_rxrpc
ausearch -k dirtyfrag_splice
ausearch -k dirtyfrag_namespace
Tutte e cinque le regole caricate (auditctl -R, res=1 per ogni CONFIG_CHANGE). Le regole hanno catturato la configurazione dei namespace dell'exploit:
time->Wed Aug 5 08:26:37 2026
type=PROCTITLE msg=audit(1785932797.328:592): proctitle="./exp"
type=SYSCALL msg=audit(1785932797.328:592): arch=c000003e syscall=272 success=yes exit=0 a0=50000000 a1=0 a2=0 a3=0 items=0 ppid=5198 pid=5199 auid=1000 uid=1001 gid=1001 euid=1001 suid=1001 fsuid=1001 egid=1001 sgid=1001 fsgid=1001 tty=pts2 ses=2 comm="exp" exe="/home/testuser/dirtyfrag/exp" subj=unconfined key="dirtyfrag_namespace"
Decodificato: syscall=272 (unshare), a0=50000000 equivale a CLONE_NEWUSER | CLONE_NEWNET, uid=1001 (testuser non privilegiato), comm="exp". Il log completo è in detection/ausearch_dirtyfrag_observed.txt.
Screenshot: l'output di ausearch mostra il caricamento delle regole e l'evento dell'exploit.

Il playbook completo è in reports/incident-dirtyfrag.md: sintesi esecutiva, cronologia, IoC, mappatura MITRE ATT&CK, contenimento, eradicazione, ripristino e lezioni apprese.
Mitigazione immediata in fase di esecuzione. Non sopravvive a un riavvio per i moduli già caricati (vedi la nota sotto):
sudo mitigation/dirtyfrag_mitigation.sh
Cosa fa:
/etc/modprobe.d/dirtyfrag.conf per bloccare esp4, esp6 e rxrpc. Include le righe blacklist e alias … off. Un one-liner semplice perde queste righe (l'autocaricamento per alias funzionava comunque).Impatto: disabilitare questi moduli fa smettere di funzionare le VPN IPsec (ESP) e il filesystem AFS (RxRPC).
Correzione permanente: aggiornare a un kernel che contiene le patch upstream. Oppure mettere in blacklist i moduli all'avvio (initcall_blacklist=esp4,esp6,rxrpc).
Questo repository è solo per ricerca e formazione autorizzate sulla sicurezza difensiva.
Vedi LICENSE. Questo laboratorio è per uso educativo nel tuo ambiente isolato solo. Non eseguire il PoC su sistemi che non sei autorizzato a testare.
| Variante | CVE | Sink | Percorso di attivazione | Richiede userns non privilegiato |
|---|
| Scrittura Page-Cache xfrm‑ESP | CVE‑2026‑43284 | crypto_authenc_esn_decrypt() in esp_input() | socket(AF_INET) con UDP‑encap, poi xfrm_input() | Sì (CAP_NET_ADMIN) |
| Scrittura Page-Cache RxRPC | CVE‑2026‑43500 | rxkad_verify_packet_1() (pcbc(fcrypt)) | socket(AF_RXRPC) | No |
!skb_cloned() && !skb_has_frag_list()skb_cow_data()crypto_authenc_esn_decrypt() emette uno STORE dei 32 bit di ordine superiore dell'ESN. Quel valore è replay_esn->seq_hi. L'attaccante sceglie questo valore alla registrazione della SA con l'attributo netlink XFRMA_REPLAY_ESN_VAL.| Componente | Dettagli |
|---|
| Hypervisor | VirtualBox |
| VM target | Kali Linux 2026.1 (snapshot ripristinato a uno stato vulnerabile) |
| Kernel | 6.18.9+kali‑amd64 (più vecchio delle correzioni di maggio 2026) |
| PoC dell'exploit | V4bel/dirtyfrag (singolo file C) |
| Rilevamento | auditd (regole in detection/dirtyfrag.rules) |
| Livello | Cosa vede | Stato |
|---|
| auditd | Socket AF_ALG e AF_RXRPC, splice, unshare, letture SUID | Sì. Distribuito e validato (file di regole detection/dirtyfrag.rules) |
| Sigma | Pattern di syscall per SIEM | Sì. Regola pronta (detection/sigma/dirty_frag_exploit.yml) |
| YARA | Codice PoC su disco o in memoria | Sì. Regola pronta (detection/yara/dirty_frag_exploit.yar) |
| FIM (AIDE/Tripwire) | Modifiche ai file | No. Cieco, perché non avviene alcuna scrittura su disco |
| Sintomo | Causa probabile | Correzione |
|---|
./exp stampa failed / post-write verify failed | Kernel corretto (da maggio 2026 o successivo) | Avviare un kernel più vecchio o ripristinare uno snapshot pre-aggiornamento. Ricontrollare con poc/check_vulnerable.py |
unshare(CLONE_NEWUSER) restituisce EPERM | Namespace utente non privilegiati disabilitati (AppArmor o sysctl) | Controllare sysctl kernel.unprivileged_userns_clone. Su Ubuntu, controllare AppArmor. Kali lo consente per impostazione predefinita |
esp4/esp6/rxrpc non caricati | Moduli non disponibili | sudo modprobe esp4 esp6 rxrpc (RxRPC si carica automaticamente con socket(AF_RXRPC)) |
Binario su "corretto" ma nessuna shell di root | drop_caches già eseguito, o cache delle pagine mantenuta da processi | Rieseguire l'exploit. Se bloccato, riavviare la VM |
| Exploit eseguito, poi ancora root-abile dopo la mitigazione | Cache delle pagine non flushato, o moduli già caricati prima del blacklist | echo 3 > /proc/sys/vm/drop_caches. L'arresto completo richiede un riavvio |