Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ The tool parses the captured snapshot, injects the gadget tuples, recomputes the

## CVE-2025-54068 – RCE without `APP_KEY`

According to the vendor advisory, the issue affects Livewire v3 (>= 3.0.0-beta.1 and < 3.6.3) and is unique to v3.
According to the vendor advisory, the issue affects Livewire v3 (>= 3.0.0-beta.1 and <= 3.6.3) and is unique to v3.

`updates` are merged into component state **after** the snapshot checksum is validated. If a property inside the snapshot is (or becomes) a synthetic tuple, Livewire reuses its meta while hydrating the attacker-controlled update value:

Expand Down Expand Up @@ -122,11 +122,42 @@ Key reasons this works:
- `getMetaForPath()` trusts whichever synth metadata already existed for that property even if the attacker previously forced it to become a tuple via weak typing.
- Recursion plus weak typing lets each nested array be interpreted as a brand new tuple, so arbitrary synth keys and arbitrary classes eventually reach hydration.

### High-value pre-auth target: Filament login forms

Applications built on top of Livewire often expose an even easier pre-auth surface than a toy `public $count;` property. For example, Filament login pages commonly hydrate a weakly typed `$form` object that is already serialized as a `form` tuple in the snapshot. That removes the "scalar -> array -> `arr` tuple" setup step entirely:

- The snapshot already contains something like `{"form":[{...},{"s":"form","class":"App\\Livewire\\Forms\\LoginForm"}]}`.
- An attacker can send `updates.form` with nested malicious tuples directly, because recursion will eventually reinterpret children such as `[payload, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"}]`.
- This is why pre-auth Livewire entrypoints that expose `FormObjectSynth` objects are especially attractive: they already provide both instantiation and public-property assignment.

### Patch analysis: preserve raw metadata during update recursion

The fix introduces a dedicated `hydratePropertyUpdate()` path so nested update values no longer call generic `hydrate($child, ...)` on attacker-controlled children:

```php
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);
});
}
```

Security impact of the patch:

- Nested updates are revalidated against the original raw snapshot path instead of trusting fresh attacker-supplied tuple metadata.
- Recursive hydration no longer lets children redefine `s` or `class` mid-flight.
- This blocks both arbitrary synthesizer switching and arbitrary class selection inside nested update arrays.

## Livepyre – end-to-end exploitation

[Livepyre](https://github.com/synacktiv/Livepyre) automates both the APP_KEY-less CVE and the signed-snapshot path:

- Fingerprints the deployed Livewire version by parsing `<script src="/livewire/livewire.js?id=HASH">` and mapping the hash to vulnerable releases.
- Fingerprints the deployed Livewire version by parsing `<script src="/livewire/livewire.js?id=HASH">` (or `?v=HASH`) and mapping the hash to vulnerable releases.
- Collects baseline snapshots by replaying benign actions and extracting `components[].snapshot`.
- Generates either an `updates`-only payload (CVE-2025-54068) or a forged snapshot (known APP_KEY) embedding the phpggc chain.
- If no object-typed parameter is found in a snapshot, Livepyre falls back to brute-forcing candidate params to reach a coercible property.
Expand Down Expand Up @@ -154,9 +185,10 @@ python3 Livepyre.py -u https://target/livewire/component -a base64:APP_KEY \

## References

- [Synacktiv – Livewire: Remote Command Execution via Unmarshaling](https://www.synacktiv.com/publications/livewire-execution-de-commandes-a-distance-via-unmarshaling.html)
- [Synacktiv – Livewire: Remote Command Execution via Unmarshaling](https://www.synacktiv.com/en/publications/livewire-remote-command-execution-through-unmarshaling)
- [synacktiv/laravel-crypto-killer](https://github.com/synacktiv/laravel-crypto-killer)
- [synacktiv/Livepyre](https://github.com/synacktiv/Livepyre)
- [GHSA-29cq-5w36-x7w3 – Livewire v3 RCE advisory](https://github.com/livewire/livewire/security/advisories/GHSA-29cq-5w36-x7w3)
- [livewire/livewire commit `ef04be7` – Fix property update hydration](https://github.com/livewire/livewire/commit/ef04be759da41b14d2d129e670533180a44987dc)

{{#include ../../banners/hacktricks-training.md}}