spotify / web-api-examples

Basic examples to authenticate and fetch data using the Spotify Web API
Apache License 2.0
1.99k stars 1.67k forks source link

Authorization: Query parameter 'state' not protecting against attackers #19

Open irvin93d opened 6 years ago

irvin93d commented 6 years ago

According to the Authorization code flow (link), on GET https://accounts.spotify.com/authorize the query parameter state can be used to protect against cross-site request forgery. I however don't see how the implementation in the web example gives any extra security against attackers.

In the file web-api-auth-examples/authorization_code/app.js (link), following code is implemented:

app.get('/callback', function(req, res) {
    var code = req.query.code || null;
    var state = req.query.state || null;
    var storedState = req.cookies ? req.cookies[stateKey] : null;

    if (state === null || state !== storedState) {
        // Complain about mismatch
    }
    // ...
}

Both variables state and storedState are provided by the client, so an attacker could just make up an arbitrary cookie and state that matches. I agree that it would prevent gaining access by mistake, if the query link is shared. It does however not provide any security against attackers as stated in the documentation.

Feel free to correct if I am wrong!

zbo14 commented 4 years ago

@irvin93d My understanding is the state parameter is designed to prevent CSRF attacks.

Yes, an attacker could send an HTTP/S request to the /callback route with a forgedstate param and spotify_auth_state cookie (e.g. abc). But unless they had a valid authorization_code for someone else's account, they wouldn't achieve anything besides triggering a POST request to the Spotify auth service. (Q: what would an attack with the attacker's authorization code look like?)

How would the attacker convey a valid authorization_code for someone else's account to the /callback route? One way is CSRF - they could send the victim a specially-crafted URL.

If the victim visits this URL and completes the auth process, they're redirected to the /callback route, which checks if the state param === spotify_auth_state cookie. The attacker specified the state in the specially-crafted URL sent to the victim - it should be abc. But the cookie is coming from the vicitm's browser, which doesn't have the spotify_auth_state cookie set to abc. Maybe the victim has a different value for the cookie or doesn't have the cookie at all. Either way, the strict comparison should fail. This is how the /callback route detects if the request wasn't intentionally triggered by the end user whose browser sent it. And even if the attacker sent several URLs with different state params, they would need to guess the victim's spotify_auth_state cookie for the attack to succeed.

Hopefully this explanation helps, happy to field feedback if it's unclear/wrong.