一条链接,三个文件:Telegram 桌面版那个忘了转义的分号
目录
- 你被拉进了一个群
- 一条链接,为什么变成了两条进程
- 分号:数据里不该出现的边界
- interpret:——只给发布脚本留的专属通道
- 三个默认值:漏洞从「理论可行」到「一键可得」
- 我写了个最小复现:1 行变 3 条
- 修复只有一行,而且它静悄悄
你被拉进了一个群
某天,有人把你拉进一个 Telegram 群。Telegram 的默认设置允许任何人这样做,不需要你点同意。
群里有一个链接,看起来是个普通网址。你点了它。然后你的账号,就不再只属于你了。
这个漏洞编号 CVE-2026-107181,CVSS 3.1 评分 8.1(High)。它的发现者 BeakSec 在 10 月 3 日公开了完整分析。整个攻击链拆开来看,没有一环是「黑客炫技」——每一环都是桌面软件里再普通不过的设计,只是它们恰好能首尾相连。
一条链接,为什么变成了两条进程
现代操作系统允许程序注册 URI scheme:tg:// 开头的链接归 Telegram 管。系统遇到这种链接,就启动 Telegram,把整条 URL 当作命令行参数传进去。
问题来了:如果 Telegram 已经在运行呢?
操作系统不会检查,它会照常再拉一个进程起来。两个一模一样的 Telegram 进程,谁是新来的?谁又是老板?
Telegram 自己的做法是:新进程试着连一个本地 socket。连上了,说明已经有一个实例活着,于是新进程把链接序列化成一行文本,扔给 socket 里的老进程,然后自己退出。
注意这一步:socket 不传对象,只传字节。新进程内存里的 URL 对象没法直接穿过这条通道,必须被压扁成一串字符——把结构化的数据变成文本,再把文本还原成结构化,这个过程叫序列化/反序列化。而每一次这样的搬运,都是「边界从结构里消失、变成字符」的时刻。
Telegram 的协议很简单:每条指令是 关键字:参数;,分号结尾。打开一个链接,就是:
1
OPEN:tg://x?a=1;
老进程收到后,在每一个分号处切一刀,把每一段当作一条独立指令处理。
分号:数据里不该出现的边界
现在,如果这个「参数」里自己就带着一个分号呢?
一个正常的链接 tg://x?a=1 在 Telegram 里没有对应处理函数,点了也白点——它只是个载体。但攻击者构造的链接长这样:
tg://x?a=1;OPEN:interpret:指令文件;CMD:quit
对新进程来说,这个分号只是 URL 参数里的一个普通字符,它照样把整条链接序列化、原样写进 socket:
OPEN:tg://x?a=1;OPEN:interpret:指令文件;CMD:quit;
老进程呢?它按分号切分,得到三条指令:
OPEN:tg://x?a=1
OPEN:interpret:指令文件
CMD:quit
一条链接,变成了三条指令。这就是注入:数据里的一个字符,被解析器当成了边界。写这段代码的人忘了转义——socket 协议自己定义的分隔符,和 URL 里用户可控的字符,撞在了同一个人身上。
interpret:——只给发布脚本留的专属通道
注入本身还不够。如果 Telegram 只能接受 OPEN:、CMD:quit 这种无害指令,那最坏情况也就是把应用关掉。真正致命的是第四条路:OPEN: 接受任意 URL scheme,而 Telegram 内部注册了一个不对操作系统公开的 scheme——interpret:。
这个scheme是干嘛的?它是 Telegram 当年给自己发布流程准备的:新版构建好之后,脚本要把它发到频道里,配一段更新日志。与其手工操作,脚本写了一个小文本文件,里面写上「把哪个文件发到哪个频道」,然后让 Telegram 自己读这个文件、自己发。
这个「解释器」InterpretSendPath 做的事是:读取磁盘上任意路径的文件,发送到一个频道。它不确认是谁请求的,不弹任何确认框。
当年这个设计是合理的:只有发布脚本会走到这条路,而攻击者要是能碰发布脚本的机器,早就自己读文件了。问题在于——一个为特权通道设计的函数,被注入变成了一条链接就能触达的入口。
三个默认值:漏洞从「理论可行」到「一键可得」
到这里,攻击链还差最后一环:攻击者要怎么把自己的指令文件放到受害者磁盘上?
答案是三个默认值——Telegram Desktop 出厂时的默认配置,每一个单独看都合情合理:
默认①:群里收到的文件自动下载。 群文件最大 8MB 会自动落盘,无需点击。文件落到 C:\Users\<用户名>\Downloads\Telegram Desktop\ 下,文件名就是发送者起的名字——位置完全可预测。
默认②:无本地口令。 Telegram 把本地数据加密,但密钥链条是「口令→KEK→DEK」。不设本地口令时,喂进 KDF 的就是空字符串,而盐就明文存在 tdata/key_datas 里。读这一个文件,就够解开 DEK,进而解开所有本地数据——包括那个识别你身份的会话授权。
默认③:允许陌生人把你拉进群。 攻击者建一个群,把你拉进去,自动下载就发生在你「顺便进群看链接」的那一刻。连让你点「同意入群」都不需要。
三个默认值,通过自动下载落在可预测路径;一条被注入的链接(经由一个会 302 跳转到 tg:// 的普通网址),通过 socket 触发注入;interpret: 把三个文件读走、发到攻击者的频道;攻击者用这三份文件重建 tdata,启动 Telegram——你的会话就开了。其中那个神秘的文件夹名 D877F783D5D3EF8C 也不是随机的:它由字符串 data 派生,每台安装都一样。
顺带一提,这个「默认配置就是攻击面」的逻辑,跟国内桌面聊天软件是同构的——任何注册了 URI scheme、又通过本地 socket 传递链接的客户端,都有这样一条「没人知道它存在」的内部通道;区别只在于通道那头挂着的函数有没有权限、认不认人。
我写了个最小复现:1 行变 3 条
我不打算真的去复现攻击(也复现不了,没有 Windows 环境)。但注入的机制本身可以复现——它只是一个「分隔符不转义」的问题。
我写了个 30 行的模拟:一个 UNIX socket 服务器扮演「已在运行的 Telegram 实例」,收到一行文本后按分号切分并派发;一个客户端扮演「新进程」,把 URL 序列化后写进 socket。攻击者链接照抄原报告:
tg://x?a=1;OPEN:interpret:指令文件;CMD:quit
未修复的版本(不转义,直接按分号切)输出:
收到 1 行,派发出 3 条指令:
OPEN: tg://x?a=1
OPEN: interpret:指令文件 ← 危险的那条,真的被单独派发了
CMD: quit
修复的版本(发送端把分号转义成 %3B,接收端切完再解码)输出:
收到 1 行,派发出 1 条指令:
OPEN: tg://x?a=1;OPEN:interpret:指令文件;CMD:quit ← 整体是一条 URL,没人被坑
同一个链接,两种待遇。差异只有一行代码,但这一行决定了「分号是数据,还是边界」。
修复只有一行,而且它静悄悄
修复提交是 db3405699f,2026 年 9 月 16 日:彻底移除 interpret:// scheme 和 InterpretSendPath,并把 socket 协议的分隔符改成百分号十六进制转义——数据里的分号,从此再也变不成指令边界。9 月 17 日的 7.2.9 修好了问题。
有意思的是,这次修复是静悄悄的:7.2.9 的更新日志只提了一句「修复媒体查看器崩溃」,那条真正堵住攻击链的提交标题叫「Remove legacy interpret path helper」,没有任何安全公告。漏洞是 6 月 25 日通过 ZDI 报告的,厂商在 9 月独立修复后 ZDI 才在 9 月 30 日关案,披露权回到研究者手里——CVE 直到 10 月 7 日才分配。
回顾整条链:socket 序列化信任了 URL 的内容,分号切分信任了参数里的字符,解释器信任了调用者,出厂默认值信任了「用户会自己改设置」。每一层都觉得自己不是边界,于是边界不存在了——直到有人发现,它们恰好可以首尾相连。
修复只有一行转义。而这一行,把边界放回了它唯一该在的地方:发送端。
评论(0)
暂无评论,来写第一条吧~