在无需访问网站的情况下获取浏览器的访问权限

admin 2025年3月3日15:10:41评论5 views字数 5608阅读18分41秒阅读模式

 

漏洞引发的原因是 Firebase(CVE-2024-45489)。

我们从 Arc 的主页开始,这也是我第一次听说它时打开的页面。我下载了安装包并开始分析,第一个发现是 Arc 需要用户登录才能使用。为什么他们需要账户?

介绍 Arc 的云功能

我启动了 mitmproxy 并注册了一个账户,发现他们使用 Firebase 进行身份验证,但没有其他请求。难道他们真的只用 Firebase 来做身份验证?

在探索了一会儿后,我发现 Arc 有一个名为Easels的功能,它类似于白板,可以与他人共享,并让他们在 Web 上查看。然而,当我点击“分享”按钮时,我的 mitmproxy 并没有捕获到任何请求——那么这里到底发生了什么?

攻击基于 Objective-C 的 Firebase 应用

根据我以往对 iOS 应用的渗透测试经验,我立刻有了一个猜测:这可能与 Firestore 相关。

Firestore 是一种 Database-as-a-Backend(数据库即后端)服务,允许开发者无需编写后端逻辑,而是通过数据库安全规则,直接让用户访问数据库。这种机制已经导致许多服务存在不安全或安全规则不足的问题。

此外,在 Firebase 的 Swift SDK 中,Firestore 往往不会遵循系统代理设置。因此,基于我的猜测,我编写了一个 Frida 脚本来捕获相关的 API 调用。

var documentWithPath = ObjC.classes.FIRCollectionReference["- documentWithPath:"];var queryWhereFieldIsEqualTo = ObjC.classes.FIRQuery["- queryWhereField:isEqualTo:"];var collectionWithPath = ObjC.classes.FIRFirestore["- collectionWithPath:"];function getFullPath(obj) { if (obj.path && typeof obj.path === "function") { return obj.path().toString(); } return obj.toString();}var queryStack = [];function logQuery(query) { var queryString = `firebase.${query.type}("${query.path}")`; query.whereClauses.forEach((clause) => { queryString += `.where("${clause.fieldName}", "==", "${clause.value}")`; }); console.log(queryString);}Interceptor.attach(documentWithPath.implementation, { onEnter: function (args) { var parent = ObjC.Object(args[0]); var docPath = ObjC.Object(args[2]).toString(); var fullPath = getFullPath(parent) + "/" + docPath; var query = { type: "doc", path: fullPath, whereClauses: [] }; queryStack.push(query); logQuery(query); },});Interceptor.attach(collectionWithPath.implementation, { onEnter: function (args) { var collectionPath = ObjC.Object(args[2]).toString(); var query = { type: "collection", path: collectionPath, whereClauses: [] }; queryStack.push(query); },});Interceptor.attach(queryWhereFieldIsEqualTo.implementation, { onEnter: function (args) { var fieldName = ObjC.Object(args[2]).toString(); var value = ObjC.Object(args[3]).toString(); if (queryStack.length > 0) { var currentQuery = queryStack[queryStack.length - 1]; currentQuery.whereClauses.push({ fieldName: fieldName, value: value }); } }, onLeave: function (retval) {},});var executionMethods = [ "- getDocuments", "- addSnapshotListener:", "- getDocument", "- addDocumentSnapshotListener:", "- getDocumentsWithCompletion:", "- getDocumentWithCompletion:",];executionMethods.forEach(function (methodName) { if (ObjC.classes.FIRQuery[methodName]) { Interceptor.attach(ObjC.classes.FIRQuery[methodName].implementation, { onEnter: function (args) { if (queryStack.length > 0) { var query = queryStack.pop(); logQuery(query); } }, }); }});function formatFirestoreData(data) { if (data.isKindOfClass_(ObjC.classes.NSDictionary)) { let result = {}; data.enumerateKeysAndObjectsUsingBlock_( ObjC.implement(function (key, value) { result[key.toString()] = value.toString(); }) ); return JSON.stringify(result); } return data.toString();}var documentMethods = [ { name: "- updateData:completion:", type: "update" }, { name: "- updateData:", type: "update" }, { name: "- setData:completion:", type: "set" }, { name: "- setData:", type: "set" },];documentMethods.forEach(function (method) { if (ObjC.classes.FIRDocumentReference[method.name]) { Interceptor.attach( ObjC.classes.FIRDocumentReference[method.name].implementation, { onEnter: function (args) { var docRef = ObjC.Object(args[0]); var data = ObjC.Object(args[2]); var fullPath = getFullPath(docRef); var formattedData = formatFirestoreData(data); console.log( `firebase.doc("${fullPath}").${method.type}(${formattedData})` ); }, } ); } else { console.log("Warning: " + method.name + " not found"); }});

虽然是个临时脚本,但它有效。于是,我在启动时加载了该脚本并运行 Arc,结果如下:

firebase.doc("preferences/UvMIUnuxJ2h0E47fmZPpHLisHn12");firebase.doc( "preferences/UvMIUnuxJ2h0E47fmZPpHLisHn12/stringValues/autoArchiveTimeThreshold");firebase.doc("preferences/UvMIUnuxJ2h0E47fmZPpHLisHn12");firebase.doc( "preferences/UvMIUnuxJ2h0E47fmZPpHLisHn12/stringValues/autoArchiveLittleArcTimeThreshold");firebase.doc("preferences/UvMIUnuxJ2h0E47fmZPpHLisHn12");firebase.doc( "preferences/UvMIUnuxJ2h0E47fmZPpHLisHn12/stringValues/autoArchiveTimeThresholdsPerProfile");firebase.doc("users/UvMIUnuxJ2h0E47fmZPpHLisHn12");firebase .collection("user_referrals") .where("inviter_id", "==", "UvMIUnuxJ2h0E47fmZPpHLisHn12");firebase .collection("boosts") .where("creatorID", "==", "UvMIUnuxJ2h0E47fmZPpHLisHn12");

看起来 Arc 在 Firestore 中存储了一些偏好设置,以及一个基本的用户对象、推荐信息和 Boosts。

Arc Boosts 到底是什么?

Arc Boosts 允许用户自定义网站,比如屏蔽元素、更改字体、颜色,甚至使用自定义 CSS 和 JS。

你明白这意味着什么了吗? 于是,我手动登录到我的账户,使用我的测试 Firebase 账户页面,并执行了完全相同的查询来获取我的 Boosts:

在无需访问网站的情况下获取浏览器的访问权限img

酷,让我在 google.com 上创建一个简单的 Boost。

在无需访问网站的情况下获取浏览器的访问权限img

嘿!我们的 Boost 出现了,让我们尝试更改一些参数。

我发现它是通过 creatorID 进行查询的,我们无法查询不同的 creatorID,只能查询自己的。但如果我们把自己的 Boost 更新为另一个用户的 ID 会发生什么?

于是,我用我的另一个账户尝试了这个方法,然后当我在另一台电脑(受害者设备)上访问 Google.com 时,得到了如下结果:

在无需访问网站的情况下获取浏览器的访问权限img

什么鬼?这真的有效?

快速回顾

    Arc Boosts 可以包含任意 JavaScript 代码。
    Arc Boosts 存储在 Firestore 中。
    Arc 浏览器通过 creatorID 字段获取需要使用的 Boosts。
    我们可以随意更改 creatorID 字段为任何用户的 ID。
因此,如果我们能轻松获取某个用户的 ID,就能构建完整的攻击链。

获取其他用户的 ID

用户推荐

当有人推荐你使用 Arc,或者你推荐别人使用 Arc 时,你的用户 ID 会自动存储在 user_referrals 表中。这意味着你只需要向某人索取他们的 Arc 邀请码,他们很可能会提供给你。

已发布的 Boosts

你可以与其他人分享 Arc Boosts(前提是它们不包含 JavaScript)。Arc 甚至有一个公开网站[1],其中的 boostSnapshots(已发布的 Boosts)包含了创建者的用户 ID。

用户 Easels

Arc 还有一个名为 Easels 的功能,基本上是一个共享白板。你可以与他人共享 Easels,这也能让你获取某人的用户 ID。

整合攻击链

最终的攻击流程如下:
通过上述方法之一获取受害者的用户 ID。
在自己的账户上创建一个包含任意有效负载的恶意 Boost。
更新 Boost 的 creatorID 字段,将其改为目标用户的 ID。
当受害者访问被攻击的网站时,他们的浏览器会自动加载并执行恶意代码。

在特权页面上实现 RCE(远程代码执行)

在进一步研究时,我发现 Boosts 其实可以在其他协议页面上执行(尽管无法在客户端创建它们)。 这意味着攻击者可以创建一个 Boost 目标指向 settings,它会在 chrome://settings 页面上执行,从而进一步提升权限。

隐私问题

在研究过程中,我发现浏览器会向服务器发送一些数据,例如每次访问网站时都会触发如下查询:
firebase .collection("boosts") .where("creatorID", "==", "UvMIUnuxJ2h0E47fmZPpHLisHn12") .where("hostPattern", "==", "www.google.com");
hostPattern 代表你访问的网站,这实际上违反了 Arc 的隐私政策[2],因为该政策明确声明 Arc 不会追踪用户访问的网站。

References

[1] 公开网站: https://arc.net/boosts
[2] Arc 的隐私政策: https://arc.net/privacy

声明:⽂中所涉及的技术、思路和⼯具仅供以安全为⽬的的学习交流使⽤,任何⼈不得将其⽤于⾮法⽤途以及盈利等⽬的,否则后果⾃⾏承担。

原文始发于微信公众号(白帽子左一):在无需访问网站的情况下获取浏览器的访问权限

 

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年3月3日15:10:41
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   在无需访问网站的情况下获取浏览器的访问权限https://cn-sec.com/archives/3789280.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息