All private/unlisted videos of a victim can be stolen if the victim visits a malicious link.
Using the same technique, the victim’s liked videos, watch later and any private playlist’s content can be stolen.
Due to the lack of CSRF protection, a malicious website can play any video/playlist on a YouTube TV via the
lounge API in the name of the victim.
https://youtu.be/HmdyzRH67ac - length: 1:46
Source code of the POC shown in the video is attached to this report.
Stealing liked videos, watch later and private playlist content is not shown in the POC.
There is no CSRF protection on the
https://www.youtube.com/api/lounge/bc/bind endpoint. A malicious website can send a
POST request to this enpoint to request a video/playlist to be played on a YouTube TV. The malicious website can specify which TV to send the video to using the
This sections describes how, using this CSRF, the attacker can steal all of the victim’s private/unlisted videos. More specifically, here I describe how the POC code works.
- POC starts a Flask webserver, to serve the victim opening the malicious page. Let’s call the POC python script backend.
- Victim opens the malicious webpage. Let’s call the victim’s page frontend.
- Backend requests the attacker to enter the victim’s channel ID.
- The channel ID’s second character is changed to a
U. The resulting string is the playlist ID of the playlist
Uploads from [Channel Name], which contains all private/unlisted uploads. Only the owner can see the private/unlisted videos in this playlist. Other users don’t even see the private/unlisted video IDs.
- The POC sets up a fake TV, and starts to poll the events for the new malicious TV.
- The frontend is instructed to execute the CSRF request, and play the playlist generated in
Step 4.on the malicious TV.
- When the CSRF request is sent by the frontend, the backend recieves the TV play event, containing the IDs of all of the victim’s videos, including private/unlisted video IDs.
- The backend queries the YouTube Data API with all of the video IDs, to find out the which videos are private/unlisted.
- The unlisted videos are ready, the backend prints the IDs out for the attacker. The unlisted videos only require knowing the video ID to watch.
- Backend instructs the frontend to play the victim’s private videos on the malicious TV one by one. For every video, the backend sets up a new malicious TV, tells the frontend to play the specific video, listens for play events, and recieves the play event for the TV with a special
cttparameter. Using the
ctt, the backend queries the
get_video_infoYouTube endpoint for the specific private video, authenticates itself with the
ctt, and greps the private video’s title and direct video URl from the response.
- After every private video is played by the frontend, the backend prints the details of all of the private videos for the attacker.
- The POC script is done.
The specific technical details of how the backend/frontend constructs/sends the requests can be found in the attached POC script.
poc.py and the
frontend.html files have to be in the same directory for the script to run.
Stealing WL/Likes/Private Playlists:
Step 6. of the POC, any other private playlists can be played just as the
Uploads from [Channel Name] playlist. And listening for the play event on the backend, the attacker can get the list of video ID’s the given private playlist contains.
All channels have a few special private playlists:
WL-> The victim’s
LL-> The victim’s
These two can also be stolen using the same method.
HL used to point to the victim’s
Watch History, but as of my testing, that playlist is not available anymore.
This attack works with not only private, but “draft”, “scheduled” and “removed” videos as well. (These videos are just “private” internally.)
This section was not included in the original report.
Q: Is the
/bind request giving you a
A: That’s “normal”. I stripped off so many parameters that it returns an error, but the video/playlist still gets played on the TV.