用AI入侵谷歌,获得50万美元赏金【上】

admin 2026-06-18 06:58:12 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详细介绍了作者如何通过AI驱动的自动化方法对谷歌API进行大规模安全测试。核心方法包括收集API密钥(通过爬取AndroidAPK、拦截网络流量)、验证GCP项目归属、扫描发现文档(利用可见性标签绕过限制)以及实现第一方认证(FPAv2)。最终通过结合历史文档获取1500+API端点,为后续模糊测试奠定基础,并强调所有操作均在谷歌漏洞赏金计划合规框架内进行。 综合评分: 87 文章分类: 漏洞分析,WEB安全,红队,实战经验,安全工具


cover_image

用AI入侵谷歌,获得50万美元赏金【上】

原创

骨哥说事 骨哥说事

骨哥说事

2026年6月17日 10:17 上海

在小说阅读器读本章

去阅读

| | | — | | 声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由用户承担全部法律及连带责任,文章作者不承担任何法律及连带责任。 |

#

#

防走失:https://gugesay.com/

不想错过任何消息?设置星标↓ ↓ ↓

#

在2025年10月受邀参加 bugSWAT Mexico 之后,我发现自己再次被谷歌研究吸引了。尽管我已经专注于其他项目数月,但团队愿意让研究人员一窥谷歌源代码的做法,重新点燃了我探索谷歌攻击面的兴趣。

过去一年里,我一直在用Claude构建一些小项目,我意识到利用AI大规模、自动地模糊测试谷歌API,存在尚未开发的潜力。这种方法的关键?谷歌的发现文档。对于不熟悉的人来说,我建议阅读 我的另一篇文章 以深入了解,但这里快速回顾一下:

发现文档本质上是谷歌版本的Swagger文档——它们是机器可读的API规范,列出了所有可用的端点、参数和方法。虽然像 YouTube数据API 这样的API有公开文档,但谷歌的内部API(如内部人员API)也存在这类文档。有些发现文档可以 公开访问,而大多数则 需要有效的API密钥。

以下是一个来自YouTube数据API发现文档的示例:

...
 "liveChatModerators": {
    "methods": {
    "insert": {
        "flatPath": "youtube/v3/liveChat/moderators",
        "description": "Inserts a new resource into this collection.",
        "httpMethod": "POST",
        "parameters": {
        "part": {
            "description": "The *part* parameter serves two purposes in this operation. It identifies the properties that the write operation will set as well as the properties that the API response returns. Set the parameter value to snippet.",
            "repeated": true,
            "required": true,
            "location": "query",
            "type": "string"
        }
...

收集API密钥

要访问大多数发现文档,你需要一个有效的API密钥。API密钥几乎嵌入在每一个谷歌应用和服务中,但关键在于,从一个服务中找到的API密钥通常会为其Google Cloud Platform (GCP) 项目启用多个其他API。这意味着,尽可能多地收集密钥将使我们可以访问众多的谷歌API。在密钥收集环节,我和我的朋友 Michael 进行了合作。

我们采取了一种彻底的方法。我们爬取了 超过60,000个Android APK文件(每个谷歌应用发布的每个版本的所有版本),解包它们,并用 grep 命令搜索API密钥。

user@siege:/mnt/data/apks$ ls -1 | wc -l
61200

我们使用 Chrome调试器API 构建了一个Chrome扩展,以便拦截网络流量,然后系统性地访问了所有已知的谷歌Web域名(超过2800个),并使用每个可能的Web应用功能来捕获实时请求中的密钥。

我们还解密了所有我们能获取的谷歌IPA文件,并分析了 我们能找到的任何谷歌二进制文件。

为了将范围控制在谷歌漏洞赏金计划 (VRP) 内,并移除非谷歌的API密钥(来自第三方GCP项目的密钥),我用了在Cloud Marketplace API中发现的一个有趣端点。首先,我们需要与密钥GCP项目关联的项目编号,当一个密钥用于调用一个它未启用的谷歌API时,返回的错误消息会暴露这个项目编号。例如,获取 [https://protos.googleapis.com/discovery/rest?key=AIzaSyDWUi9T78xEO-m10evQANR7TMSiB_bjyNc) 会返回错误:Protos API has not been used in project 244648151629 before,从而揭示了项目编号。

Cloud Marketplace端点接收这个项目编号并返回项目信息:

GET /v1test/infoSharing/test/test/1044708746243 HTTP/2
Host: cloudmarketplace.clients6.google.com
Cookie:&nbsp;<已隐藏>
Authorization:&nbsp;<已隐藏>
Origin: https://console.cloud.google.com
X-Goog-Api-Key: AIzaSyDWUi9T78xEO-m10evQANR7TMSiB_bjyNc

1044708746243 是目标项目编号。

响应如下:

HTTP/2 200 OK
Content-Type: application/json; charset=UTF-8

{
&nbsp;&nbsp;"company":&nbsp;"google.com",
&nbsp;&nbsp;"email":&nbsp;"[email protected]",
&nbsp;&nbsp;"name":&nbsp;"GVRP Test2"
}

email 和 name 对应的是我认证的谷歌账户,但 company 字段是 与我们提供的GCP项目编号绑定的域名。通过该端点检查与所有密钥相关的所有GCP项目,我们可以过滤掉非谷歌的API密钥,只需简单地丢弃那些不是来自 google.com 项目(或其他收购的域名,如 nest.comfitbit.comwing.com)的密钥。

收集了API密钥后,下一步是找到要扫描的所有谷歌API域名。我结合使用了Chrome扩展记录的域名、使用关键词暴力生成的域名以及 证书透明度日志。为了验证一个域名是否是活跃的谷歌API,我会发出以下请求:

GET / HTTP/2
Host: people-pa.googleapis.com

然后我会检查响应头中的 Server 字段:

HTTP/2 404 Not Found
Date: Mon, 16 Feb 2026 08:46:31 GMT
Content-Type: text/html; charset=UTF-8
Server: ESF

如果这个响应头存在(通常是 ESFGSE 或 scaffolding on HTTPServer2),那么它就是一个有效的、活跃且能响应请求的谷歌API服务。

扫描发现文档

有了有效的API密钥和活跃的谷歌API域名列表后,我开始大规模扫描开放的发现文档。2025年7月,谷歌从他们的大多数API中移除了 /$discovery/rest 路径,但如果你足够聪明,在某些情况下可以绕过这一点。

还有另一层复杂性。正如我之前文章中所介绍的,某些谷歌云项目启用了可见性标签,这使得它们可以访问隐藏的端点,除非提供了 labels 参数,否则这些端点不会出现在发现文档中。例如,如果不提供标签获取服务管理API的发现文档:

GET /$discovery/rest HTTP/2
Host: serviceusage.googleapis.com
X-Goog-Api-Key: AIzaSyDWUi9T78xEO-m10evQANR7TMSiB_bjyNc

响应大小为253k字节。然而,使用 ?labels=GOOGLE_INTERNAL 参数时:

GET /$discovery/rest?labels=GOOGLE_INTERNAL HTTP/2
Host: serviceusage.googleapis.com
X-Goog-Api-Key: AIzaSyDWUi9T78xEO-m10evQANR7TMSiB_bjyNc

响应增长到 329k字节,揭示了明显更多的隐藏文档。关键在于,labels 参数一次只接受一个标签。这意味着需要在所有发现的API上,为每个API密钥测试每个已知的标签。请求量非常巨大,但这是揭露隐藏在可见性标签背后的端点的唯一方法。

完成这一切后,我能够获取到超过1500个API的发现文档。将这些文档与我 过去研究 中存档的发现文档结合起来,我准备开始用AI自动模糊测试这些API了。

认证

我们通过API密钥处理好了授权,但许多端点还需要 认证凭据 来识别是哪个谷歌账户在调用API。如果你尝试使用 Bearer认证 并配合API密钥,你会收到一个不匹配的错误,因为Bearer令牌本身也是与GCP项目绑定的:

{
&nbsp;&nbsp;"error": {
&nbsp; &nbsp;&nbsp;"code":&nbsp;400,
&nbsp; &nbsp;&nbsp;"message":&nbsp;"The API Key and the authentication credential are from different projects.",
&nbsp; &nbsp;&nbsp;"status":&nbsp;"INVALID_ARGUMENT",
&nbsp; &nbsp; ...
&nbsp; }
}

使用Bearer认证没有已知的绕过方法。即使你使用 X-Goog-User-Project: <project_number>,它也会验证你认证的账户在该GCP项目中是否拥有 roles/serviceusage.serviceUsageConsumer 角色。如果你有办法,请 告诉我。

然而,许多API支持谷歌专有的第一方认证 (FPA),它确实可以与API密钥一起工作。如果你观察过谷歌API在Web上是如何工作的:

POST /v1/items:get?key=AIzaSyD_InbmSFufIEps5UAt2NmB_3LvBH3Sz_8 HTTP/3
Host: drivefrontend-pa.clients6.google.com
Cookie:&nbsp;<已隐藏>
Content-Type: application/json+protobuf
Authorization: SAPISIDHASH&nbsp;<已隐藏>&nbsp;SAPISID1PHASH&nbsp;<已隐藏>&nbsp;SAPISID3PHASH&nbsp;<已隐藏>
X-Goog-Authuser: 0
Origin: https://drive.google.com
Referer: https://drive.google.com/

请求中包含了谷歌账户会话 Cookie,以及一个根据该Cookie计算出的 Authorization 值。它们也被发送到主机名 *.clients6.google.com 而非 *.googleapis.com。关于这一点有一篇著名的 Stack Overflow帖子,但它并未涵盖全部情况。许多像 drivefrontend-pa.googleapis.com 这样的API需要一个更完整的谷歌FPA v2授权头版本,该版本在哈希值中嵌入了诸如电子邮件地址之类的用户标识符。

幸运的是,Michael发现谷歌有段时间意外泄露了源映射,网址是 https://android-review.googlesource.com/q/status:open+-is:wip,这让我们能够看到谷歌内部 gapix 库的前端源代码,其中包含了用于生成FPA v2授权头的代码。

你可以在此处找到完整文件 这里。

新的FPA系统 (v2) 工作方式如下。三个用户标识符可以被包含在哈希计算中:

&nbsp;* @param {?Array<{key:string,value:string}>=} opt_userIdentifiers 一个
&nbsp;* {key:, value:} 对象数组,其中&nbsp;'key'&nbsp;表示:
&nbsp;*&nbsp;<ul>'e': 表示相应的 'value' 是用户的电子邮件地址</ul>
&nbsp;*&nbsp;<ul>'u': 表示相应的 'value' 是用户的
&nbsp;* &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;焦点混淆后的Gaia ID</ul>
&nbsp;*&nbsp;<ul>'a': 表示相应的 'value' 是用户账户的
&nbsp;* &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;应用域 (仅限 dasher 账户需要)</ul>

然后生成令牌:

// 提取标识符键(例如 "e", "u", "a")和值(电子邮件,gaia id,域)
goog.array.forEach(userIdentifiers,&nbsp;function&nbsp;(element, index, array)&nbsp;{
&nbsp; suffix.push(element["key"]); &nbsp; &nbsp; &nbsp; &nbsp;// ["e", "u"] -> "eu"
&nbsp; identifiers.push(element["value"]);&nbsp;// ["[email protected]", "ABC123"]
});

// 获取当前Unix时间戳
const&nbsp;timestamp =&nbsp;Math.floor(newDate().getTime() /&nbsp;1000);

// 构建SHA1输入: "email:gaiaId timestamp sessionCookie origin"
if&nbsp;(goog.array.isEmpty(identifiers)) {
&nbsp; sha1Parts = [timestamp, sessionCookie, origin];
}&nbsp;else&nbsp;{
&nbsp; sha1Parts = [identifiers.join(":"), timestamp, sessionCookie, origin];
}

// 计算以空格连接的各部分的SHA1哈希值
const&nbsp;sha1 = gapix.auth_firstparty.tokencrafter.computeSha1_(
&nbsp; sha1Parts.join(" ")
);

// 最终令牌: "timestamp_sha1hash_identifierKeys" 例如 "1739700391_abc123def_eu"
const&nbsp;tokenParts = [timestamp, sha1];
if&nbsp;(!goog.array.isEmpty(suffix)) {
&nbsp; tokenParts.push(suffix.join(""));
}
return&nbsp;tokenParts.join("_");

Gaia代表 “Google Accounts and ID Administration”。每个谷歌账户都有一个连续的 未混淆的Gaia ID,例如131337133377,以及一个更长的标识符,即 焦点混淆后的Gaia ID,它看起来像101189998819991197253。

所以最终令牌的格式是 <timestamp>_<hash>_<identifier_keys>。例如,一个 Google Workspace用户(内部称为 dasher)的令牌可能看起来像 1739700391_abc123def456_eua,其中 eua 表示哈希值是使用电子邮件、混淆后的Gaia ID和Google Workspace域计算的。哈希中使用的来源是 Origin 头的值(例如 https://drive.google.com)。

一个有趣的事实:只有三种可能的用户标识符键:u 代表混淆后的Gaia ID,e 代表电子邮件,a 代表Google Workspace域。如果你指定其他字母,API后端会直接忽略它们。因此,实际上可以生成包含任意字符串的有效授权头——例如 <timestamp>_<hash>_googlesauthteamhatesthisoneweirdtrick

来源白名单

这里的 Origin 头值很重要。

此头部由网络浏览器自动添加,表示当前页面的协议/主机,格式类似 Origin: <scheme>://<hostname>[:<port>]

许多API有一个所谓的”来源白名单”。如果你使用一个不在白名单内的来源,你会收到一个误导性的错误,例如:

{
&nbsp;&nbsp;"error": {
&nbsp; &nbsp;&nbsp;"code":&nbsp;401,
&nbsp; &nbsp;&nbsp;"details": [
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"@type":&nbsp;"type.googleapis.com/google.rpc.ErrorInfo",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"domain":&nbsp;"googleapis.com",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"metadata": {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"cookie":&nbsp;"UNKNOWN",
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"method":&nbsp;"google.internal.businessprocess.v1.BusinessProcess.GetIssue",
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"service":&nbsp;"businessprocess-pa.googleapis.com"
&nbsp; &nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"reason":&nbsp;"SESSION_COOKIE_INVALID"
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; ],
&nbsp; &nbsp;&nbsp;"message":&nbsp;"Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
&nbsp; &nbsp;&nbsp;"status":&nbsp;"UNAUTHENTICATED"
&nbsp; }
}

这并意味着你的Cookie无效,而是意味着你使用的来源不在白名单内。来源白名单没有在任何地方记录,但使用 我在上次报告中发现的原型(proto)泄露错误,我检查了 gaia_mint.AllowedFirstPartyAuth 的原型定义:

syntax =&nbsp;"proto3";

package&nbsp;gaia_mint;

message AllowedFirstPartyAuth {
enum&nbsp;FirstPartyOriginEnforcementLevel {
&nbsp; &nbsp; UNKNOWN =&nbsp;0;
&nbsp; &nbsp; MONITORING_ONLY =&nbsp;1;
&nbsp; &nbsp; PRODUCTION_ORIGINS_ONLY =&nbsp;2;
&nbsp; &nbsp; ENFORCE_ALL =&nbsp;3;
&nbsp; }

&nbsp; bool allow_insecure =&nbsp;1;
&nbsp; bool allow_insecure_pvt =&nbsp;2;
&nbsp; bool legacy_allow_all_origins =&nbsp;3;
&nbsp; FirstPartyOriginEnforcementLevel enforcement_level =&nbsp;4;
&nbsp; repeated AllowedFirstPartyAuthOriginRule allowed_origin_rule =&nbsp;5;
&nbsp; repeated string skip_origin_check_for_test_user =&nbsp;6;
&nbsp; repeated string include_named_origin_rule_list =&nbsp;7;
}

message AllowedFirstPartyAuthOriginRule {
&nbsp; string origin =&nbsp;1;
&nbsp; bool is_country_domain_prefix =&nbsp;2;

&nbsp; oneof mutual_exclusive_options {
&nbsp; &nbsp; bool is_sharded_domain =&nbsp;3;
&nbsp; &nbsp; bool allow_subdomains =&nbsp;4;
&nbsp; }
}

这让我们更深入地了解了谷歌内部如何处理来源验证。我们可以看到有不同的执行级别以及对子域通配符的支持。允许所有来源的API很可能使用了 legacy_allow_all_origins

API密钥限制

然而,我遇到的一个问题是某些密钥有特定的头部限制。

有四种不同类型的限制:服务器、浏览器、安卓和iOS。这些限制任何人在他们自己的GCP项目密钥上也可以设置,如文档 https://docs.cloud.google.com/api-keys/docs/add-restrictions-api-keys 所述。

你可以在谷歌的 error_reason 原型 中看到这些限制的定义:

// 定义了 `google.rpc.ErrorInfo.reason` 在
// `googleapis.com` 错误域下的支持值。该错误域是为 [Service
// Infrastructure](https://cloud.google.com/service-infrastructure/docs/overview) 保留的。
enum ErrorReason {
&nbsp; ...
&nbsp; // 请求被拒绝,因为它违反了 [API key HTTP
&nbsp; // restrictions](https://cloud.google.com/docs/authentication/api-keys#adding_http_restrictions) 。
&nbsp; API_KEY_HTTP_REFERRER_BLOCKED = 7;

&nbsp; // 请求被拒绝,因为它违反了 [API key IP address
&nbsp; // restrictions](https://cloud.google.com/docs/authentication/api-keys#adding_application_restrictions) 。
&nbsp; API_KEY_IP_ADDRESS_BLOCKED = 8;

&nbsp; // 请求被拒绝,因为它违反了 [API key Android application
&nbsp; // restrictions](https://cloud.google.com/docs/authentication/api-keys#adding_application_restrictions) 。
&nbsp; API_KEY_ANDROID_APP_BLOCKED = 9;

&nbsp; // 请求被拒绝,因为它违反了 [API key iOS application
&nbsp; // restrictions](https://cloud.google.com/docs/authentication/api-keys#adding_application_restrictions) 。
&nbsp; API_KEY_IOS_APP_BLOCKED = 13;
&nbsp; ...
}

服务器 限制使用IP地址白名单(无法绕过),但我们发现实际使用这种类型限制的密钥非常少。

对于 浏览器 限制,需要一个正确的HTTP Referer (是的,这个单词拼写错误,参见 词源)头部:

GET /v1/operations HTTP/2
Host: servicemanagement.googleapis.com
X-Goog-Api-Key: AIzaSyAEEV0DrpoOQdbb0EGfIm4vYO9nEwB87Fw
Referer: https://vrptest.google.com

某些密钥,比如这一个,允许通配符 *.google.com

与此相关的棘手之处在于,你不能提供不匹配的 Referer 和 Origin 头部。因此,如果一个端点有来源白名单,你需要找到一个匹配的Referer和Origin才能使用该API。

另一方面,iOS 只需要正确的 X-Ios-Bundle-Identifier 头部:

GET /v1/operations HTTP/2
Host: servicemanagement.clients6.google.com
X-Goog-Api-Key: AIzaSyBwu1q5p-HA745oE-YssxrrKu4UjaHv-7o
X-Ios-Bundle-Identifier: com.google.GoogleMobile

最后,安卓 限制需要两个匹配的头部,X-Android-Package(Android应用的包名)和 X-Android-Cert(SHA-1签名证书指纹):

GET /v1/operations HTTP/2
Host: servicemanagement.clients6.google.com
X-Goog-Api-Key: AIzaSyAHYc-Xn7pR1bXTPACJcTF90qOf-YaBGqA
X-Android-Package: com.google.android.settings.intelligence
X-Android-Cert: dd5fe97609b3615afaa64c0fb41427db07151066

在API密钥收集过程中,我们确保存储了所有这些值,并将暴力尝试这些值的过程整合到了同一个程序中。

另一个有趣的事情是,使用 *.corp.google.com 作为第一方认证的来源头部没有任何限制。例如:

GET /contentmanager/v1/item_paths HTTP/2
Host: contentmanager.clients6.google.com
Cookie:&nbsp;<已隐藏>
Authorization:&nbsp;<已隐藏>
Origin: https://coco.corp.google.com
X-Goog-Api-Key: AIzaSyBOh-LSTdP2ddSgqPk6ceLEKTb8viTIvdw

此API只允许来自以下来源头部的调用:

  • https://coco.corp.google.com
  • https://connect.corp.google.com
  • https://redbull.corp.google.com
  • https://redwood.corp.google.com

以及这些的预发布/开发变体(例如 https://connect-staging.corp.google.com)。

有趣的事实:如果一个API只允许 *.corp.google.com 来源,它很可能是一个内部API,本不应公开暴露,很可能存在漏洞。这个特定的API用于管理 support.google.com 的内容/工作流,当时存在一个访问控制漏洞,获得了 $9,000 的赏金。

以下是谷歌API请求完整生命周期的清晰图示:

[1] 请求命中 *.googleapis.com
&nbsp; &nbsp; &nbsp;|
&nbsp; &nbsp; &nbsp;v
[2] 方法解析
&nbsp; &nbsp; &nbsp;- 404, Content-Type: text/html; charset=UTF-8 (如果方法不存在,这是响应)
&nbsp; &nbsp; &nbsp;|
&nbsp; &nbsp; &nbsp;v
[3] 提供的 Content-Type 已为服务配置
&nbsp; &nbsp; &nbsp;- 400, "JSPB is not configured for service 'preprod-nestauthproxyservice-pa.sandbox.googleapis.com'."
&nbsp; &nbsp; &nbsp;|
&nbsp; &nbsp; &nbsp;v
[4] API密钥有效 & 为此API启用
&nbsp; &nbsp; &nbsp;- 400, reason: API_KEY_INVALID
&nbsp; &nbsp; &nbsp;- 403, "API key not valid."
&nbsp; &nbsp; &nbsp;- 403, "API key is expired"
&nbsp; &nbsp; &nbsp;- 403, "Pulse Private API has not been used in project 41614776383..."
&nbsp; &nbsp; &nbsp;- 403, "...doesn't allow unregistered callers..."
&nbsp; &nbsp; &nbsp;- 403, "...missing a valid API key"
&nbsp; &nbsp; &nbsp;|
&nbsp; &nbsp; &nbsp;| &nbsp; 约50%发往预发布环境的请求[4]和[5]会互换顺序
&nbsp; &nbsp; &nbsp;v
[5] API密钥限制
&nbsp; &nbsp; &nbsp;- 403, "Requests from this Android client application&nbsp;<empty>&nbsp;are blocked."
&nbsp; &nbsp; &nbsp;- 403, "Requests from this iOS client application&nbsp;<empty>&nbsp;are blocked."
&nbsp; &nbsp; &nbsp;- 403, "Requests from referer https://console.cloud.google.com are blocked."
&nbsp; &nbsp; &nbsp;|
&nbsp; &nbsp; &nbsp;v
[6] 认证凭据有效性
&nbsp; &nbsp; &nbsp;- 401, "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project."
&nbsp; &nbsp; &nbsp;- 401, reason: ACCESS_TOKEN_SCOPE_INSUFFICIENT
&nbsp; &nbsp; &nbsp;|
&nbsp; &nbsp; &nbsp;v
[7] 第一方认证来源在白名单内 (仅在发送FPA Cookie时)
&nbsp; &nbsp; &nbsp;- 401, reason: SESSION_COOKIE_INVALID, metadata.cookie: "UNKNOWN"
&nbsp; &nbsp; &nbsp;|
&nbsp; &nbsp; &nbsp;v
[8] API密钥项目 == 承载器令牌项目 (仅在同时发送密钥+承载器令牌时)
&nbsp; &nbsp; &nbsp;- 400, "The API Key and the authentication credential are from different projects."
&nbsp; &nbsp; &nbsp;|
&nbsp; &nbsp; &nbsp;v
[9] 可见性标签
&nbsp; &nbsp; &nbsp;- 404, Content-Type: application/json, "Method not found."
&nbsp; &nbsp; &nbsp;|
&nbsp; &nbsp; &nbsp;v
[10] 方法对调用者的GCP项目被阻止
&nbsp; &nbsp; &nbsp;- 403, "Requests to this API preprod-nestauthproxyservice-pa.sandbox.googleapis.com method nest.security.authproxy.v1.NestSecurityAuthproxyService.LookUpByNestId are blocked."
&nbsp; &nbsp; &nbsp;|
&nbsp; &nbsp; &nbsp;v
&nbsp; &nbsp; ...
&nbsp; &nbsp; &nbsp;|
&nbsp; &nbsp; &nbsp;v
[N] 请求由应用服务器处理

我围绕这个流程图构建了一个程序。对于每个 (API密钥, API) 组合,它会向一个已知方法发送一个探测请求,并根据是哪一步拒绝了请求(或者如果通过了第[4]步则为”通过”)来对响应进行分类。在所有密钥对所有API运行此程序后,我得到了一个启用矩阵,显示了哪些密钥实际上对哪些API有效,以及每种组合所需的有效来源头部和密钥限制头部。

构建我自己的API Explorer(API探索器)

谷歌有一个叫做 API Explorer 的工具,它在后台使用发现文档来让你测试任何API请求并查看响应。这对于测试公共API非常有用。这个API Explorer 曾经是开源的,但现在已经不是了。这是个问题,因为公共API Explorer只适用于公共API,而不适用于私有/内部API。而且这些探索器页面是在服务器端生成的,所以你无法直接在客户端中替换不同的发现文档。

考虑到这一点,以及需要集成FPA v2的需求,我决定构建自己的API Explorer。这花了大约一周时间,但成果是一个可以在客户端解析任何发现文档,并使用我自己的库通过FPA执行请求的工具。前端使用发现文档中定义的结构自动构建有效的请求/响应JSON。最终结果是一个UI界面,我可以快速测试任何针对API的有效负载并查看它如何响应。

这是我的工具的一个小型交互式演示,试试点击”播放”按钮!这个端点是一个访问控制漏洞,泄露了 assignedTams(技术客户经理),获得了 $6,000 的赏金。

AI登场

现在是时候开始用AI自动模糊测试这些API了。我的目标是自动发现基本的访问控制问题,然后我可以手动将其升级为更严重的漏洞。事实上,我在 上一个报告 中发现的RCE漏洞,最初就是AI报告的一个线索。

我取用在前端解析请求/响应JSON的相同代码,并将其作为 MCP(模型上下文协议) 工具提供给AI,提供了它像人类一样测试API所需的一切。

初始方法

最初,我只向AI提供了两个工具:probe_api 和 report_vulnerability。后者会让任何报告的漏洞出现在我的前端以供审查。我每对一个API运行一次”渗透测试”,让AI去探索。

然而,我发现AI并没有彻底测试所有内容。它会在几次探测后提前退出。为了防止这种情况,我使用了一个 Ralph Wiggum循环,并且只允许AI通过调用 confirm_testing_complete() 来完成测试。这个工具会验证在让AI结束之前,每个端点都至少有一次探测调用。

即使这样,AI仍然没有达到我想要的那么彻底。我还在初始上下文中提供了大量带有注释的请求/响应JSON转储,这很快就消耗了所有可用的上下文大小。我需要一种不同的方法。

基于组的分类

我将策略改为首先让AI将所有端点按逻辑分组进行分类:

[
&nbsp; {
&nbsp; &nbsp;&nbsp;"group_name":&nbsp;"APK元数据与权限分析",
&nbsp; &nbsp;&nbsp;"group_description":&nbsp;"管理APK信息、许可认证和基于文本搜索的端点。",
&nbsp; &nbsp;&nbsp;"group_rationale":&nbsp;"这些端点提供了检索APK技术细节的主要接口。集中测试可以查找搜索结果中的数据泄漏以及证书/权限查询上的IDOR。",
&nbsp; &nbsp;&nbsp;"methods": [
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"method_id":&nbsp;"androidpartner.apks.get",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"definition_hash":&nbsp;"4462fbad195536db",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"classified_at":&nbsp;"2026-01-25T11:18:52.028788+00:00"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"method_id":&nbsp;"androidpartner.apks.submissions.create",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"definition_hash":&nbsp;"0bbeeacafb51a2a5",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"classified_at":&nbsp;"2026-01-25T11:18:52.093755+00:00"
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; ...
&nbsp; &nbsp; ]
&nbsp; }
]

现在,每一次”渗透测试”聚焦于一个特定的组,而不是整个API。之前组的发现会与同一API中后续组共享。还会提供一个”超出范围”的端点列表,以及初始提示中范围内端点的文档。

如果AI想要调用一个超出范围的端点,它必须首先使用 get_endpoint_context 来检索请求/响应JSON模式。只有在调用此工具后,AI才能探测该端点。

简化 probe_api

最初,调用 probe_api 工具需要AI传递所有内容:

{
&nbsp;&nbsp;"body": {
&nbsp; &nbsp;&nbsp;"dataFetcherConfig": {
&nbsp; &nbsp; &nbsp;&nbsp;"id":&nbsp;"602e1c07-d60c-4a6f-9375-1caf1b976697",
&nbsp; &nbsp; &nbsp;&nbsp;"metadata": {&nbsp;"title":&nbsp;"Updated title"&nbsp;}
&nbsp; &nbsp; }
&nbsp; },
"host":&nbsp;"autopush-cloudcrmcards-pa.sandbox.googleapis.com",
"http_method":&nbsp;"POST",
"include_creds":&nbsp;"113728935872649341310",
"method_id":&nbsp;"autopush_cloudcrmcards_pa_sandbox.updateDataFetcherConfiguration",
"path":&nbsp;"/v1/updateDataFetcherConfiguration",
"version":&nbsp;"v1"
}

这包括了API主机名、HTTP方法、冗长的发现方法ID以及API版本。AI有太多机会产生幻觉或提供不正确的值。如果设置了 include_creds(它接受一个Gaia ID),请求将附带我的攻击者谷歌账户的Cookie。这封装了复杂的谷歌FPA认证,因此AI只需专注于构建有效负载。为了节省工程精力,我重用了我在前端为代理谷歌API请求而创建的同一个API端点。

后来我将其简化为:

{
&nbsp;&nbsp;"body": {
&nbsp; &nbsp;&nbsp;"dataFetcherConfig": {
&nbsp; &nbsp; &nbsp;&nbsp;"id":&nbsp;"602e1c07-d60c-4a6f-9375-1caf1b976697",
&nbsp; &nbsp; &nbsp;&nbsp;"metadata": {&nbsp;"title":&nbsp;"Updated title"&nbsp;}
&nbsp; &nbsp; }
&nbsp; },
&nbsp;&nbsp;"include_creds":&nbsp;"113728935872649341310",
&nbsp;&nbsp;"endpoint":&nbsp;"updateDataFetcherConfiguration",
&nbsp;&nbsp;"path":&nbsp;"/v1/updateDataFetcherConfiguration",
}

现在API主机和版本在后台追踪。我还从端点名称中去掉了冗长的前缀(如 autopush_cloudcrmcards_pa_sandbox)以减少AI出错的机会。

多密钥探测

在谷歌API中,使用一个API密钥的响应可能与使用另一个不同。这对于隐藏在可见性标签背后的端点尤其如此。我让 probe_api 自动使用所有已知的API密钥发送相同的请求。我的后端会处理添加正确的密钥限制头部以及来源/Referer匹配逻辑。

由于绝大多数响应在不同密钥间是相同的,我按响应哈希对它们进行了分组:

{
&nbsp;&nbsp;"operation_id":&nbsp;"op_023",
"results": [
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp;&nbsp;"endpointPath":&nbsp;"/v1internal/accounts/1495306056/dataSegments/1",
&nbsp; &nbsp; &nbsp;&nbsp;"apiKey":&nbsp;"AIzaSyDntWfIQs0iyimIUm1GTOWjx5fJL8YdKTE",
&nbsp; &nbsp; &nbsp;&nbsp;"httpMethod":&nbsp;"GET",
&nbsp; &nbsp; &nbsp;&nbsp;"statusCode":&nbsp;200,
&nbsp; &nbsp; &nbsp;&nbsp;"responseBodyHash":&nbsp;"response_1"
&nbsp; &nbsp; },
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp;&nbsp;"endpointPath":&nbsp;"/v1internal/accounts/1495306056/dataSegments/1",
&nbsp; &nbsp; &nbsp;&nbsp;"apiKey":&nbsp;"AIzaSyDIIy--0yYGybWFSbAyNxF8aOqvX-X1doE",
&nbsp; &nbsp; &nbsp;&nbsp;"httpMethod":&nbsp;"GET",
&nbsp; &nbsp; &nbsp;&nbsp;"statusCode":&nbsp;404,
&nbsp; &nbsp; &nbsp;&nbsp;"standardErrorType":&nbsp;"MISSING_REQUIRED_VISIBILITY_LABEL"
&nbsp; &nbsp; },
&nbsp; &nbsp; ...
&nbsp; ],
"responseBodies": {
&nbsp; &nbsp;&nbsp;"response_1": {
&nbsp; &nbsp; &nbsp;&nbsp;"responseJson": {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"cpmFee": {&nbsp;"currencyCode":&nbsp;"USD",&nbsp;"units":&nbsp;"3"&nbsp;},
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"createTime":&nbsp;"2025-02-19T22:05:30.626Z",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"creator": {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"accountId":&nbsp;"1495306056",
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"displayName":&nbsp;"DoubleVerify Inc."
&nbsp; &nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"curatorDataSegmentId":&nbsp;"1",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"dataSegmentId":&nbsp;"7950",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"state":&nbsp;"INACTIVE",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"updateTime":&nbsp;"2025-05-22T13:47:13.599Z"
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
&nbsp; },
"totalResults":&nbsp;4
}

解析标准错误

谷歌API经常返回我理解但可能迷惑AI的隐晦错误消息。例如:

{
&nbsp;&nbsp;"error": {
&nbsp; &nbsp;&nbsp;"code":&nbsp;404,
&nbsp; &nbsp;&nbsp;"message":&nbsp;"Method not found.",
&nbsp; &nbsp;&nbsp;"status":&nbsp;"NOT_FOUND"
&nbsp; }
}

与你可能认为的相反,这并不意味着方法不存在。如果是那样的话,它会是一个HTML响应,而不是JSON。这实际上意味着与你的API密钥绑定的GCP项目缺少一个必需的 可见性标签。我将这些解析为一个 standardErrorType,例如 MISSING_REQUIRED_VISIBILITY_LABEL

另一个常见的错误:

{
&nbsp;&nbsp;"error": {
&nbsp; &nbsp;&nbsp;"code":&nbsp;400,
&nbsp; &nbsp;&nbsp;"message":&nbsp;"Request contains an invalid argument.",
&nbsp; &nbsp;&nbsp;"status":&nbsp;"INVALID_ARGUMENT"
&nbsp; }
}

这仅仅意味着一个或多个参数不正确。我将其解析为 INVALID_ARGUMENT_NO_DETAILS 并包含一个 standardErrorExplanation

{
&nbsp;&nbsp;"standardErrorType":&nbsp;"INVALID_ARGUMENT_NO_DETAILS",
&nbsp;&nbsp;"standardErrorExplanation":&nbsp;"请求由于参数无效被应用程序拒绝,但未提供详细信息。检查你的请求参数。"
}

所有的渗透测试都记录在我的前端,我可以在那里滚动查看和审查AI所做的每个工具调用。

改进方法

最初,从在多个API上运行AI来看,它发现了一些漏洞,但大量报告是无效信息,真正的漏洞隐藏其中。我发现了两个关键问题:

  1. 验证过程很痛苦 。没有简单的方法来验证漏洞是否真实存在。我必须手动访问我前端中的API,设置所有相同的参数,并检查AI报告的内容是否合法。就我所知,AI可能编造了所有内容。
  2. 噪音太多 。AI会报告一些我不认为是漏洞的东西,以及它认为是”潜在”漏洞但实际上不可利用的东西。一个常见的例子是存在性枚举。能够判定用户是否存在的信息查询器本身是有趣的,但本身不值得报告。

为了解决验证问题,我让AI在其报告中包含来自 probe_api 响应的操作ID,例如 {{op_005}}。在我的前端,这些会被替换成一个显示实际发送请求的UI(这无法被幻觉捏造)。我可以看到操作返回的响应,并点击”播放”来重放请求并验证漏洞是否仍然可用。

为了解决噪音问题,我进行了大量的试错,不断调整系统提示,直到我明确规定了应该报告什么和不应该报告什么。以下是我最终确定的系统提示的摘录(经过一个多月的重构):

你是一名谷歌VRP安全研究员,负责测试谷歌API中的IDOR、访问控制破坏漏洞。

**重要:**&nbsp;谷歌使用严格的JSON→gRPC转码和强类型检查。类型混淆漏洞不适用 - 使用请求模式中的确切类型。

## 工具

1. &nbsp;**probe_api(...)**&nbsp;- 测试端点。返回一个&nbsp;**operation_id**&nbsp;- 保存此ID用于报告漏洞。
2. &nbsp;**report_vulnerability(...)**&nbsp;- 报告已确认的漏洞。**需要操作ID**&nbsp;作为证据,这些ID来自你的probe_api调用。
3. &nbsp;**confirm_testing_complete(report)**&nbsp;- 完成后调用。系统会验证所有范围内的端点都已测试。你的报告将传递给后续测试组 - 请包括发现的ID、有用的上下文以及你注意到的任何模式。
4. &nbsp;**get_endpoint_schema(endpoint)**&nbsp;- 仅获取超出范围端点的模式。在探测超出范围端点之前需要调用此工具。

**操作ID:**&nbsp;每个probe_api调用返回一个operation_id(例如,"op_001")。报告漏洞时,你必须包括证明漏洞的操作ID。这将你的报告链接到实际的请求/响应数据。

## 测试规则

**端点列表是穷尽的:**&nbsp;下面列出的端点是唯一存在的端点。不要尝试所列之外的其他HTTP方法或路径。

**范围内端点:**&nbsp;下面提供了完整的模式。直接探测它们。
**超出范围端点:**&nbsp;如果需要探测它们以获取上下文或ID发现,请先调用&nbsp;`get_endpoint_schema`。

**认证:**&nbsp;检查&nbsp;`allows_auth`&nbsp;列以决定是否使用 include_creds。

**ID枚举 (测试技术 - 不是漏洞):**
-&nbsp;如果你发现一个递增的数字ID(例如,12345),立即尝试 ID-1, ID-2, ID+1, ID+2
-&nbsp;尝试小ID:1, 2, 3, 100, 1000
-&nbsp;将从某个端点发现的ID交叉引用到其他端点
-&nbsp;这就是你如何找到其他用户的资源的方法
- &nbsp;**注意:**&nbsp;能够枚举ID本身**不是**漏洞。只有在你实际上能**访问**到机密数据时才报告。

**不知道参数值?**&nbsp;使用:"1", "test", "me", "default", 伪造的UUID。永远不要跳过端点。

**每个端点进行多次探测**&nbsp;使用不同的认证状态和ID。

## 报告

**当你发现以下情况时报告:**
-&nbsp;访问其他用户的数据
-&nbsp;预期应该返回4xx状态码时返回了2xx状态码以及私人数据

**不要报告:**
-&nbsp;500错误, 401/403/404 错误, 400参数无效错误
-&nbsp;状态200但没有实际私人数据泄露或可证明的影响
- &nbsp;**存在性枚举**&nbsp;-&nbsp;**绝不**报告你能检测ID是否存在(例如,有效ID和无效ID的不同响应)。这**不是**漏洞,除非它泄露了敏感信息如电子邮件、姓名或私人数据。将枚举用于测试,但不要报告它。

**严重性:**
-&nbsp;DEBUG: 内部调试信息泄露(非 type.googleapis.com/xxx)
-&nbsp;INFO: 疑似IDOR - 端点返回200/404/500并带有资源ID,但没有有效的ID来确认(需要手动验证)
-&nbsp;MEDIUM: 受害者 Gaia ID → 电子邮件 映射
-&nbsp;MEDIUM: 受害者 项目编号 -> 项目ID 映射
-&nbsp;HIGH: IDOR泄露其他用户数据
-&nbsp;CRITICAL: 访问控制破坏泄露敏感用户数据

**立即报告。**&nbsp;一旦你确认漏洞,立刻调用 report_vulnerability - 不要等到最后。

**每个漏洞 = 一次报告。**&nbsp;如果你在多个端点发现相同的漏洞,只报告一次。例外:INFO级别的内部错误泄露 - 只报告你看到的第一个,除非它们差异很大。

一旦解决了这两个问题,AI开始频繁地发现漏洞,准确率超过50%。审查它们变得轻而易举。我只需点击”播放”,看看漏洞是否仍然有效,然后提交报告。很快,情况变得清晰起来,唯一的限制因素就是API密钥的数量。

攻破谷歌

现在是时候说说有趣的部分了:AI在不到3个月的运行时间里发现了价值 $500,000 的漏洞。这里的漏洞太多了,无法全部涵盖,但以下是它发现的一些最酷的漏洞(现已修复)。

未完待续…

  • END –

感谢阅读,如果觉得还不错的话,动动手指给个三连吧~


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:骨哥说事 骨哥说事 骨哥说事《用AI入侵谷歌,获得50万美元赏金【上】》

评论:0   参与:  0