Festify Fuckery


About a week ago, I was contacted by @Ashleigh_tech on Twitter. She had the idea to mess around with a website called Festify. Festify is a service that allows people at a party to vote for the next song, which is of course very tempting for hackers...

The tweet that started it all

Festify uses websockets for communication between the client and the server, which is apparently kind of a pain to intercept. Firefox's developer tools shows a websocket connection being made, but doesn't show the stuff being sent over it. Wireshark and Burp proxy weren't a huge success either (Ashleigh said Burp worked for her, but it didn't show anything on my computer). After wasting way too much trying other stuff (I had never heard of websockets before this), I finally found out Chrome's developer tools do allow you to look at data sent over websockets (I didn't even bother to try that before because I thought it was basically the same as Firefox).

Chrome developer tools

{
    "t": "d",
    "d": {
        "r": 7,
        "a": "p",
        "b": {
            "p": "/votes/-LX5yoDA5nm0-B3PzTue/spotify-4QVOTT9CM2ftSLwnYGNDjd/NDkAXVe4tlfH2LLU2QEshefh5uF2",
            "d": true
        }
    }
}

As you can see, the JSON request above contains three "random" looking strings. The first one is the party's ID, which is just in the URL (for example: https://festify.us/party/-LX5yoDA5nm0-B3PzTue). The second one is the Spotify ID of the song you're voting for. You can get this using Spotify's API or just by copying a song's link (like https://open.spotify.com/track/6TfBA04WJ3X1d1wXhaCFVT). The last part however, is a bit harder. Festify is a Firebase website. When you visit Festify, a session is created. This gives you an idToken and a localId (also an accessToken, but this doesn't seem to be needed for anything really). To submit a vote, you first need to send an authentication request over the websocket, with the idToken as credential. Only then, you can send the vote request, which has to contain the localId associated with the idToken used before (this is the third parameter in the JSON request above). So for every vote, you have to create a new session, which is unfortunately rate limited. The rate limit is pretty loose though, since it seems to be based on the IP address of the user and at a party there will probably be a lot of requests from the same network.

 

This is a simplified version of the requestSong function. The full source code, which also contains the session creation script and a slightly more advanced function with error handling (read: yeeting the whole thing in a try/except block) can be found on this git repository.

def requestSong(partyid, songid, ws_addr):
    session = generatesession.getSession()  # Generate session using the session creation script
    idtoken = session['idtoken']
    localid = session['localid']

    # JSON request templates
    authenticate = f'{{"t":"d","d":{{"r":2,"a":"auth","b":{{"cred":"{idtoken}"}}}}}}'
    vote = f'{{"t":"d","d":{{"r":7,"a":"p","b":{{"p":"/votes/{partyid}/spotify-{songid}/{localid}","d":true}}}}}}'

    # Send requests over the websocket
    ws = create_connection(ws_addr)
    ws.send(authenticate)
    ws.send(vote)
    ws.close()

A big part of the credits go to @Ashleigh_tech and @usrbinpikachu. This whole project was Ashleigh's idea and Jake helped with intercepting the websocket stuff (without him I would have spent even longer messing around with that).

 

This is what our test party looks like now, by the way.

Test party

 

And finally: I think this tweet summarizes the whole thing pretty well.

We shouldn't be allowed access to the internet