Informations

Estimated reading time : 2 minutes

Tags : javascript reverse engineering cwe-288

Table of contents :

Intro

I often use Watch2Gether to watch videos with my friends. It's a good website, and if we ignore the player with buggy controls, it's working like a charm.

A feature that I loved was the ability of setting an arbitrary HLS stream that I hosted on my server.

However, this feature got locked for registered users only, and I hate creating accounts everywhere. So I decided to reverse engineer the website to find out how to bypass this restriction.

This might seem like a tidious task, but it took me less than 3 minutes to find the solution. And maybe that this blog post will help you for similar situations!

Hoes does it work?

Connecting

When connecting to a room, a websocket connection is created to wss://nova.w2g.tv/w2gsub (I trimmed the query parameters for readability).

This connection is used to synchronize the video playback between all the users in the room.

Monitoring

Using Chrome's Devtools to monitor the websocket connection, we can change the video source and see what C2S packets are sent to the server.

{
    "channel": "vrcm1twk4bhxpl2g1w",
    "data": {
        "target": "stream_state",
        "payload": {
            "url": "<redacted>",
            "title": "<redacted>",
            "thumb": "<redacted>",
            "syncTime": 1698347235057,
            "autoplay": true
        }
    }
}

Here, the payload object contains the new video source (url) along with some other informations that can be totally ignored.

So the solution would be to replay the packet to the server with our own video source this time.

Replaying the packet

We can use the queryObjects function to get the websocket object that's been created by the website at connection time.

queryObjects(WebSocket)

This will return after some time an array of websocket objects. We can right click on the first one and select Store as global variable to get a reference to it.

And then paste this function into the console :

function setStream(url) {
    // We need the channel id
    let channelId = document.querySelector("div#w2g-top-inviteurl > input").value.split("?r=")[1]
    temp1.send(
        `{"channel":"${channelId}","data":{"target":"stream_state","payload":{"url":"${url}","title":"","thumb":"","syncTime":${new Date().getTime()},"autoplay":true}}}`
    )
}

The syncTime field seems to be mandatory, so we'll just use the current timestamp.

setStream("https://example.com/stream.m3u8")

And that's it! The video source has been changed to our own stream.

Conclusion

Have fun and turn this into a Greasemonkey script or something! I'm too lazy to do it myself.