Note: This was co-written with Brian Gladstein, then head of marketing at Cmd. Cmd was acquired by Elastic and the product is no longer available but the protection discussed at the end could be enforced by any Linux security tool with the ability to compare command line arguments pre-execution
News broke earlier this month of a critical bug in the Linux command ‘sudo’, a core tool in Linux that allows user to run programs with elevated privileges. In typical Internet fashion, the announcement of this bug (labelled CVE-2019–14287) was followed by lots of scary headlines about opening Linux up to unauthorized users and enabling hackers to gain root access. But that’s probably not the case.
In this post we will break down the bug by explaining what sudo is, a bit about how it works, and why CVE-2019–14287 probably doesn’t affect you (because you’re probably not using sudo how it was intended). We’ll also cover how Cmd’s users can quickly add a rule to catch this bug being exploited on unpatched systems.
The “Roots” of the Linux Security Model
Tools for managing user access control on Linux have evolved as Linux has matured from a niche desktop OS in the nineties to its current role as the dominant OS of choice in the enterprise cloud.
The fundamental permission model for users is based on the one used by Unix. It was originally designed for a system where dozens or hundreds of users want to keep their files separate and inaccessible to the other users.
Every user is assigned a user ID number (UID), generally ranging from 0 to somewhere around 10000. Linux maintains a list of usernames but internally it only cares about UIDs. Every file and resource on the system is owned by a user (there are groups and GIDs but we can ignore those for now). As an example, if a user with UID 1 wants to read from, write to or execute a file that is owned by the user with UID 2, the permissions of that file must be modified to explicitly allow non-owners read, write and/or execute access, respectively.
The UID 0 is reserved for the user root, also sometimes called superuser, who can generally bypass all UID-based permission restrictions and access any file. Getting access to root means getting access to the entire system and is the ultimate prize for an attacker.
Sudo Avoids Giving Out Root Access
As Linux’s usage evolved, admins wanted a way to temporarily grant regular users limited access to the root account for specific reasons without giving them the root password. Sudo (short for SuperUser-Do) was introduced as an enhancement to the all-or-nothing approach enabled by the basic permission model.
Sudo is a special binary, owned by root and carrying special permission granted to it called setuid. A user who runs a setuid binary automatically inherits the permissions of the owner of the binary. When a non-root user executes a root-owned, setuid binary, the program that runs as a result will be running with the permissions of root, instead of the invoking user.
In effect, the sudo binary becomes the final arbiter of root access, metering it out in limited quantities as required. Incidentally, bugs involving sudo can be tricky to debug because a security feature of Linux automatically disables the ptrace() syscall on setuid binaries, which prevents tools like gdb and strace from being used.
Most People Don’t Use Sudo The Way It Was Intended
The sudo configuration file called sudoers actually has a fairly expressive language. For example, you can write things like “user bob can temporarily become user alice but only to run the mysql command.” Sudo will enforce these rules and run the mysql binary with the permissions of alice, and then force the user to go back to being bob.
In the real world, however, usage of sudo is more limited. Most systems these days come configured with a sudo user group (sometimes called wheel) that is granted the power to become root for any purpose. This is still better than giving everyone the password to root, since at least sudo usage is logged, but it does sound awfully similar to the all-or-nothing method it was intended to replace.
This real-world usage, where sudo is configured to let people easily elevate to root access (which is the default in Linux) instead of letting people easily elevate to other, still-restricted users, is exactly why most people aren’t affected by the bug below. The result of this bug is that someone can use sudo to elevate to root in a situation where the sudoers file should not allow it. If the sudoers file does allow it, as it does in most instances, the bug has no impact.
Walking Through CVE-2019–14287
This is a good opportunity for an example. You’ll see below a user called alice that needed to become user bob temporarily but never needed to become root. In this case you could tell sudo to allow the former but not the latter. The sudoers syntax would look something like
alice ALL=(ALL,!root) /bin/bash
When user alice elevates privileges to user bob, everything works as planned:
[alice@localhost ~] $ sudo -u bob /bin/bash [sudo] password for alice: [bob@localhost /home/alice] $ whoami bob [bob@localhost /home/alice] $
Note: the home directory hasn’t changed but /home/alice doesn’t alias to ~ when you’re no longer alice
Furthermore, if you try to become root, sudo correctly prevents this (when
-u is not specified, root is assumed):
[alice@localhost ~]$ sudo /bin/bash [sudo] password for alice: Sorry, user alice is not allowed to execute '/bin/bash' as root [alice@localhost ~]$
Remember from above that the system doesn’t really care about users, and under the hood you’re just a number to Linux? Sudo also lets you specify who you want to become via number. In the following example, user number 8 corresponds to user mail:
[alice@localhost ~]$ sudo -u#8 /bin/bash [sudo] password for alice: [mail@localhost /home/alice]$ whoami mail [mail@localhost /home/alice]$
If we specify UID 0 instead of root, the system knows what we’re asking for (root) and prevents it, since user number 0 is always the root user:
[alice@localhost ~]$ sudo -u#0 /bin/bash [sudo] password for alice: Sorry, user alice is not allowed to execute '/bin/bash' as root [alice@localhost ~]$
What happens, however, if we pass an invalid user ID, like -1?
[alice@localhost ~]$ sudo -u#-1 /bin/bash [sudo] password for alice: [root@localhost /home/alice]# whoami root [root@localhost /home/alice]#
Something doesn’t look right here! Note that we were successfully able to elevate privileges to root, even though attempting to elevate to root by the correct UID 0 was unsuccessful. So what happened?
The bug that was discovered, that triggered this CVE, is that neither the sudo binary nor the underlying syscall are checking that the UID is within the acceptable range. When sudo invokes the syscall to become the desired user, it’s still running as root. The syscall sees a value of -1 for the desired UID and reports success because it interprets the value -1 as a request for “no change in user id”. Since sudo believes the request to change user succeeded, it proceeds to run whatever command was requested.
This bug may sound simple and obvious in retrospect, but the fact that it went undiscovered for years in a security-critical package like sudo that’s used every day by millions of users is a testament to how small mistakes with subtle effects can have far reaching consequences when it comes to security on Linux.
EDR tools are no substitute for patching systems but if you can detect this invocation it could be a good stop-gap until you can a patched version of sudo installed.