漏洞
Telegram:一个链接,怎样让电脑里的文件被发走
Telegram Desktop 的旧文件发送功能可被外部链接唤起,在特定条件下把本地文件乃至登录会话材料发走;更新至当前稳定版 7.3.1 可关闭这条入口,已经泄露的登录还需在设备列表中终止。

文章导航
聊天软件每天替我们收文件、发文件。通常,发哪一份、发给谁,要由自己决定。这次 Telegram Desktop 的漏洞让一个外部链接接管了这两项选择:程序读到一份本地文本,照着里面的路径取文件,再用已经登录的账号把它发出去。整个发送过程没有再询问用户。
BeakSec 在 10 月 3 日公开了这条攻击链,10 月 7 日进入 CVE 记录,编号为 CVE-2026-107181。公开复现在 Windows 的 Telegram Desktop 6.9.3 上完成,7.2.8 源码仍保留整条路径;首次修复版是 7.2.9。研究中最值得警惕的后果,是程序可能把自己的登录会话材料一起发走。拿到足够材料、且能够解开本地加密后,攻击者就能尝试复用已有登录。
如果电脑里还在运行旧版 Telegram Desktop,应先退出并更新。本文发布时,官方最新稳定版是 7.3.1,GitHub 发布于 10 月 10 日。7.2.9 是这次修复的起点,当前安装应优先选最新稳定版。手机端和网页端不使用本文追踪的桌面程序代码,不能由这条 CVE 推断它们也有同样漏洞。
1 点击链接以后,为什么会多出一个进程
先看一次正常操作。浏览器遇到 tg:// 链接,会通过操作系统找到 Telegram Desktop,把链接交给它。即使 Telegram 已经开着,系统仍可能启动一份新进程。新进程发现已有实例,就把这次要打开的链接送给旧进程,然后退出。聊天窗口因此继续留在原处,用户通常感觉不到这次交接。
两份进程通过本地套接字通信,这属于 IPC,也就是进程间通信。在 7.2.8 的 Sandbox::socketConnected() 中,新进程把 URL 拼成一条文本记录:前面加 OPEN:,末尾加分号。旧进程的 readClients() 每遇到一个分号,就认为一条记录结束,可以开始处理下一条。
// 7.2.8:URL 写入 IPC 记录
commands += u"OPEN:"_q + url.toString(QUrl::FullyEncoded) + ';';
问题出在链接本身也能含有分号。QUrl::FullyEncoded 处理的是 URL 的表示方法,分号在 URL 中仍可以合法出现;外面这层自定义通信格式却把同一个字符当成结束标记。到旧进程那里,一条链接中的一部分就变成了下一条记录。只要那部分恰好长得像它认识的操作,程序便会继续处理。
下面用一个不含文件操作的例子看这个差异。旧格式把 tag=blue 误切成第二条记录;这条示例记录本身不会触发发文件。我们用离线格式模型核对了分割和修复后的往返结果,没有启动 Telegram 或连接其服务。
URL:tg://resolve?domain=demo;tag=blue
旧格式:OPEN:tg://resolve?domain=demo;tag=blue;
切分为:OPEN:tg://resolve?domain=demo
tag=blue
这也解释了触发入口。聊天窗口里直接点一个 tg:// 链接,通常由当前进程内部处理,走不到这次出错的套接字路径。公开方案让普通网页链接先进入外部浏览器,再重定向回 tg://,让操作系统启动新进程。浏览器可能在这里询问是否允许打开 Telegram,是否出现提示取决于浏览器及既有授权。用户需要点击链接,有时还要确认启动应用;看见一条消息本身不会完成这条攻击链。
2 旧的自动发布功能,接住了多出来的记录
记录被拆开,只说明程序会多处理一个输入。要走到文件外传,还需要一个能替用户发文件的入口。Telegram Desktop 当时恰好保留了 interpret::它接收本地文本文件的路径,读取文本,再按照其中的字段发送另一份文件。
这个功能有明确的历史用途。2018 年的引入提交同时加入了更新发布脚本,用它自动发布版本材料。多年后,入口仍在普通客户端里。Application::checkStartUrls() 发现 interpret scheme,取出路径交给窗口;MainWidget::handleStartFiles() 随后直接调用 Support::InterpretSendPath()。
这个 helper会识别目标频道或超级群、可选话题、文件路径和说明文字。它还支持 from:,用于核对当前账号,可这项字段是可选的。文本不写这一行,账号核对就不会发生。程序于是把外部送来的发布指令,当成了可以由当前会话执行的任务。
真正发出文件的位置在函数末尾:它准备媒体列表,取当前会话的 API,再调用 sendFiles()。同一个窗口处理普通本地文件时会打开选择收件人的界面,这个辅助路径没有相应的发送确认。攻击者因此能通过一份指令决定读取哪一个现有文件,以及把它送到哪一个可用目标。
这里的能力由 Telegram 进程的文件权限决定。进程能读到、路径能定位、且能作为文件发送的内容,才会进入这条链。套接字里虽然还有名叫 CMD: 的操作,其 execExternal() 分支只处理显示窗口和退出等应用动作;把这次问题叫成任意系统命令执行,会误解它真正危险的地方:一个已经登录、能够读文件和上传文件的程序,替外部输入完成了发送。
3 链接生效之前,那份指令要先落到磁盘上
interpret: 读的是本地路径。攻击者还得先把指令文件放到用户电脑上,并知道程序应该到哪里找它。公开复现借用了群里的文本附件:用户打开群,客户端按自动下载设置把小文件保存到磁盘,随后才点击群中的网页链接。文件没有被当作程序执行;它的文字内容被旧 helper 当作发布指令读取。
这一步受实际设置约束。7.2.8 的默认规则给私人聊天和群组中的普通文件设置了 8 MiB 自动下载上限,频道普通文件的默认上限为零。用户修改过的规则、单个会话的覆盖设置还会改变结果。DocumentMedia::automaticLoad()另外检查保存路径:需要先询问用户选位置时,普通文件不会直接自动下载。
发送目标也有条件。helper 使用 historyLoaded(toId) 查找目的会话,查不到就返回错误,无法凭一个陌生编号随意建出目的聊天。因此,公开方案需要用户已经加载攻击者控制的超级群。限制陌生人拉群、关闭陌生群文件自动下载,都能削弱这种投递方式;用户自己下载文件或进入同类目标后,相应条件仍可能重新满足。
窗口锁定和目录布局同样影响结果。checkStartUrls() 只在活动窗口解锁时处理这批本地文件操作,锁定时不会立即走到 helper。公开复现还依赖所测 Windows 安装的工作目录与下载目录关系;便携安装、自定义工作目录、不同系统的路径不能直接照搬。它们解释了为什么一条链接无法在任意机器上无条件成功,也指出了维护者该检查哪些真实设置。

4 文件加密了,为什么登录仍可能被复制
文件外传已经足够严重。研究进一步选择 Telegram 自己的 tdata,因为客户端需要把登录状态留在磁盘,下一次打开时才能直接继续聊天。里面保存的授权密钥可以让客户端以已有会话连接服务器,价值远高于一份普通附件。
这些材料在磁盘上经过加密。关键在于解密所需的东西分别在哪里。Storage::Domain 生成一个随机本地密钥,用它保护账户数据;再从本地口令和盐值派生另一把密钥,把随机密钥加密存放。key_datas 留着盐值和这个被包裹的本地密钥,方便客户端下次恢复。
默认没有设置本地口令时,派生函数的口令输入为空。CreateLocalKey() 对空口令使用一次 PBKDF2-HMAC-SHA512 迭代,非空口令才使用较高迭代次数。盐值可以公开;空口令也没有秘密。拿到保存它们的文件,就具备了重新派生包裹密钥、解开随机本地密钥的条件。
授权文件接着提供第二步。writeMtpData()/readMtpData() 用本地密钥写入和读回序列化的 MTProto 授权;序列化内容包含用户、主数据中心和授权密钥列表。恢复时,setMtpAuthorization() 与 startMtp() 将读回的密钥交给连接实例。研究者利用外传的密钥材料、授权文件及索引完成了 Windows 会话复制演示。SOSEC 核对了这条存储和恢复路径,本文没有重新运行账号接管实验。
强本地口令会改变这一步:取得加密文件的人还需要解开口令保护,难度取决于口令强度。已经解锁的旧客户端仍可能替攻击者发送其他可读文件,所以本地口令无法替代更新。账号的“两步验证密码”则用于登录认证,本地口令用于保护这台电脑上的存储,两者控制的位置不同。已有会话材料泄露后,应处理那份授权本身。
5 补丁既修了分号,也删掉了自动发送入口
9 月 16 日的修复提交同时改了通信格式和功能入口。发送端增加专用于 IPC 的转义:分号变成 %003b,百分号变成 %0025,控制字符和非 ASCII 字符也按四位十六进制编码。原本就包含 %003b 的文本,会先把百分号转义,避免被误还原成分号。
同一个 URL 的新 IPC 表示:
OPEN:tg://resolve?domain=demo%003btag=blue;
接收顺序:先按 ; 切出一条完整记录
再把记录内容里的 %003b 还原成 ;
先分记录、后还原字符,这个顺序保住了链接的完整身份。若先把转义还原,再做分割,危险的分号就会重新出现在分割阶段。7.2.9 的转义函数和接收逻辑遵守了这个顺序。我们的离线模型还核对了普通文本、字面百分号、中文、换行和代理对字符的往返;它验证格式关系,覆盖范围到此为止。
接收端另外收紧了混合操作:同一轮解析含有 OPEN: 时跳过 CMD: 和 CTRL:;同一连接一旦出现外部 URL,就丢弃其中的本地文件路径。这两项检查的持续范围不同,前者针对本轮记录,后者保存在连接状态中。到 7.3.1,当前源码仍保留这些保护,并补充了处理嵌套事件循环时的连接存活检查。
interpret: 则从应用分发、窗口调用和辅助函数中一起删除。只修格式可以阻止这次外部链接拼出额外记录;删掉普通客户端中的旧发布入口,还撤掉了那项不经确认的文件发送能力。我认为这是补丁中很重要的取舍:内部自动化功能一旦接到日常链接处理上,就拥有了远超用户预期的权限。清除这条连接,比继续靠隐藏的使用约定约束它更可靠。
6 更新之后,还要看旧会话是否已经离开电脑
日常使用者先核对实际运行版本,退出所有旧实例,再启动官方当前稳定版。检查便携目录和常用快捷方式,避免更新了一份安装,却继续打开另一份旧程序。正常启动、打开自己的聊天、手动选择一个无敏感内容的文件并确认收件人,足以检查更新后的基本使用;不要拿陌生链接验证补丁。
通过 Linux 发行版安装的人还要看包维护状态。本文发布日,Ubuntu 的记录对 22.04、20.04 和 18.04 的包仍标为 Needs evaluation,24.04 与 26.04 标为 Not in release。这些标签没有给出可据以确认回补的修复包。使用发行版包时需要相应维护者的修复记录;切换官方安装渠道时,应先处理重复安装,避免两份客户端混用。若更新失败,暂时退出旧客户端,可改用已更新的其他官方客户端;回退桌面版也应留在含有本次修复的构建内。
如果此前点过可疑跳转,同时出现自己没发送过的文件,或设备列表里有陌生会话,更新只关闭后续入口,无法收回已发出的东西。应从可信设备进入“设置 → 设备”,检查并终止可疑授权;无法判清时终止其他会话,再重新登录需要保留的设备。Telegram 官方的失窃设备指引也把终止旧会话列为必要操作。被外传文件里的 API 密钥等秘密,另外按实际内容更换;删除下载的指令文件不会使已经复制的授权失效。
这次漏洞把一个容易忽略的事实摆到了眼前:聊天软件本来就有读文件、联网、以你的身份发送消息的能力。出错的地方,是它在接收外部链接时丢失了“哪些字属于链接”的界限,又把剩余内容交给了旧自动化入口。补丁重新保住链接的边界,撤掉无确认发送功能;用户在已有泄露迹象时,还需要结束那份可能已被复制的登录。做到这里,这次更新才同时处理了程序里的错误和账号里留下的风险。