Exploit-DB #37706
Description: Libuser Library - Multiple Vulnerabilities
Type: dos
Platform: linux
Date: 2015-07-27
Author: Qualys Corporation

Exploit:
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::/:|...|...|