无需访问任何网站即可访问任何人的 Arc 浏览器 Gaining access to anyones Arc browser without them even visiting a website

原始链接: https://kibty.town/blog/arc/

在笔记应用程序 Arc 中,由于使用 Firestore,个人信息可能容易受到攻击。 对 Arc 的分析表明,它使用 Firestore 进行身份验证和存储用户首选项。 据观察,该应用程序在与 Firestore 通信时不遵守系统代理设置。 通过利用动态检测工具 Frida,创建了一个脚本来监控 Firestore 通信。 调查结果显示,诸如自动存档时间阈值、自动存档小弧时间阈值、每个配置文件的自动存档时间阈值、用户 ID 和用户引用 ID 等首选项都存储在 Firestore 中。 此外,还可以更改 Boosts 的创建者 ID,从而导致未经授权访问其他用户的 Boosts。 这一发现创建了一个攻击链,允许通过包含恶意 JavaScript 的特制 Boost 对受害者的设备造成潜在的危害。 用户 ID 可以通过用户推荐、发布 Boost 或共享 Eases 来获取,从而获得对其他用户设备进行未经授权的访问的其他方式。 尽管 Arc 最初拒绝参加标准错误赏金计划,但他们后来为这一关键发现提供了 2,000 美元的奖励。 针对报告的漏洞,Arc 已采取措施解决这些问题,包括消除对 Firestore 的依赖、采取措施防止在本地执行 Boost、审核现有的 Firestore 访问控制列表 (ACL) 规则以及进行内部和外部安全审核。 他们还打算建立一个正式的错误赏金计划。 调查期间出现了隐私问题,敏感数据(例如网站访问信息)在没有适当屏蔽或加密的情况下被发送到服务器。 不过,Arc 已确认他们将在未来的更新中采取适当的措施来解决这些问题。

In Arc, a note-taking application, personal information may be vulnerable due to its usage of Firestore. An analysis of Arc revealed that it uses Firestore for authentication and storing user preferences. It was observed that the application did not adhere to system proxy settings when communicating with Firestore. By utilizing Frida, a dynamic instrumentation tool, a script was created to monitor Firestore communication. The findings showed that preferences such as auto archive time thresholds, auto archive little arc time threshold, auto archive time thresholds per profile, user ids and user referral ids were being stored in Firestore. Additionally, it was possible to change the creator ID of Boosts, leading to unauthorized access to other users' Boosts. This discovery created an attack chain allowing for a potential compromise of a victim's device through a specially crafted Boost containing malicious JavaScript. User IDs can be obtained through user referrals, publishing Boosts, or by sharing Easels, resulting in additional ways to gain unauthorized access to other users' devices. Although Arc initially declined to participate in a standard bug bounty program, they later offered a reward of $2,000 USD for this critical finding. In response to the reported vulnerabilities, Arc has taken steps to address these issues, including removing reliance on Firestore, implementing measures to prevent executing Boosts locally, auditing existing Firestore Access Control List (ACL) rules, and conducting both internal and external security audits. They also intend to establish a formal bug bounty program moving forward. Privacy concerns were raised during the investigation, with sensitive data, such as site visited information, being sent to the server without proper masking or encryption. However, Arc has confirmed that they will be taking appropriate actions to remedy those issues in future updates.


we start at the homepage of arc. where i first landed when i first heard of it. i snatched a download and started analysing, the first thing i realised was that arc requires an account to use, why do they require an account?

introducing arcs cloud features

so i boot up my mitmproxy instance and i sign up, and i see that they are using firebase for authentication, but no other requests, are they really just using firebase only for authentication?

after poking around for a bit, i discovered that there was a arc featured called easels, easels are a whiteboard like interface, and you can share them with people, and they can view them on the web. when i clicked the share button however, there was no requests in my mitmproxy instance, so whats happening here?

hacking objective-c based firebase apps

from previous experience hacking an IOS based app, i immediately had a hunch on what this was, firestore.

firestore is a database-as-a-backend service that allows for developers to not care about writing a backend, and instead write database security rules and make users directly access the database.

this has of course sparked a lot of services having insecure or insufficient security rules and since researching that, i would like to call myself a firestore expert.

firestore has a tendency to not abide by the system proxy settings in the Swift SDK for firebase, so going off my hunch, i wrote a frida script to dump the relevant calls.

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");
  }
});

hacky script, but it works. so i launched arc with the script loaded on startup and this is what i got:

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");

sick. so it looks like arc stores some preferences in firestore, along with a basic user object, referrals and boosts

what the hell are arc boosts

arc boosts are a way for users to customize websites, by blocking elements, changing fonts, colors, and even using their own custom css and js.

do you see where this is going?, so, i manually logged into my account using my dummy page to test firebase accounts, and executed the exact same query to get my boosts:

cool, let me create a simple boost on google.com

hey! theres our boost, lets try changing some parameters around.

i see that it queries by creatorID, and we cant query a different creator ID than the original, but what if we update our own boost to have another users id?

well, i tried it with another account of mine, and this way the result when i went to google.com on the other computer (the victim one)

what the fuck? it works?

quick recap

  • arc boosts can contain arbitrary javascript
  • arc boosts are stored in firestore
  • the arc browser gets which boosts to use via the creatorID field
  • we can arbitrarily chage the creatorID field to any user id

thus, if we were to find a way to easily get someone elses user id, we would have a full attack chain

getting another users id

user referrals

when someone referrs you to arc, or you referr someone to arc, you automatically get their user id in the user_referrals table, which means you could just ask someone for their arc invite code and they'd likely give it

published boosts

you can share arc boosts (only if they don't have js in them) with other people, and arc has a public site with boosts, and boostSnapshots (published boosts) contain the user id of the creator.

user easels

arc has a feature called easels, which are basically whiteboards, you can share easels, and this also allows you to get someones user id.

putting it together

this would be the final attack chain:

  • obtain the user id of the victim via one of the mentioned methods
  • create a malicious boost with whatever payload you want on your own account
  • update the boost creatorID field to the targets
  • whenever the victim visits the targeted website, they will get compromised

the browser company normally does not do bug bounties (update: see at the end of post), but for this catastrophic of a vuln, they decided to award me with $2,000 USD

the timeline for the vulnerability:

  • aug 25 5:48pm: got initial contact over signal (encrypted) with arc co-founder hursh
  • aug 25 6:02pm: vulnerability poc executed on hursh's arc account
  • aug 25 6:13pm: added to slack channel after details disclosed over encrypted format
  • aug 26 9:41pm: vulnerability patched, bounty awarded
  • sep 6 7:49pm: cve assigned (CVE-2024-45489)

rce on priviliged pages

while poking around, i saw that boosts actually execute for other protocols aswell (even though you cant create them in the client), so someone could create a boost targeting the page settings, and it would execute on chrome://settings, which allows further escalation of priviliges.

privacy concerns

while researching, i saw some data being sent over to the server, like this query everytime you visit a site:

firebase
  .collection("boosts")
  .where("creatorID", "==", "UvMIUnuxJ2h0E47fmZPpHLisHn12")
  .where("hostPattern", "==", "www.google.com");

the hostPattern being the site you visit, this is against arc's privacy policy which clearly states arc does not know which sites you visit.

update

in light of these vulnerabilities and to introduce new features arc is switching off of firebase. additionally, arc has published their own write-up addressing these issues

a tldr version would be:

  • confirming they had fixed the issue
  • they are adding a feature to disable boosts in the client, preventing this vulnerability from happening on people that do not use boosts
  • they are doing an audit of their current firebase ACL rules internally
  • they have estabilished proper protocols for security issues

additionally, from internal discussions with arc they are also:

  • are fixing the mentioned privacy concerns in the v1.61.1 update
  • moving off firebase for new features and products
  • they are doing a external security audit for this version
  • are starting a bug bounty program for further vulnerabilities
相关文章
联系我们 contact @ memedata.com