背景介绍
假设你把手机递给我,10 秒钟以内能做出最糟糕的事情是什么?
某天,国外白帽小哥收到一条有趣的消息,其中包含了 Telegram 的链接,单击该链接后,会发现自己的帐户已经登录了,出于好奇,白帽小哥注销了帐户,给自己发送了一条包含相同链接的消息,单击后发现再次登录。那么这条链接有什么特别之处吗?看起来似乎只是 Telegram 的默认行为。
探究
第一步是弄清楚 Telegram 客户端如何将会话传递到浏览器。当我点击链接时,我注意到网址栏上有东西闪了一下:
web.telegram.org/#tgWebAuthToken=dGhpcyB0b2tlbiBpcyByYW5kb20gYW5kIDEwMjQgYml0cyBsb25nLCBidXQgaW4gdGhlIGJsb2cgcG9zdCBpIHJlcGxhY2VkIGl0IHdpdGggdGhpcyBmdW4gZWFzdGVyIGVnZyBmb3IgdGhvc2Ugd2l0aCBhIGtlZW4gZXllIQ&tgWebAuthUserId=420493337&tgWebAuthDcId=4
看起来 Telegram 只是打开一个附加了帐户令牌的 URL,令牌被放入哈希片段中,一旦 Web 客户端加载并意识到那里有一个令牌,令牌就会迅速消失。
虽然非常方便,但该功能却令人担忧,因为即使你使用 2FA 和锁定的设备(例如非 root/越狱的手机),也可以使用它快速访问你的帐户。
那么这个 URL 及其会话从何而来?白帽小哥在 tdesktop (tdesktop 是官方跨平台桌面客户端(macOS 上的 Telegram Lite)) 的代码中搜索了各种关键字,例如“web.telegram.org”和“tgWebAuthToken”,但却没有找到任何结果,最终白帽小哥决定构建该应用程序并进行调试。
经过几个小时的编译,白帽小哥终于构建并运行了自己的 tdesktop,点击了一些测试链接,并逐步查看代码后,最终来到了下面的断点:
这就是为什么之前找不到关键字的原因!此技巧适用的域名列表由 Telegram 服务器发送给你,并存储在配置中的 url_auth_domains (url_auth_domains 是用于登录 Web 客户端的域列表,但 autologin_domains 键下还有另一个列表,用于 bugs.telegram.org 等 Web 应用程序)密钥下,可以在上面的 locals 中看到当前提供的域名列表。
一旦点击具有匹配域名的链接,客户端就会将其发送到 Telegram 的服务器,如果一切正常,将收到一个临时 URL,其中包含令牌和附加的所有内容,对于那些在家玩游戏的人,我们发送一个仅包含 url set (还有 peer 、 msg_id 和 button_id 字段,但如果将 flag 设置为 f_url 就会跳过它们)的 messages_requestUrlAuth ,并希望获取包含新 url 的 urlAuthResultAccepted。
在弄清楚了它的工作原理后,白帽小哥开始寻找一种方法来破解它。
看起来整个初始 URL 都被保留了,包括路径、查询参数和哈希片段,但被强制为 https,例如:
- http://web.telegram.org/ 变为 https://web.telegram.org/#tgWebAuthToken=…
- https://z.t.me/pony 变为 https://z.t.me/pony?tgWebAuth=1#tgWebAuthToken=…
- https://k.t.me/#po=ny 变为 https://k.t.me/?tgWebAuth=1#po=ny&tgWebAuthToken=…
更多例子:
除了 web.telegram.org 之外的所有域都是为 t.me Deep Link而构建,在没有路径的情况下继续其中任何一个只会将你带到 telegram.org 主页,使用兼容路径(如 z.t.me/share?url=lyra.horse
)进行操作,将使用哈希片段打开相应的客户端,例如:
https://web.telegram.org/a/#?tgaddr=tg%3A%2F%2Fmsg_url%3Furl%3Dlyra.horse
通常会通过 HTTP 301 重定向来执行,但如果设置了 tgWebAuth
参数并且Deep Link有效,你将可以运行这个有趣的 javascript:
<html>
<head>
<meta name="robots" content="noindex, nofollow">
<noscript><meta http-equiv="refresh" content="0;url='https://web.telegram.org/a/#?tgaddr=tg%3A%2F%2Fmsg_url%3Furl%3Dlyra.horse'"></noscript>
<script>
try {
var url = "https:\/\/web.telegram.org\/a\/#?tgaddr=tg%3A%2F%2Fmsg_url%3Furl%3Dlyra.horse";
var hash = location.hash.toString();
if (hash.substr(0, 1) == '#') {
hash = hash.substr(1);
}
location.replace(hash ? urlAppendHashParams(url, hash) : url);
} catch (e) { location.href=url; }
function urlAppendHashParams(url, addHash) {
var ind = url.indexOf('#');
if (ind < 0) {
return url + '#' + addHash;
}
var curHash = url.substr(ind + 1);
if (curHash.indexOf('=') >= 0 || curHash.indexOf('?') >= 0) {
return url + '&' + addHash;
}
if (curHash.length > 0) {
return url + '?' + addHash;
}
return url + addHash;
}
</script>
</head>
</html>
<!-- page generated in 4.3ms -->
一开始白帽小哥有点困惑,但最终意识到这只是处理 URL 哈希片段的简单 hack, URL 的哈希片段部分永远不会发送到服务器,因此如果服务器也想添加自己的哈希片段,则无法知道将你重定向到哪里。
在这个特定的例子中,我们的 URL 中已经有了 #tgWebAuthToken=...
,当重定向到 Web 客户端时,我们希望将它与 #?tgaddr=...
结合起来(所以最终我们得到 #?tgaddr=...&tgWebAuthToken=...
)。
接下来的时间,白帽小哥一直在尝试 Telegram 的各种网络客户端,一个鲜为人知的事实是,至今仍可以通过访问 web.telegram.org/?legacy=1
来访问旧版 Telegram Web 客户端,此外,会话在 Web 客户端之间共享,因此即使目标使用新的 Web 客户端,旧 Web 客户端中的漏洞可能依然有用。
之后白帽小哥查看了移动应用程序, iOS 和 Android 客户端都支持链接身份验证,考虑到从移动设备复制会话令牌通常要困难得多,这让整个情况变得令人担忧。
在 Android 上,白帽小哥搞乱了intents,但最终陷入了另一个死胡同,因为自 Android 12 以来,网络链接的intents已被锁定,并且需要域验证才能工作,另外白帽小哥还搞乱了protocol intents,但应用程序的编写方式阻止了在这些情况下附加令牌。
难道就没法利用了吗?
在以上的研究中,由于无法提出成功的远程攻击,因此是无法获得漏洞赏金的,但这并不意味一切都是徒劳的。
结合目前的所有研究,再加上一些锦上添花,我们可以创建一个场景,只需几秒钟就可以窃取某人的 Telegram 会话,无论是他们的计算机、手机还是平板电脑。
首先向受害者的 Telegram 应用程序中发送“z.t.me”,并让其点击链接,这会将受害者的浏览器重定向到 telegram.org/#tgWebAuthToken=...
,在这里,浏览器中将域编辑为 telegramz.org
,然后点击回车,域上的 JavaScript 将从这里获取它,并使用令牌登录攻击者的一台设备。
攻击演示视频:
即使对于技术水平较低的攻击者来说,这种攻击也非常容易实现,假设有高人已经设置了一个自定义域名,攻击者所需要知道的就是如何点击链接并在 URL 栏中添加一个字母,无需任何专业的工具,无需了解目标的任何信息,甚至都不需要电话…
那么 Telegram 应该如何做呢?
通过二维码登录Telegram:
- 在手机上打开Telegram
- 进入设置-设备-链接桌面设备
- 将你的手机对准屏幕的二维码以确认登录
以上内容由骨哥翻译并整理。
原文:https://lyra.horse/blog/2024/05/stealing-your-telegram-account-in-10-seconds-flat/