The following article was written by Alex Bell, software engineer at Cmd specializing in Linux cybersecurity.
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.
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.
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.
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.
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:
Furthermore, if you try to become root, sudo correctly prevents this:
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:
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:
What happens, however, if we pass an invalid user ID, like -1?
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.
A patch for this bug has already been released by the author of sudo and packages for major distros are now available. If for some reason you’re not able to install this update immediately, or if you simply want more visibility and alerting around who might attempt to run this command and where, I’m going to show you how to write basic trigger for Cmd using our CQL (command query language).
Once you’ve written the trigger, click save, and the new rule will be active on every monitored server in your fleet within minutes. A user attempting to run the command can be prompted for 2FA, have their session terminated or simply generate an alert via your preferred channel.
Note: The second parameter we need to check for, 4294967295, works the same as -1 because it’s being stuffed into an unsigned integer bucket so it overflows.
After the trigger is applied, Cmd displays a custom message and terminates the session.
User permissions on Linux can be complex, and striking the right balance between usability and security can be difficult. We believe in defense in depth, and Cmd provides an additional layer of visibility and control on top of the solid security foundation provided by Linux.
By the way — if you want to try Cmd for yourself (it’s easy and takes virtually no time to set up) you can sign up for a free trial here