Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <e758e2d9-3c1f-c006-4ad2-f0f38ec7e669@orlitzky.com>
Date: Wed, 9 Oct 2019 10:14:30 -0400
From: Michael Orlitzky <michael@...itzky.com>
To: oss-security@...ts.openwall.com
Subject: CVE-2019-17365: Nix per-user profile directory hijack

Product: Nix
Versions affected: 2.3 and earlier
Author: Michael Orlitzky
Bug report: Reported privately to the NixOS security team on 2019-08-19.


== Summary ==

Out of the box, Nix creates an empty, world-writable, per-user profile
directory. After Nix is installed but before a victim has (re)logged in,
the victim's personal profile directory can be hijacked by an attacker
on on the system who has no other special privileges. Thenceforth, the
attacker controls that profile directory and can take over the target
account.


== Details ==

Nix is a package manager that can install software both globally (for
the system) and locally (for each user). One critical aspect of the
per-user support is that each user has a unique local "Nix profile"
directory. After installing Nix, the intent is that the next time a
user logs in, her per-user profile directory is created automatically
and some tweaks are made to allow her to use Nix right away.

The automatic profile-directory creation involves two key things.
First, the Nix installation script (scripts/install-multi-user.sh)
modifies /etc/bashrc, /etc/zshrc, and /etc/profile.d/nix.sh to include
the upstream file scripts/nix-profile-daemon.sh.in every time a user
logs in. That script, in turn, is supposed to create the user's Nix
profile directory if it does not exist:

  # Set up the per-user profile.
  mkdir -m 0755 -p $NIX_USER_PROFILE_DIR
  if ! test -O "$NIX_USER_PROFILE_DIR"; then
    echo "WARNING: bad ownership on $NIX_USER_PROFILE_DIR" >&2
  fi

If the directory does not exist, then when the user creates it, he
will own it and everything is fine.

However the second key aspect of this process is that, for the user to
be able to create $NIX_USER_PROFILE_DIR, he must be able to write to
its parent directory. That parent directory is shared by all users on
the system, and as a result, is world-writable (so that everyone can
create his own subdirectory thereof). This is enforced by the
installation script scripts/install-multi-user.sh...

  _sudo "to make the basic directory structure of Nix (part 2)" \
    mkdir -pv -m 1777 /nix/var/nix/{gcroots,profiles}/per-user

by the RPM spec file nix.spec.in...

  # make per-user directories
  for d in profiles gcroots;
  do
    mkdir -p $RPM_BUILD_ROOT/nix/var/nix/$d/per-user
    chmod 1777 $RPM_BUILD_ROOT/nix/var/nix/$d/per-user
  done

and even in one place by the Nix "LocalStore" class in
src/libstore/local-store.cc,

  Path perUserDir = profilesDir + "/per-user";
  createDirs(perUserDir);
  if (chmod(perUserDir.c_str(), 01777) == -1) ...

The sticky bit here is better than nothing, but is ultimately
insufficient. The problem with this approach is that after Nix is
installed, _any_ user on the system can create and thereafter own
_any_ local profile directory. I can't overwrite an existing one, but
I can create your profile directory before you have ever logged in.
Since the global hacks above load code from your Nix profile
directory, this lets me run code when you log in.

To make the situation a bit worse, the very last thing that gets
executed by scripts/nix-profile-daemon.sh.in when you log in is,

  export PATH="$HOME/.nix-profile/bin:...:$PATH"

where $HOME/.nix-profile is a symlink to your Nix local profile directory,

  if test "$USER" != root; then
    ln -s $NIX_USER_PROFILE_DIR/profile $HOME/.nix-profile
  else

Since I can write to your local profile directory, and since that
location is prepended to your path, I can override all of your system
executables with my own copies. Putting everything together, this
allows any user on the system to escalate his privileges to that of
any other non-root user. The root user is probably safe since he does
not use a local profile directory; however, on many systems, "root" is
not the only super-user account.


== Exploitation ==

The following example takes over the "toor" account using an
unprivileged "user" account.

  1. Install Nix as root.

  2. As "user", create the directory that "toor" would like to use for
     his per-user profile:

       user $ cd /nix/var/nix/profiles/per-user
       user $ mkdir toor

     At this point, "user" owns toor's profile directory; the end is
     nigh.

  3. To make matters worse, the unprivileged "user" can inject programs
     into toor's PATH:

       user $ mkdir -p toor/profile/bin
       user $ cp /path/to/exploit toor/profile/bin/cd
       user $ cp /path/to/exploit toor/profile/bin/cp
       user $ cp /path/to/exploit toor/profile/bin/ls
       user $ cp /path/to/exploit toor/profile/bin/exit
       user $ ...

  4. Log in as toor; a warning is issued:

       Nix: WARNING: bad ownership on /nix/var/nix/profiles/per-
       user/toor, should be toor
       toor $

     This is better than nothing, but it's too late.

  5. Anything "toor" does (cd, cp, ls, exit,...) gives the unprivileged
     user control of his account.

Powered by blists - more mailing lists

Please check out the Open Source Software Security Wiki, which is counterpart to this mailing list.

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.