背景
在之前一篇关于 [Vim 和 Emacs 中由 AI 发现的漏洞](https://blog.calif.io/p/mad-bugs-vim-vs-emacs-vs-claude) 的文章里,探讨了看似无害的操作流程如何导致代码执行。此次进一步探究执行 `cat readme.txt` 是否安全,结果表明,若使用 iTerm2,并不安全。同时,要感谢 OpenAI 在这个项目上的合作。
iTerm2 的 SSH 集成功能
iTerm2 具备 SSH 集成功能,能更深入了解远程会话。它通过在远程端部署名为 conductor 的小型辅助脚本实现该功能,而非简单“盲目”在远程 shell 中输入命令。大致流程为:1. iTerm2 通常通过 `it2ssh` 启动 SSH 集成;2. 通过现有 SSH 会话发送远程引导脚本 conductor;3. 远程脚本成为 iTerm2 的协议对等方;4. iTerm2 和远程 conductor 通过交换终端转义序列协调各种操作,如发现登录 shell、检查 Python 环境、切换目录、上传文件、运行命令等。关键在于,没有单独的网络服务,conductor 只是在远程 shell 会话中运行的脚本,协议通过普通的终端输入输出(I/O)进行传输。
PTY 知识回顾
过去,终端是连接到计算机的键盘和屏幕等实际硬件设备,程序从该设备读取输入并将输出写回。像 iTerm2 这样的终端模拟器是现代的软件版硬件终端,负责绘制屏幕、接收键盘输入并解释终端控制序列。而 shell 和其他命令行程序期望与类似真实终端设备的东西交互,这就是操作系统提供伪终端(PTY)的原因,PTY 是旧硬件终端的软件替代品,位于终端模拟器和前台进程之间。在正常的 SSH 会话中,iTerm2 将字节写入 PTY,前台进程是 `ssh`,`ssh` 将这些字节转发到远程机器,远程 conductor 从其标准输入读取这些字节。所以,当 iTerm2 想“向远程 conductor 发送命令”时,实际上是在本地将字节写入 PTY。
conductor 协议
SSH 集成协议使用终端转义序列作为传输方式,有两个关键部分:`DCS 2000p` 用于挂载 SSH conductor,`OSC 135` 用于 pre - framer conductor 消息。在源代码层面,`DCS 2000p` 会使 iTerm2 实例化一个 conductor 解析器,解析器接收 `OSC 135` 消息,如 `begin `、命令输出行、`end r`、`unhook` 等。因此,合法的远程 conductor 可以完全通过终端输出与 iTerm2 进行通信。
核心漏洞
该漏洞源于信任机制的失效,iTerm2 会接受并非来自受信任的真实 conductor 会话的终端输出中的 SSH conductor 协议,即不可信的终端输出可以模仿远程 conductor。这意味着恶意文件、服务器响应、横幅或 MOTD 可以输出伪造的 `DCS 2000p` 挂载信息和伪造的 `OSC 135` 回复,然后 iTerm2 会表现得好像正在进行真实的 SSH 集成交互,这就是漏洞利用的基础。
漏洞利用的实际原理
漏洞利用文件包含一个伪造的 conductor 记录。当受害者运行 `cat readme.txt` 时,iTerm2 会渲染该文件,该文件包含一条伪造的 `DCS 2000p` 行用于宣告一个 conductor 会话,以及伪造的 `OSC 135` 消息用于响应 iTerm2 的请求。一旦挂载信息被接受,iTerm2 就会启动正常的 conductor 工作流程,在源代码中,`Conductor.start()` 会立即发送 `getshell()` 请求,成功后再发送 `pythonversion()` 请求。所以,漏洞利用无需注入这些请求,iTerm2 会自行发起,恶意输出只需模仿回复即可。
状态机分析
伪造的 `OSC 135` 消息虽简单但精准,操作如下:1. 为 `getshell` 启动一个命令体;2. 返回看起来像 shell 发现输出的行;3. 成功结束该命令;4. 为 `pythonversion` 启动一个命令体;5. 以失败结束该命令;6. 取消挂载。这些操作足以使 iTerm2 进入正常的回退路径,此时 iTerm2 认为已经完成了足够的 SSH 集成工作流程,可以进入下一步:构建并发送 `run(...)` 命令。
sshargs 的作用
伪造的 `DCS 2000p` 挂载信息包含多个字段,其中包括攻击者可控的 `sshargs`。这个值很重要,因为 iTerm2 稍后会在构建 conductor 的 `run ...` 请求时将其用作命令材料。漏洞利用者选择 `sshargs`,使得 iTerm2 进行 Base64 编码后,`run ` 最后 128 字节的块变为 `ace/c+aliFIo`,这个字符串同时满足是 conductor 编码路径的有效输出和一个有效的相对路径名这两个条件。
使漏洞利用成为可能的 PTY 混淆问题
在合法的 SSH 集成会话中,iTerm2 将 Base64 编码的 conductor 命令写入 PTY,`ssh` 会将其转发到远程 conductor。而在漏洞利用的情况下,iTerm2 仍然将这些命令写入 PTY,但此时并没有真正的 SSH conductor,本地 shell 会将其作为普通输入接收。记录会话时呈现的情况为:`getshell` 以 Base64 形式出现,`pythonversion` 以 Base64 形式出现,然后出现一个长的 Base64 编码的 `run ...` 有效负载,最后一个块是 `ace/c+aliFIo`。前面的块作为无意义的命令会执行失败,如果该路径在本地存在且可执行,那么最后一个块就会成功执行。
复现步骤
可以使用 `genpoc.py` 复现原始的基于文件的概念验证(PoC):`python3 genpoc.py`,`unzip poc.zip`,`cat readme.txt`。这将创建 `ace/c+aliFIo`(一个可执行的辅助脚本)和 `readme.txt`(一个包含恶意 `DCS 2000p` 和 `OSC 135` 序列的文件)。前者会误导 iTerm2 与一个伪造的 conductor 进行通信,当最后一个块到达时,后者会让 shell 执行实际的命令。为使漏洞利用生效,需要在包含 `ace/c+aliFIo` 的目录中运行 `cat readme.txt`,这样攻击者构造的最后一个块才能解析为一个真实的可执行路径。
漏洞披露时间线
- 3 月 30 日:向 iTerm2 报告了该漏洞。- 3 月 31 日:该漏洞在提交 `a9e745993c2e2cbb30b884a16617cd5495899f86` 中得到修复。- 在撰写本文时,该修复尚未应用到稳定版本中。当补丁提交后,尝试仅使用该补丁从头开始重建漏洞利用,该过程中使用的提示信息位于 [`prompts.md`](https://github.com/califio/publications/tree/main/MADBugs/iTerm2/prompts.md),生成的漏洞利用脚本是 `genpoc2.py`,其工作方式与 `genpoc.py` 非常相似。