GitHub Security Advisory: GHSA-q4ph-8x8g-95f8
Package: azuracast/azuracast (COMPOSER)
Severity: HIGH (CVSS 8.8)
CWE: CWE-94: Improper Control of Generation of Code ('Code Injection')
Vulnerable versions: <= 0.23.5
Patched in: 0.23.6

Summary: AzuraCast Vulnerable to Liquidsoap Code Injection via Incomplete cleanUpString-to-toRawString Migration in Remote Relay Password Field

Details:
## Summary

The `cleanUpString()` method in `ConfigWriter.php` uses an ungreedy regex to strip Liquidsoap string interpolation patterns (`#{...}`) from user input. This regex can be bypassed via nested interpolation syntax (`#{#{EXPR}}`), allowing injection of arbitrary Liquidsoap code. Commit `ff49ef4` migrated most user-controlled fields to the safe `toRawString()` method but left the remote relay password field using the vulnerable `cleanUpString()`. A user with the `RemoteRelays` station permission can achieve arbitrary code execution in the Liquidsoap process, leak internal API keys, or disrupt station operation.

## Details

### The Vulnerable Sanitizer

`cleanUpString()` at `backend/src/Radio/Backend/Liquidsoap/ConfigWriter.php:1349-1367`:

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

    // Remove strings that are interpolated
    $string = preg_replace(
        '/#{(.*)}/U',   // Ungreedy: matches minimum chars to first }
        '$1',
        $string
    );

    $string = preg_replace(
        '/\$\((.*)\)/U',
        '$1',
        $string ?? ''
    );

    return $string ?? '';
}
```

The `/U` (ungreedy) flag causes `.*` to match the **minimum** characters until the first `}`. With nested input `#{#{EXPR}}`:

1. Regex finds `#{` at position 0
2. Ungreedy `.*` matches `#{EXPR` (stops at the **first** `}`)
3. Full match consumed: `#{#{EXPR}` — replacement with capture group `$1` yields: `#{EXPR`
4. The trailing `}` is appended by the regex engine (it was outside the match)
5. **Final result: `#{EXPR}`** — a valid Liquidsoap string interpolation expression

### The Incomplete Patch

Commit `ff49ef4` ("Use raw strings for user-input strings to avoid interpolation", 2026-03-06) correctly migrated host, username, mount, name, description, genre, and URL fields to `toRawString()`. However, the password field was left using `cleanUpString()`:

`ConfigWriter.php:1208-1215`:
```php
$password = self::cleanUpString($source->password);  // Still vulnerable

$adapterType = $source->adapterType;
if (FrontendAdapters::Shoutcast === $adapterType) {
    $password .= ':#' . $id;
}

$outputParams[] = 'password = "' . $password . '"';  // Double-quoted = interpolated
```

The password is embedded in a Liquidsoap **double-quoted string**, which evaluates `#{...}` interpolation expressions.

### Why toRawString() Is Safe

`toRawString()` uses Liquidsoap raw string delimiters (`{str_xxxxx|...|str_xxxxx}`) which **do not perform interpolation**, making them immune to this attack class.

### The Input Path

1. Attacker sends `PUT /api/station/{station_id}/remote/{id}` with `source_password` containing the nested payload
2. Entity setter truncates to 100 chars via `mb_substr` (payloads fit within this limit)
3. No validation on password content
4. On station config regeneration, `ConfigWriter::getOutputString()` calls `cleanUpString()` on the password
5. Bypas