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

118 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 fixes**`chdir("/")` + 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_ATTACH`es 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.