$9.7 万赏金背后的 Google 内部 API:firefly-pa 副本闸门两次裸奔

导语:赏金猎人 Abhishek(@aacle_)九月中旬连发两条 Google 漏洞报告,合计斩获 $97,604。攻击对象是同一个内部接口 firefly-pa.googleapis.com:第一次靠”伪造 worker 回调把任务从 NEW 推到 COMPLETE”拿到 $60k,三天后又用同一条思路配合”会泄露目的路径的失败响应”再撸 $37,604,掏出了 Google 员工的 /etc/passwd 和 root 的 /etc/shadow。


一、目标画像:firefly-pa 是什么

firefly-pa.googleapis.com 是 Google 内部的一个文件副本服务(Firefly Personal Archive 的缩写)。外部域名虽是 googleapis.com,但解析到的服务不在公开 API 网关后面——典型的”半个内部接口被误暴露在公网”。

接口里跑的核心逻辑只有两步:

  • 提交副本任务,把 A 路径文件拷到 B 路径。
  • worker 完成后回调一个内部调度端点,把任务状态从 NEW 推到 COMPLETE。

第一步和第二步都没做权限检查。任何拿到这个域名的外部用户都能调。攻击者最初的入口就是看到了 firefly-pa.googleapis.com 的 DNS 记录,把它当公开 API 一通乱扫才挖出来的。


二、漏洞一:调度信任伪造,$60,000

任务卡死的真因

提交副本任务后,服务端本应让 worker 异步执行,状态从 NEW → RUNNING → COMPLETE_SUCCESS。但 firefly-pa 的实现里有个老 bug:worker 回调失败后,调度端不会把任务标记成 FAILED,而是让它永远停留在 NEW。

副本任务永远停在 NEW

对运维来说这是延迟问题,对攻击者来说这是金矿——任务还在 NEW,意味着提交者随时可以再”补一个回调”。

伪造 worker 回调

攻击者没等真正的 worker,而是自己手动向调度端点发了这么一段 JSON:

{"taskId":"a030d1a000000000","status":{"status":"STATUS_COMPLETE_SUCCESS"}}

调度端没校验调用方身份:谁发 taskId + STATUS_COMPLETE_SUCCESS,它就信。结果副本任务直接被打上完成标记,文件从 A 拷到了 B。整个流程零认证、零鉴权、零速率限制。

bigstoreIdentifier 的真相

原以为 bigstoreIdentifier 字段只接受 Bigstore 路径。攻击者随手塞了个 /etc/passwd.borg 进去,结果副本服务居然真的去读——而且返回的内容爆了:百万行级别的 Google 员工账号列表。

事后 Google 评审认定这是 critical 级别,$60,000 入账。

任务 JSON 与执行流程(红箭头示意 taskId 链路)

三、漏洞二:错在”会说”,$37,604.40

副本能跑,但什么都看不见

三天后,Abhishek 找到了 firefly-pa 另一个端点:一个未鉴权的文件副本接口,body 长这样:

{"refFilename":"/etc/passwd"}

诡异的是——成功时它返回 {}。文件已经在服务器上拷好了,但调用方拿不到文件名、路径、任何提示。等同于”会读但看不见”。

这个端点成功时返回空对象

如果只到这里就放弃,这条线索一分钱不值。但攻击者做了两件关键的”错峰”动作。

第一步:故意读不存在的文件,让报错”开口说话”

他换成 /etc/shadow。读不到,接口这次不沉默了——返回里包含完整的目的路径、文件名命名格式、以及 worker 进程 ID。

等于把内部文件系统的”目录排布 + 命名规则”全抖了出来。

第二步:用 /batch 把副本锁在同台机器

firefly-pa 的 /batch 接口允许一次提交多个任务。Abhishek 把所有试探请求打包成一个 batch,强制调度把它们钉在同一台 worker 上。这样读出来的 worker ID 都是连续的,便于邻接推断。

第三步:枚举邻接 worker ID,77.1 万次暴力猜

worker ID 是顺序分配的连续号。他从报错里知道目的文件的 worker 编号是 N,那么 N-1、N+1、N+2 都是别人的文件名,只有一个”空缺”是他自己的副本。

时间戳规则 + 周期计数器 + 邻接 ID → 暴力枚举 771,000 个候选路径 → 服务器最终确认”这个路径就是你的文件”。

第四步:拿漏洞一的 read 能力兜底读

光知道路径还不够——成功响应仍是空 {}。但前一个 $60k 的 bug 还在,firefly 的 read API 能直接读 Bigstore 上的任意文件。两个 bug 一组合:

# 1. 漏洞二告诉你文件路径
oracle_search >> /tmp/verified_path.txt
# 2. 漏洞一从那条路径把 /etc/shadow 真的读回来
python3 firefly_read_poc.py /tmp/verified_path.txt
# ⚡ root:x:0:0:root:/root:/bin/bash
77.1 万次枚举后命中 + firefly 读取

第二个 bug 评审给出 $37,604.40,正好和第一个 bug 的 $60k 拉开一定等级。


四、为什么这两个洞都该被堵

两次赏金,根源都在 Google 内部接口的”宽松信任”上:

  • 调度端不验证 worker 回调的调用方身份 → 任务状态被任意伪造。
  • bigstoreIdentifier 字段没白名单 → 任意主机路径都能当 Bigstore key 用。
  • 失败响应回吐了完整目的路径 → 把内部文件系统排布免费送给外部。

任意一条独立都是 high,三个叠在一起,攻击者能从 0 到 root 一条直线走通。


五、红队视角的几个迁移启发

  • 内部域名暴露在公网(googleapis.com 下的 firefly-pa)是头号入口。建议先扫一遍目标公司的 .googleapis.com、.amazonaws.com、*.azurefd.net 看有没有不该外露的内部 API。
  • 任务/异步系统的状态机永远值得手工 fuzz:NEW/RUNNING/COMPLETE 之间的状态转换一旦缺少服务端权威校验,就能伪造推进。
  • 错误响应是金矿。/etc/shadow 失败时多说的那几句话比成功响应还值钱。
  • 多个 low/medium 漏洞叠起来常常等于 critical——漏洞二的”成功时返回空”单独看像是设计缺陷,配合漏洞一的任意文件读取就直逼 root。

© 版权声明
THE END
喜欢就支持一下吧
点赞5 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容