AzuraCast is software that lets people run their own internet radio station from their computer or server. It's popular with college radio stations, community broadcasters, and hobbyists who want to avoid relying on big tech companies.
A serious security flaw has been discovered in versions before 0.23.6. Think of it like this: the software is supposed to keep uploaded audio files in a specific folder, like keeping your documents in a locked filing cabinet. But the vulnerability is like someone finding a way to slip files into other drawers in your office — including the ones that run your computer's core functions.
Here's what makes it dangerous. Someone with legitimate access to upload media (a DJ, station manager, or volunteer) could trick the system into uploading a malicious program disguised as an audio file. Because the file ends up in the wrong location, it could actually execute and take control of the entire server. That means an attacker could steal broadcasts, listener data, or use your server to attack other computers.
Who's at risk? Mostly small radio stations and independent broadcasters running AzuraCast on their own servers. The threat comes from insiders — people who already have permission to upload files but decide to abuse that trust.
The good news is that there's no evidence this is being actively exploited in the wild yet, giving people time to protect themselves.
What you should do: First, update AzuraCast to version 0.23.6 or later immediately. Second, review who has media upload permissions and remove anyone you don't absolutely trust. Third, if you run AzuraCast, back up your data now in case something goes wrong during the update.
Want the full technical analysis? Click "Technical" above.
CVE-2026-42605 is a path traversal vulnerability in AzuraCast's chunked file upload endpoint that permits an authenticated user with media management privileges to write arbitrary files anywhere on the server filesystem. On default installations — which use a local filesystem storage backend — this trivially escalates to remote code execution by dropping a PHP webshell into the web root. CVSS 8.8 (HIGH) reflects the low attack complexity and high impact triad, gated only by the requirement for an authenticated session with station file manager access.
The vulnerability exists in all AzuraCast versions prior to 0.23.6 and was silently present since the Flow.js resumable upload feature was introduced. No exploitation in the wild has been observed as of publication.
Affected Component
The vulnerable endpoint is POST /api/station/{station_id}/files/upload, handled by AzuraCast's API controller responsible for media management. The Flow.js protocol splits large uploads into numbered chunks; the server reassembles them using metadata supplied by the client, including a currentDirectory parameter that specifies the destination subdirectory within the station's media storage path. This parameter is passed directly into a filesystem path construction routine without normalization or traversal sequence stripping.
Storage backend: LocalFilesystemAdapter (default). PHP runtime: the AzuraCast application server (nginx + PHP-FPM) serves files from the web root, making the process user's filesystem access the effective privilege boundary.
Root Cause Analysis
Root cause: The currentDirectory POST parameter is concatenated directly into the destination path passed to the Flow.js chunk reassembly routine without stripping ../ sequences, permitting traversal out of the station's media storage directory.
The following pseudocode reconstructs the vulnerable path-building logic from the pre-patch codebase:
// FilesController::uploadAction() — simplified pseudocode
// Equivalent PHP logic reconstructed from pre-0.23.6 behavior
string uploadAction(Request $request, int $station_id)
{
Station *station = getStation(station_id);
StoragePath *base = station->getMediaStoragePath(); // e.g. /var/azuracast/stations/1/media
// BUG: currentDirectory is attacker-controlled, never sanitized
string currentDirectory = $request->getParsedBodyParam("currentDirectory", "");
// Direct string concatenation — no realpath(), no traversal check
string destPath = base->getPath() + "/" + currentDirectory;
// Flow.js chunk reassembly writes the final file to destPath
FlowUploader::reassemble($request, destPath); // writes file at destPath/
}
The FlowUploader::reassemble routine honors the assembled destination path verbatim. Because currentDirectory can contain arbitrary sequences like ../../../../../../var/www/html/, the reassembled file lands anywhere the PHP-FPM process user can write. No call to realpath() or equivalent prefix-assertion is made before the write occurs.
The following illustrates the exact parameter structure sent during a malicious upload chunk:
EXPLOIT CHAIN:
1. Attacker authenticates to AzuraCast with any account holding
"Manage Station Media" permission (station-scoped, low privilege).
2. Enumerate station_id via GET /api/stations (unauthenticated endpoint)
to obtain a valid {station_id} integer.
3. Determine web root via error messages, default paths, or Docker
layer inspection. Default: /var/www/html/public (Docker install)
or /var/azuracast/www/public (Ansible install).
4. Construct traversal string:
currentDirectory = "../../../../../../var/www/html/public"
Depth depends on station media path nesting; 6 levels covers
default Docker layout reliably.
5. Send single-chunk Flow.js upload (flowTotalChunks=1) with:
flowFilename = "shell.php"
currentDirectory =
file body = PHP webshell payload
6. Server writes /var/www/html/public/shell.php owned by www-data.
7. Trigger execution:
GET /shell.php?cmd=id HTTP/1.1
Host: target.example.com
Response: uid=33(www-data) gid=33(www-data) groups=33(www-data)
8. Pivot: www-data has read access to /var/azuracast/.env containing
database credentials, AzuraCast secret key, and broadcast passwords.
Single-chunk uploads (setting flowTotalChunks=1 and flowChunkNumber=1) bypass any intermediate temp-file staging: the reassembler treats the upload as complete and moves the chunk directly to the resolved destination path in one filesystem operation.
Memory Layout
This vulnerability is not a memory corruption issue — it is a logic flaw in path construction. The relevant "layout" is the filesystem hierarchy and how the unsanitized parameter escapes the intended trust boundary:
FILESYSTEM STATE — INTENDED BEHAVIOR:
Station media root: /var/azuracast/stations/1/media/
Upload destination: /var/azuracast/stations/1/media//
Example (benign):
currentDirectory = "subfolder"
Final path: /var/azuracast/stations/1/media/subfolder/track.mp3 [OK]
FILESYSTEM STATE — AFTER TRAVERSAL:
currentDirectory = "../../../../../../var/www/html/public"
Constructed path:
/var/azuracast/stations/1/media/
+ ../../../../../../var/www/html/public
= /var/www/html/public/ <-- escaped station root
+ shell.php
= /var/www/html/public/shell.php <-- WEBSHELL WRITTEN HERE
PHP-FPM (www-data) serves /var/www/html/public/* directly.
shell.php is now an executable endpoint with no auth requirement.
PATH COMPONENTS RESOLVED BY OS:
/var/azuracast/stations/1/media/ (6 real segments after /)
../../../../../../ (6 x ".." → reaches /)
var/www/html/public/shell.php (attacker-controlled suffix)
Patch Analysis
Version 0.23.6 introduces path sanitization before the destination is constructed. The fix validates that the resolved absolute path is prefixed by the station's authorized media storage path, rejecting any request where traversal would escape the boundary.
// BEFORE (vulnerable, pre-0.23.6):
string destPath = base->getPath() + "/" + currentDirectory;
// No validation — currentDirectory written directly into path
FlowUploader::reassemble($request, destPath);
// AFTER (patched, 0.23.6):
string rawDirectory = $request->getParsedBodyParam("currentDirectory", "");
// Strip ALL path traversal sequences before use
string safeDirectory = sanitizeCurrentDirectory(rawDirectory);
string destPath = base->getPath() + "/" + safeDirectory;
// Secondary defense: assert resolved path is within authorized root
string resolvedDest = realpath_or_throw(destPath);
string resolvedBase = realpath_or_throw(base->getPath());
if (!str_starts_with(resolvedDest, resolvedBase + "/")) {
throw SecurityException("Path traversal detected in currentDirectory");
}
FlowUploader::reassemble($request, destPath);
// sanitizeCurrentDirectory() — strips traversal sequences
string sanitizeCurrentDirectory(string input) {
// Remove null bytes
input = str_replace("\0", "", input);
// Collapse and remove traversal sequences
while (str_contains(input, "..")) {
input = str_replace("../", "", input);
input = str_replace(".." , "", input);
}
// Strip leading slash (prevent absolute path injection)
input = ltrim(input, "/\\");
return input;
}
The defense-in-depth approach — sanitize first, then assert with realpath() — is the correct pattern. A realpath()-only check is insufficient if the directory doesn't exist yet (some PHP versions return false for non-existent paths, nullifying the prefix check). The patch applies both layers.
Detection and Indicators
Detection relies primarily on web server and PHP-FPM access logs. Look for:
INDICATORS OF COMPROMISE:
1. Upload requests with encoded traversal in POST body:
Grep nginx access.log for:
POST /api/station/.*/files/upload
Then inspect raw body logs or WAF logs for:
currentDirectory=..%2F | currentDirectory=../ | %2e%2e%2f
2. Unexpected .php files in web root directories:
find /var/www/html -name "*.php" -newer /var/www/html/index.php \
-not -path "*/vendor/*" -not -path "*/src/*"
3. PHP-FPM execution of files outside application source tree:
Monitor php-fpm slow log and error log for scripts in:
/var/www/html/public/*.php (non-framework files)
4. Outbound connections from www-data:
auditd rule: -a always,exit -F arch=b64 -S connect \
-F uid=33 -k www-data-outbound
5. Anomalous GET requests to non-application PHP files:
GET /shell.php HTTP 200
GET /cmd.php HTTP 200
GET /upload.php HTTP 200
Remediation
Immediate: Upgrade to AzuraCast 0.23.6 or later. No configuration change is sufficient as a standalone mitigation — the parameter sanitization must be present in application code.
Defense-in-depth for self-hosters who cannot immediately patch:
Restrict the PHP-FPM process user (www-data) to read-only access on all directories outside /var/azuracast/stations/ and /var/www/html/ using filesystem ACLs or a dedicated service account.
Mount the web root with a noexec-equivalent control: configure nginx to deny execution of .php files from upload directories explicitly.
Apply a WAF rule blocking currentDirectory POST parameters containing ../, %2e%2e, or %2f sequences against the upload endpoint.
Audit existing media directories for unexpected .php, .phtml, or .phar files.
Principle of least privilege reminder: "Manage Station Media" is a granular permission intended for content managers, not administrators. The blast radius of this permission was implicitly elevated to OS-level RCE — a permission model failure that the patch addresses at the filesystem boundary rather than the permission layer.