黑客如何突破ClaudeCowork的沙箱“越狱”至Mac电脑的?

admin 2026-08-04 08:01:58 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 攻击者利用ClaudeCowork沙箱的Linux虚拟机,通过unshare创建用户命名空间获取capability,利用AFNETLINK触发内核加载actpedit模块,利用CVE-2026-46331页缓存投毒篡改只读文件,最终通过coworkd执行被投毒代码获得VMroot权限并逃逸至宿主机Mac。建议关闭非特权用户命名空间和采用seccomp白名单。 综合评分: 95 文章分类: 漏洞分析,渗透测试,红队,内网渗透,安全建设


cover_image

黑客如何突破Claude Cowork的沙箱“越狱”至Mac电脑的?

云鼎实验室

2026年7月28日 14:38 广东

在小说阅读器读本章

去阅读

以下文章来源于腾讯安全 ,作者云鼎实验室

腾讯安全 .

致力于成为产业数字化升级的安全战略官,守护政府及企业的数据、系统、业务安全,为产业数字化升级保驾护航

摘要:Claude Cowork 是 Anthropic 为 Claude 智能体提供的沙箱执行环境,运行在 macOS 宿主机的 Apple Virtualization 框架之上。本文详细拆解一条始于非特权 Linux 会话用户、终于宿主机 Mac 文件系统读写的完整攻击链。原始研究将攻击过程划分为六个阶段,本文归纳为四个核心攻击步骤:

1.unshare(CLONE_NEWUSER|CLONE_NEWNET)

创建用户+网络命名空间,获取全套 Linux Capability

2.AF_NETLINK socket →request_module() → kmod 全局模块加载

将 act_pedit.ko 加载到全局内核

3.CVE-2026-46331 页缓存投毒

通过 sendfile 零拷贝 + pedit 绕过 COW,篡改目标二进制文件的页缓存副本

4.coworkd 重新执行被投毒代码

获得 VM 完整 root 权限,通过 virtiofs-root 读写宿主机 Mac 登录用户可访问的 任意文件

沙箱原理概览

根据 Accomplish 团队的分析,Claude Cowork 在 macOS 上使用 Apple Virtualization 框架(VZVirtualMachine)创建 Linux 虚拟机,将 AI 智能体的代码执行隔离在 VM 内部。每个用户会话对应一个独立的一次性非特权 Linux 用户,受 seccomp 过滤器约束(deny-list(默认允许)模式,仅拦截少数特定调用)。文件夹共享由名为coworkd 的 root 守护进程代理为挂载点,通过 virtio-fs 实现,用户选定的本地目录在 VM 内可见。

宏观架构如下:

Claude Cowork 沙箱架构

关键信息:

从架构图可以看出,Cowork VM 内存在 coworkd 守护进程(root 权限运行、未加固)和 /mnt/.virtiofs-root/ 共享挂载点这两个攻击面。只要攻击者在 VM 内获得 root 权限,就能通过 virtiofs-root 直接读写宿主机 Mac 登陆用户可访问 的任意文件——整个宿主机 / 以读写方式共享进 VM,这正是原研究 “SharedRoot” 命名的由来。

攻击链总览

原始研究将完整攻击过程划分为六个阶段(起始状态 → 获取 capabilities → 触达漏洞代码 → 获取写原语 → 提升为 VM root → 离开虚拟机),本文将其归纳为四个核心攻击步骤,全部由非特权会话用户发起,无需任何初始 root 权限:

四步完整攻击链

为什么这条攻击链具有普遍性?

它不依赖任何特定网络拓扑或物理网卡。即使攻击者被隔离在私有网络命名空间中(CLONE_NEWNET),只要 AF_NETLINK socket 不被 seccomp 拦截,就能通过 request_module() → kmod 路径触发全局内核自动加载任意已存在的内核模块——此 kmod 逃逸步骤不需要任何网络设备。后续页缓存投毒仅需 CLONE_NEWNET 自动创建的 lo 接口,无需物理网卡或外部网络连通。

逐步深度拆解

// 3.1 第 1 步:unshare 创建用户+网络命名空间

攻击起点是 Claude Cowork 会话中的非特权 Linux 用户。该用户无root权限,受 seccomp 过滤器约束,从传统威胁模型来看是完全受限的进程。

CLONE_NEWUSER 是所有 Linux 命名空间类型中唯一不需要 CAP_SYS_ADMIN 就能由非特权用户创建的类型。当调用 unshare(CLONE_NEWUSER | CLONE_NEWNET) 时:

无需任何特权即可执行

unshare –user –map-root-user –net /bin/bash

子命名空间内部视角

capsh –print | grep Current:

Current: =ep    # 全套 Linux Capability 在位

VM初始命名空间视角: 该进程仍是普通用户

cat /proc/PID/status | grep Uid:

Uid: 1002 1002 1002 1002    # 四个值全是普通用户 ID

内核的”双面人校验”机制在此生效:对内(子命名空间)视进程为 UID 0 + 全套 Capability,对外(宿主机)视其为原始 UID 1002。这种设计的本意是让子命名空间 root 可以自由管理其独立资源,同时无法对宿主机产生威胁。

// 3.2 第 2 步:通过 AF_NETLINK 触发内核加载 act_pedit.ko

核心原理:子命名空间 root 拥有 CAP_NET_ADMIN,可以通过 AF_NETLINK socket 向内核 tc 子系统发送消息。当消息引用了内核未注册的 action kind 时,内核会自动加载对应的模块,将模块注入全局内核。

整个流程可分为三步:

1.  构造并发送 netlink 消息

在子命名空间内创建 AF_NETLINK(NETLINK_ROUTE)socket,向内核 tc 子系统发送 RTM_NEWTFILTER 消息,在 lo 的 egress 方向挂载过滤器。关键消息字段:

  • tcm.parent = TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_EGRESS) —— 挂载到 clsact qdisc 的 egress
  • TCA_KIND = “basic” 或 “matchall” —— 分类器类型
  • TCA_ACT_KIND = “pedit” —— 触发模块加载的 action kind

2.  内核发现 action 未注册

tc 子系统处理消息时,发现 pedit 这个 action kind 尚未注册,随即触发标准模块加载流程。

3.  模块加载到全局内核

加载过程伴随执行上下文切换:

request_module(“act_pedit”) → 切换到 INIT ns 的 kmod 线程 → call_usermodehelper() 执行 modprobe act_pedit → act_pedit.ko 加载到全局内核

验证模块已全局加载:

lsmod | grep pedit

act_pedit 20480 0 # ← 模块已全局加载,CVE 可被利用

此步骤不需要网卡

模块加载仅通过 netlink socket 通信,由内核加载 ko 模块,不依赖任何网络接口。但后续页缓存投毒(步骤 3)需要 lo 接口——CLONE_NEWNET 会自动创建 lo(初始为 DOWN 状态),通过 link_up 拉起并配置 127.0.0.1/8 即可),无需物理网卡或外部网络连通。此步骤能成功的前提是 Cowork 的 seccomp 采用 deny-list(默认允许)模式,未拒绝 socket(AF_NETLINK) 和 unshare(CLONE_NEWUSER),导致攻击者可以创建子命名空间并自由向 tc 子系统发送 netlink 消息。

//3.3 第 3 步:CVE-2026-46331 页缓存投毒

act_pedit.ko 加载到全局内核后,攻击者利用该模块中 pedit action 写入 skb 数据时 COW(Copy-on-Write)未被触发的缺陷,向只读文件的页缓存注入恶意代码。核心机制是 sendfile() 零拷贝 + 环回接口 + egress pedit 过滤器

攻击者在子命名空间中以root身份(对初始命名空间仍为非特权用户)执行以下流程:

1.  选定目标文件

coworkd 辅助二进制(属主 root:root、权限 755),攻击者无写权限。页缓存投毒的意义正在于此:绕过文件权限,从内存层面篡改只读文件。

2.  在 lo 上安装 egress pedit 过滤器

先挂载 clsact qdisc,再在 egress 方向添加 pedit action。每个 pedit key 指定在包内某偏移处写入 4 字节 shellcode 片段;每个 burst 还须包含一个 IP IHL key(修改 IP 头),是利用的必要组成。

3.  用 sendfile() 零拷贝发送目标文件

在 lo 上建立 TCP 连接,通过 sendfile() 发出目标文件内容。零拷贝路径下 skb 数据段直接引用 page cache 页面,不做复制。

4.  pedit 触发写入,COW 被绕过

egress pedit action 触发并写入 skb 数据。由于 skb 引用的是 page cache 页本身,pedit 写入时 COW 未触发(CVE-2026-46331),page cache 中 .text 段对应的页被直接改写为 shellcode。

两个关键工程细节:

  • offset 校准:pedit 的包内偏移 ≠ 文件偏移。先写入标记字节 0xcc,扫描文件找到实际落点并计算 offset_delta,后续所有 key 的 offset 均减去该差值。

  • 写入限制:每个 burst 最多写入 36 字节(9 个 key × 4 字节),shellcode 需分块循环写入;classifier 优先用 basic + pktlen ematch,失败时回退 matchall。

为什么 sendfile() 是 COW 绕过的关键?

正常情况下,内核对 page cache 页的写入会触发 COW(复制后再写)。

但 sendfile() 的零拷贝路径让 skb 直接引用 page cache 页面。当 pedit action 修改 skb 数据时,它写入的实际上是 page cache 页本身——COW 在这条路径上被跳过了。这是 act_pedit 模块在处理零拷贝 skb 时的缺陷。

投毒后的状态:

| 层面 | 状态 | | — | — | | 磁盘上的字节 | 原始合法的二进制内容 | | 页缓存中的内容 | 已被攻击代码替换 | | 文件系统元数据 | 未修改(权限与属主不变) | | 后续读取效果 | 任何进程读取该文件(包括 coworkd)将从页缓存获取恶意版本 |

页缓存是全局的、命名空间无关的。

Linux 的页缓存由 inode 唯一标识,所有命名空间中的进程访问同一 inode 时命中同一份页缓存。这意味着子命名空间中的攻击者投毒的页缓存,对 INIT 命名空间中运行的 coworkd 守护进程同样可见。

//3.4 第 4 步:coworkd执行被投毒的helper导致 提权 + virtiofs-root 逃逸至宿主机

投毒后shellcode提权代码被注入到helper缓存中,当coworkd再次以root权限运行时,自动执行shellcode代码,获取root权限。

// coworkd 守护进程(根user_ns,真实 root)正常工作中重新执行辅助程序

coworkd (VM root, INIT user_ns)

→ execve(“coworkd-helper”)

  1. 内核读取 ELF header → 页缓存命中(磁盘数据未变)

  2. 加载 .text 段 → 页缓存已被投毒——执行攻击者的shellcode代码

  3. exec 的进程已是 root,NoNewPrivs 无效: 执行进程已是 root,没有”新特权”需要获取

  4. 攻击者代码以 根user_ns 的 root 身份执行

// 结果: 攻击者获得 VM 内完整 root 权限(INIT 命名空间,非子命名空间)

获得 根命名空间 root 后,攻击者直接访问 virtiofs-root 挂载点:

/mnt/.virtiofs-root = 宿主机 Mac 整个根文件系统(读写挂载)

窃取敏感数据

cat /mnt/.virtiofs-root/Users/myname/.ssh/id_rsa

cat /mnt/.virtiofs-root/Users/myname/.aws/credentials

植入持久化后门

echo “malicious-cron” > /mnt/.virtiofs-root/Library/LaunchDaemons/backdoor.plist

kmod命名空间逃逸机制

深度剖析

第 2 步是整个攻击链中容易被误解的环节。本节深入解释为什么 namespace 隔离在此失效。

request_module() 的执行上下文切换

request_module() → kmod 命名空间逃逸与全局模块加载

CLONE_NEWNET 不能阻止此逃逸

核心问题不在于网络命名空间是否隔离,而在于 request_module() 的执行上下文会自动切换到 INIT 命名空间的 kmod 线程。无论攻击者在什么命名空间中发起请求,模块加载始终由根ns 的真实 root 完成。页缓存也是全局的,不受命名空间约束。

改进建议

四层独立安全锁的拦截面

//5.1 改进1:关闭 unprivileged user namespaces

从根源阻止 unshare(CLONE_NEWUSER),攻击者永远无法获得任何 capability。

方式 1: 运行时生效(通用内核参数)

sysctl -w kernel.unprivileged_userns_clone=0

方式 2: Ubuntu 22.04+

sysctl -w kernel.apparmor_restrict_unprivileged_userns=1

方式 3: 永久生效

echo “kernel.unprivileged_userns_clone = 0” >> /etc/sysctl.d/99-disable-userns.conf

sysctl -p /etc/sysctl.d/99-disable-userns.conf

验证: 非特权用户创建 userns 被拒绝

unshare –user –map-root-user /bin/bash

unshare: unshare failed: Operation not permitted

对于 Cowork 这种专用沙箱 VM,关闭 unprivileged userns 没有任何副作用——VM 内不需要 Rootless 容器、Flatpak 或 bwrap 等依赖此特性的功能。

//5.2 改进2:seccomp 系统调用白名单

将 seccomp 从 deny-list (默认允许) 改为 allow-list (默认拒绝),仅放行 AI Agent 实际需要的系统调用。

// 基于 libseccomp,默认 SCMP_ACT_KILL,仅放行必要调用

scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);

// ===== 关键: 仅放行 AF_INET/AF_INET6/AF_UNIX =====

seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), 1, SCMP_A0(SCMP_CMP_EQ, AF_INET));

seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), 1, SCMP_A0(SCMP_CMP_EQ, AF_INET6));

seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), 1, SCMP_A0(SCMP_CMP_EQ, AF_UNIX));

// AF_NETLINK 不在白名单 → 自动 KILL,攻击步骤 2 被阻断

// ===== 其他必要调用(略)=====

// read, write, openat, mmap, brk, exit, futex, …

// ===== 以下不在白名单,自动 KILL =====

// unshare / clone(CLONE_NEWUSER) / clone3 / setns   ← 攻击步骤 1

// mount / pivot_root / chroot / ptrace / kexec_load …

seccomp_load(ctx);

白名单不充分

仅过滤 socket 的地址族还不够。必须在白名单中同时拒绝 unshare、clone(带 CLONE_NEWUSER)、clone3 和 setns 等所有命名空间相关调用,防止攻击者通过不同入口进入子命名空间。

//5.3 改进3:内核组件加固

内核提权漏洞太多了,理论上修不完。Linux 内核每年披露大量提权类 CVE漏洞,从 Dirty COW、Dirty Pipe 到本次的 pedit COW,攻击面遍布调度器、网络栈、文件系统、BPF 等各个子系统。指望通过打补丁消除所有内核提权漏洞是不现实的——漏洞永远修不完,但攻击路径可以封死。

本改进的目标不是修复 CVE-2026-46331,而是消灭它的载体。没有 act_pedit.ko 这个模块,漏洞代码根本不存在于内核中,无论攻击者如何尝试都无法抵达。这个策略应该推广到所有非必需的内核模块——构建”最小内核模块集”,每删除一个模块,就少一个潜在的攻击面。

阻止 act_pedit 自动加载(modprobe 被拦截)

echo “install act_pedit /bin/false” >> /etc/modprobe.d/block-net-sched.conf

加入黑名单(同时阻止手动和自动加载)

echo “blacklist act_pedit” >> /etc/modprobe.d/block-net-sched.conf

或直接删除模块文件

rm /lib/modules/$(uname -r)/kernel/net/sched/act_pedit.ko

删除 act_pedit.ko 后,攻击链第 2 步直接失效——request_module() 返回 -ENOENT,kmod 无法加载不存在的模块。这一思路适用于所有类似场景:面对层出不穷的内核提权 CVE,与其逐个打补丁追赶,不如直接删除不需要的模块,从根本上消除攻击面。

//5.4 改进4:不共享宿主机根文件系统

即使前三个改进全部失效、攻击者获得 VM 完整 root 权限,此层作为兜底防线仍能阻止数据泄露。原则很简单:不共享,就不可达。

当前 Cowork 将宿主机整个根文件系统通过 virtiofs 共享进 VM:

宿主机启动 virtiofs 守护进程

virtiofsd –shared-dir=/ # ← 宿主机 / 对 VM 完全可见

VM 内自动挂载为 /mnt/.virtiofs-root

正确做法是只共享用户明确授权给 AI Agent 的目录:

virtiofsd –shared-dir=/Users/myname/AI_Sandbox/

VM 内挂载为 /mnt/sandbox

攻击者拿到 root 后只能读写 ~/AI_Sandbox/

.ssh/ .aws/ Library/ 等敏感目录完全不可见

共享目录的边界就是攻击者破坏范围的边界。当前 virtiofs-root 的命名本身就暗示了”宿主机根目录对 VM 完全开放”,这是设计层面的根本问题。

总结

这条攻击链利用的不是单一漏洞,而是多个设计决策的连锁失误。修复优先级:

  1. 改进4(立即执行,零成本): virtiofs 共享目录从 / 缩小为指定沙箱目录

  2.  改进1 + 改进2: 关闭 unprivileged userns + seccomp allow-list,覆盖所有命名空间提权攻击

  3.  改进3: 内核模块最小化,攻击面收敛到必需集合

纵深防御是唯一可靠的策略

任一环节被阻断,整条攻击链即失效。不要指望单一防御能挡住所有攻击——攻击者只需要找到一个缺口,防御者需要保证每一环都牢固。

参考资料:

  1. Accomplish Team. SharedRoot: Escaping the Claude Cowork Sandbox. Accomplish Blog, 2026.
  2. CVE-2026-46331(“pedit COW”)
  3. Linux Kernel Documentation: kernel.unprivileged_userns_clone / kernel.apparmor_restrict_unprivileged_userns.

-END-

——关注云鼎实验室,获取更多安全情报——


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:云鼎实验室 《黑客如何突破Claude Cowork的沙箱“越狱”至Mac电脑的?》

评论:0   参与:  0