Files
SKELETONKEY/docs/EXPLOITED.md
T
KaraZajac 70972e0c9d
build / build (clang / debug) (push) Waiting to run
build / build (clang / default) (push) Waiting to run
build / build (gcc / debug) (push) Waiting to run
build / build (gcc / default) (push) Waiting to run
build / sanitizers (ASan + UBSan) (push) Waiting to run
build / clang-tidy (push) Waiting to run
build / drift-check (CISA KEV + Debian tracker) (push) Waiting to run
build / static-build (push) Waiting to run
sudoedit_editor: fix CVE-2023-22809 exploit to actually land root
The module built the right EDITOR="helper -- /etc/passwd" injection but failed
two ways, both now fixed (VM-verified root on Ubuntu 22.04.0 / sudo
1.9.9-1ubuntu2):

1. Writable-CWD guard: sudoedit refuses to edit any file whose parent dir is
   user-writable. The injected "--" is resolved relative to CWD, so running
   from home/tmp made sudoedit abort "--: editing files in a writable
   directory" before the editor ran. Fix: chdir("/") in the sudoedit child.

2. Wrong tmp copy: sudoedit hands the editor one tmp copy per file, named
   <basename>.XXXXXX. The helper wrote argv[argc-1] — the sudoers-authorized
   COVER file (e.g. motd), not the target. Fix: the helper now matches the
   target's basename prefix (passed via SKEL_TARGET) to pick the right copy.

Result: /etc/passwd gains a skel::0:0 row; `su skel` -> uid=0(root).
docs/EXPLOITED.md + VERIFICATIONS.jsonl updated. Unit harness green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118iUgHY44hdRtANgyCmu7y
2026-07-23 21:32:44 -04:00

8.5 KiB
Raw Blame History

Exploit verification ledger

What this is: results of actually running each exploit against a genuinely vulnerable VM and confirming uid=0 out of band (an independent root-owned write / /etc/shadow read / setuid-bash sentinel — never the module's own self-report). This is distinct from docs/VERIFICATIONS.jsonl's historical records, which only checked that detect() returns the right verdict.

Harness: rootless qemu/KVM over frozen point-release cloud images (unpatched → vulnerable by default), driver in the session scratch dir. Root witnessed via witness.sh (shell-probe + setuid-bash finisher + module sentinels + /etc/passwd tamper check).

Headline finding: the corpus was only ever detect-verified, never exploit-verified. Running the exploits shows a mix of genuinely-working, honestly-failing, and falsely-succeeding modules. Two flagship modules reported EXPLOIT_OK while obtaining no root at all (pwnkit, ptrace_traceme) — a false positive from the dispatcher's "execve transferred → clean child exit = OK" path. Both are addressed below.

Confirmed landing root (uid=0 witnessed out of band)

module CVE target notes
refluxfs CVE-2026-64600 Rocky 9.8 / 5.14.0-687.el9 full chain, /etc/passwd → root (earlier)
overlayfs CVE-2021-3493 Ubuntu 20.04.0 / 5.4.0-26 userns + xattr copy-up, clean
overlayfs_setuid CVE-2023-0386 Ubuntu 22.04.0 / 5.15.0-25 after a full rewrite — see below
pwnkit CVE-2021-4034 Ubuntu 20.04.0 / polkit 0.105-26ubuntu1 after a fix — see below
sudo_runas_neg1 CVE-2019-14287 Ubuntu 18.04.2 / sudo 1.8.21p2 + sudoers (ALL,!root) sudo -u#-1 → uid 0
sudoedit_editor CVE-2023-22809 Ubuntu 22.04.0 / sudo 1.9.9 + sudoers sudoedit grant after 2 fixeschdir("/") + helper basename match; su skel → uid 0

Fixed this session

  • pwnkit — reported EXPLOIT_OK but did not root (glibc "Could not open converter … to PWNKIT"). Root cause: missing the GCONV_PATH=. re-injection directory + chdir(workdir). Fixed → now lands real root on a vulnerable host. (commit 24b839e)
  • ptrace_traceme — reported EXPLOIT_OK but did not root; the bundled ptrace sequence is a non-working placeholder (it PTRACE_ATTACHes to the tracer, which by then has reparented to init → EPERM). Made honest (verifies euid==0, otherwise EXPLOIT_FAIL); a faithful CVE-2019-13272 port (Jann Horn / bcoles: PTRACE_TRACEME + setuid execve + registered polkit agent) is still required to land root. (commit 59cc2be)
  • overlayfs_setuid (CVE-2023-0386) — rewritten and now lands real root (uid=0 witnessed out-of-band on Ubuntu 22.04.0 / 5.15.0-25). The shipped module used a bogus chown-the-merged-view technique that never worked. The real bug needs a FUSE lower layer exporting a setuid-root file; overlay copy-up then materialises it in the real upper as a genuine setuid-root binary. Key findings from the port (all four were required):
    1. Overlay refuses a userns-mounted FUSE lowerdir (ENOSYS) — FUSE must be mounted in the init ns via the setuid fusermount helper (libfuse does this). A raw /dev/fuse server was tried and abandoned: its INIT handshake needs poll() on the non-blocking fd, and a malformed reply destabilised the kernel — fragile and inappropriate. libfuse is linked conditionally (pkg-config fuse/fuse3), matching pack2theroot.
    2. fuse2 low-level API (fuse_mount/fuse_new/fuse_loop_mt, empty args) — fuse_main advertises splice/copy_file_range caps that make the kernel attempt copy_file_range at copy-up → ENOSYS with no fallback.
    3. read_buf callback (copy-up's splice read path).
    4. ioctl callback — copy-up issues FS_IOC_GETFLAGS on the lower; a server without an ioctl handler returns ENOSYS and copy-up fails. This was the last missing piece. Debugging was isolated by driving the exploit orchestration against the public PoC's ./fuse, then swapping servers, then comparing fops.
  • cgroup_release_agent — two real bugs fixed (commit 8c45b2b): it read getuid() after unshare(CLONE_NEWUSER) (→ 65534, so uid_map write was "0 65534 1" → EPERM), and it omitted CLONE_NEWCGROUP (→ cgroup-v1 mount EPERM). Now the userns+cgroupns+mount setup is correct. It still can't root a bare unprivileged user on a stock systemd host: every v1 controller is pre-mounted (its release_agent is init-owned → EACCES from the userns) and a fresh named hierarchy is refused. Reachable in a container context (CAP_SYS_ADMIN / an ownable cgroup) — matches its "host root from rootless container" framing. The getuid()-after-unshare bug is a pattern to grep for across the other userns modules.

Needs a faithful PoC port (genuinely vulnerable target, exploit doesn't land)

module CVE target tested what's wrong
ptrace_traceme CVE-2019-13272 Ubuntu 18.04.2 / 4.15.0-50 placeholder technique (now honest); needs the real cred-transfer PoC (polkit agent) — substantial port
overlayfs_setuid CVE-2023-0386 Ubuntu 22.04.0 / 5.15.0-25.25 Kernel confirmed vulnerable empirically — the upstream PoC (xkaneiki, libfuse) pops root here (uid=0(root), root-owned witness). The working technique: mount a FUSE fs exporting /file (st_uid=0, mode 04777) in the init ns via the setuid fusermount3 helper, then overlay-in-userns with that FUSE lowerdir + copy-up. My module's non-FUSE chown variant yields upper/file uid=1000 (no escalation); mounting FUSE inside the userns → overlay ENOSYS. Attempted a self-contained raw /dev/fuse port: got the fusermount fd-passing handshake (SCM_RIGHTS) + mount working, but the server hits EINVAL on read() after FUSE_INIT (non-blocking fd → needs poll()), and even with poll/buffer fixes the raw server serving was flaky and repeatedly wedged/rebooted the VM — i.e. the raw protocol reimplementation is fragile and can destabilise the target, which is worse for the corpus than a lib dependency. Conclusion: use libfuse (proven, robust; matches the pack2theroot conditional-lib precedent). Port is scoped and ready; needs a clean session to implement + verify.
sudo_samedit CVE-2021-3156 Ubuntu 18.04.2 + 20.04.0 honest EXPLOIT_FAIL; Baron Samedit is heap/libc-version-specific, needs a per-target tuned PoC. Hardest.

Inconclusive (detect version-blind vs vendor backport)

module CVE note
dirty_pipe CVE-2022-0847 Ubuntu 22.04.0's 5.15.0-25.25 almost certainly carries the backported fix (USN-5317); detect() is version-only so says VULNERABLE. Needs a genuinely pre-fix kernel (e.g. 5.13.0-19, or mainline 5.16.10) to verify the exploit.

Not yet exploit-tested (the bulk — ~30 modules)

  • 🟡 kernel primitives (nf_tables, nft_set_uaf, nft_payload, nft_fwd_dup, netfilter_xtcompat, af_packet, af_packet2, af_unix_gc, cls_route4, fuse_legacy, stackrot, sequoia, nft_pipapo, vsock_uaf, pintheft): return EXPLOIT_FAIL by design; landing root needs a per-kernel --dump-offsets + --full-chain (modprobe_path finisher) pass on a vulnerable kernel.
  • Structural userspace (sudoedit_editor, sudo_chwoot, sudo_host): need specific sudo versions + sudoers config; likely tractable.
  • 2026 CVEs (copy_fail ×5, dirtydecrypt, fragnesia, cifswitch, nft_catchall, ptrace_pidfd): need vulnerable 2026 kernels; the reconstructed race triggers (bad_epoll, ghostlock, nft_catchall) are deliberately under-driven and won't pop root by design.
  • Environment-blocked: vmwgfx (VMware guest only), dirty_cow (needs ≤4.4), mutagen_astronomy (CentOS 6 / Debian 7).
  • D-Bus/desktop (pack2theroot, udisks_libblockdev): need the polkit/D-Bus stack + a provisioner rule.

Method notes for continuation

  • Frozen images: cloud-images-archive.ubuntu.com/releases/<name>/release-<date>/ are unpatched and vulnerable-by-default for CVEs disclosed after that date — far easier than downgrading packages on current images.
  • gcc must be present in the VM (several exploits compile payloads at runtime); on EOL LTS, point apt at the archive main pocket.
  • Always verify root out of band. The module self-report is not trustworthy (two flagships lied). witness.sh is the reference check.