白帽故事 · 2024年5月7日

10秒以内窃取你的Telegram帐户

背景介绍

假设你把手机递给我,10 秒钟以内能做出最糟糕的事情是什么?

file

某天,国外白帽小哥收到一条有趣的消息,其中包含了 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,点击了一些测试链接,并逐步查看代码后,最终来到了下面的断点:

file

这就是为什么之前找不到关键字的原因!此技巧适用的域名列表由 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,例如:

更多例子:

file

除了 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%3F​url%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=...&tgWeb​AuthToken=... )。

接下来的时间,白帽小哥一直在尝试 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 将从这里获取它,并使用令牌登录攻击者的一台设备。

攻击演示视频:

https://vimeo.com/941755175

即使对于技术水平较低的攻击者来说,这种攻击也非常容易实现,假设有高人已经设置了一个自定义域名,攻击者所需要知道的就是如何点击链接并在 URL 栏中添加一个字母,无需任何专业的工具,无需了解目标的任何信息,甚至都不需要电话…

那么 Telegram 应该如何做呢?

通过二维码登录Telegram:

  1. 在手机上打开Telegram
  2. 进入设置-设备-链接桌面设备
  3. 将你的手机对准屏幕的二维码以确认登录

以上内容由骨哥翻译并整理。

原文:https://lyra.horse/blog/2024/05/stealing-your-telegram-account-in-10-seconds-flat/