Sudo Policy Fixes and Fails – The Cmnd Alias

So we’ve covered the User_Alias, Host_Alias, and Runas_Alias labels up to this point.  Today, we’re going to cover the Cmnd_Alias label.  This one takes a comma separated list of commands that the policy will allow.  It can contain defaults overrides, TAGs, command flags, and command arguments.  It can use special keywords in place of commands.  It is the most complex label in the policy, and it is critical to get this “right” if you don’t want to accidentally grant more privilege than you intended.

Most of our examples (after we go over the Defaults in detail) will be identifying problem policies, and most of those problems will relate to this label.

To define the label as an alias, we use the Cmnd_Alias entry like so:

Cmnd_Alias C_DOSTUFF = /usr/bin/stuff

This would grant policy to call the command “/usr/bin/stuff” (or just “stuff” if /usr/bin is in the PATH environment variable,) without restricting the flags or arguments that can be passed to it.  If we want to force it to never allow any arguments, we can change the policy like this:

Cmnd_Alias C_DOSTUFF = /usr/bin/stuff ""

This tells the policy to make the argument list “empty,” which means “no arguments are allowed.”   We can similarly restrict the flags that may be passed.

Cmnd_Alias C_DOSTUFF = /usr/bin/stuff [!-]* ""

This is the place where it is important to remember that “whitelisting” is the way to go.  Trying to “blacklist” often leads to heartache and pain killers.  The biggest example of a failed “blacklist” mentality is this:

Cmnd_Alias SHELLS = /bin/bash, /usr/bin/ksh, /bin/zsh
Cmnd_Alias SU = /bin/su
Cmnd_Alias C_BABYADMIN=ALL, !SU, !SHELLS

This is even mentioned in the man page, and yet, it is seen in the wild all too often.  This command appears to say “the user can run any command except the listed shells or su.”  However, by being able to run any command, the user is able to make a copy of any of the shells under a different name, and then use sudo to execute them.  This does not work the way people seem to think it does.  Even the man page has a “valid” entry that is dangerous.  The “valid” example it provides is this:

jill SERVERS = /usr/bin/, !SU, !SHELLS

On the surface, it looks like “jill” can execute anything within “/usr/bin” except the SU and SHELLS commands.  Since “cp” and “mv” and the like are in “/bin” but not “/usr/bin” on many systems, this looks like it might work, right?  Here’s the problem.  The “at” command often resides in “/usr/bin” so something as simple as this could be used to also bypass this problem:

echo "cp -p /bin/bash /usr/bin/myshell" | sudo at now

This is why it is always better to whitelist as much as possible, not give any kind of “blanket” policy with dumbed down “blacklist-like” restrictions.  My preference when dealing with Cmnd_Alias issues is to have the user write a script, (or write one for them) that handles the exact command that will need privileged access, then make that be owned by root and not writable by anyone.  Grant sudo policy to that script, instead.  You can control what flags are always passed, what arguments are always passed, and so on, if you do it this way.

We won’t look at the “Defaults” overrides this week, since we’re going to being covering Defaults next week, but we will look at the TAGs again.

So the defined TAGs are listed below.  The log related ones will be covered when we go over the Defaults, but I’ll list them, anyway.

PASSWD: | NOPASSWD:  -  Force a user to authenticate, or don't force authentication for this command.
EXEC: | NOEXEC:  -  Allow or attempt to prevent the command from forking a child process.
SETENV: | NOSETENV:  -  Override the value of the "setenv" Default on a per-command basis.
LOG_INPUT: | NOLOG_INPUT:  -  Override the value of the "log_input" Default on a per-command basis.
LOG_OUTPUT: | NOLOG_OUTPUT:  -  Override the value of the "log_output" Default on a per-command basis.

Remember that the “NOEXEC:” TAG only works if the binary was compiled as a shared object binary.  Statically compiled programs (such as “ed”) will not be blocked by this hooking mechanism.  This is why the special “sudoedit” command entry is recommended for any kind of file editing situation.  We covered this here.

I know this was a lot, but it’s not nearly enough.  We’ll go into more detail as we rip apart “bad sudo” policy examples later on.  Before we get to that, though, we need to look at the Defaults, and we’ll begin that next week.

Sudo Policy Fixes and Fails – The Runas Alias

Continuing our Sudo policy series, we’re going to focus on the Runas_Alias label this week.  In many regards, this looks much like the User_Alias in that it takes users and groups and the syntax is the same.

However, its purpose is much different.  Where the User_Alias identifies who CAN run this policy entry, the Runas_Alias defines which users and groups the policy may be executed under.

The typical default entry for this (much like the Host_Alias label) is “ALL” which allows the command to be executed under any user or group identified by the calling user.

Typical sudo calls look like this:

sudo my_command

This defaults to calling “my_command” as the root user.  The policy probably had “(ALL)” as the Runas_Alias label, as mentioned above.  We don’t always want to let a user run a command as root, though.  Sometimes we want to allow one user to call a command as another non privileged user, just because that process belongs to that user.  To do this, we set a Runas_Alias label that restricts the command to being executed under that user, not root, and not “ALL.”

With such a policy in place, the user has to know how to call sudo to execute under someone other than the default “root” user.  The “-u” flag allows this.

sudo -u otheruser my_command

Sometimes, we also need to call the command as a different group.  In order to do this, we can pass the “-g” flag.  A combination of both is valid.

sudo -u otheruser -g othergroup my_command

This allows for more granular control over the policies being written, and enforces safe policy that doesn’t grant access to root except when absolutely necessary.

This is the end goal of any good policy.  Only allow what is strictly necessary, and nothing more than that.

Sudo Policy Fixes and Fails – The Host_Alias

Last week we looked at the User_Alias label.  This week, we’re going to look at the Host_Alias label.  Within a policy line, the “Host_Alias” is the next section of that policy.  This dictates on which systems/servers this policy will be allowed to run.

When the policy is unique to each server, is not maintained as a centralized monolithic structure that gets shared out to all end points, or isn’t stored in a database such as LDAP, the usual default is the “ALL” entry.  This simplifies figuring out which items to put here for a given server.  It may seem like that answer is simple, but in reality, it’s not, due to the way sudo checks the host when called to execute a policy request.

What do I mean by that?  The below snippet from the sudoers man page addresses this better than my own words ever could.

A Host_List is made up of one or more host names, IP addresses, network numbers, netgroups (prefixed with ‘+’) and other aliases.

If you do not specify a netmask along with the network number, sudo will query each of the local host's network interfaces and, if the network number corresponds to one of the hosts's network interfaces, the corresponding netmask will be used.  The netmask may be specified either in standard IP address notation (e.g. 255.255.255.0 or ffff:ffff:ffff:ffff::), or CIDR notation (number of bits, e.g. 24 or 64).  A host name may include shell-style wildcards (see the Wildcards section below), but unless the host name command on your machine returns the fully qualified host name, you'll need to use the fqdn option for wildcards to be useful.  Note that sudo only inspects actual network interfaces; this means that IP address 127.0.0.1 (localhost) will never match.  Also, the host name “localhost” will only match if that is the actual host name, which is usually only the case for non-networked systems.

As you can see, there is some reliance upon what the “host name” command returns, whether an IP is ever checked based on the actual network interfaces configured, and so on.  The loopback interface is never checked.  It is recommended to create a test policy with different Host_Alias entries that LOOK like they should work before making any assumptions about how your policy will actually behave once installed.  If you frequently change the IP address(es) of your systems, a host name is preferred.  If you change the name frequently, such as during a migration or similar, setting policy by IP might be better for your environment.  Or maybe the name is better, and you include the name change inside your distributed monolithic policy file, or LDAP entry, or what have you.  In the end, you need to figure out what will work best for your environment and be consistent with how you handle things.  It doesn’t hurt to use both host name and IP in a policy, so you can set your alias label for either, or both.

Also, you can do negation with this just as you can with User_Alias entries.  You could say “ALL, !www.example.com” to make this apply to any system that isn’t “www.example.com” (for example.)

The Host_Alias label is less important to change from the almighty “ALL” if you aren’t doing some kind of distributed shared policy, but if you get to more than a few end point servers that are not identical use case machines, you will eventually want to consider such a move.  This is why it is important to learn how the Host_Alias actually behaves for policy on each machine, and test your policy to be sure it does what you expect.

Next week we’ll cover Runas_Alias, as it is the next section of a policy line.

Thanks for reading!

Sudo Policy Fixes and Fails – The User Alias

We covered this on a very basic level last week, but today we’re going to focus on the User_Alias policy entries.

A sudo policy begins with a list of users and/or groups.  This list can be a raw listing, or it can be a label created by a “User_Alias” entry.  Organization of the policy file depends on your needs, but one common way to organize things is to use the aliases to define how pieces of a policy should look, then put all of the policies at the bottom where the alias labels are used to build out those policies.  There are other ways to structure this, as well, and we’ll cover several of them later.  For now, we’ll assume that we are doing one section of the file to cover all User_Alias entries, another section for all Cmnd_Alias entries, and so on.

A User_Alias entry is simply the key word “User_Alias” followed by the label name you would like to use, and equal sign “=” and the list of users and/or groups, comma separated, that the labeled alias will define.

User_Alias U_WEBADMINS = %webslingers

If you want to allow a group, but restrict one member of the group from having the access to the policy, you can modify it like so:

User_Alias U_WEBADMINS = %webslingers, !venom

Less useful in most situations, but still available as an option, is to use UID and GID numbers to represent the users and groups.  If the “webslingers” group has GID 404, and the “venom” user has a UID of 666, the same policy as above could be written this way, instead:

User_Alias U_WEBADMINS = %#404, !666

If you are using yellow pages with net groups, you can also reference a netgroup using a “+” instead of “%” to represent the group.

Finally, non-Unix group names and GIDs are represented with “%:” and “%:#” prefixes, instead of just “%” or “%#” per normal.

This is simple stuff, but I’ve seen poorly constructed “user alias” entries before.  This is why we’re taking it slow and hitting each type separately as we go.  A lot of this will seem redundant, but hopefully it lays it out in a simple and easily understood manner.

Thanks for reading!  Next week will be Host_Alias labels, simply because it is the next piece of the policy entry as read from left to right.

Sudo Policy Fixes and Fails – The Basics of a Sudo Policy File

I tried to hold out on switching topics, but I’m going to switch to dealing with Sudo Policy for a while.  This SSH using GnuPG keys situation is taking longer than I ever intended, and I can’t keep posting “sorry for the delay” posts.  It isn’t right for me, and it isn’t right for you, my readers.  So today, I’m officially starting the Sudo Policy series.  My oldest daughter is getting a new laptop as a late graduation gift, and after she has transferred all of her files, I’ll be taking a drive dump of that laptop for archival purposes, then installing either OpenBSD (if it will work) or some flavor-of-the-month version of Linux on it.  I might install FreeBSD if OpenBSD doesn’t work, but we’ll have to wait and see how things shake out.

In the mean time, on to today’s topic!

The sudo command is nothing more than a policy engine.  This means it takes a requested input, compares it to a policy file that tells it what is and is not permitted, then either executes the input command on behalf of the calling user, or it does not.  This is as simple as it gets when describing the program itself.  In order to take advantage of this policy engine to allow some super user privilege level tasks to be granted to a set of non-privileged users, so that we (the sysadmins) don’t have to do their work for them, but also not give them the keys to the kingdom (root password,) we need to understand the policy file that sudo uses.

The master policy file for sudo is the /etc/sudoers file.  This file contains entries that tell sudo how to check that the requesting user is allowed to run the requested command as some other user on this system.  Let’s break that sentence down a bit and go over what that means, by looking at a policy entry.

%admin ALL=(ALL) ALL

The “requesting user” has a special label within the policy file, called the “User_Alias” label.  In this case, the “User_Alias” entry is a group called “admin.”  We know it is a group, because of the percent sign in front of the name.  This corresponds to a group name at a system level.

The “on this system” portion of the policy entry is next.  The first “ALL” you see, just before the equal sign indicates that this policy is valid for all hosts.  The special label for this is the “Host_Alias.”  For simplicity, this is often left as “ALL” on most systems.  However, this gives us the flexibility to deal with using one monolithic policy file on multiple systems without accidentally granting more than we mean to on any one given system.

The “as some other user” portion of the policy entry comes after the equal sign, and is surrounded by parenthesis.  This is another label that may contain a list of users, groups, or a mix of both.  Groups still need a percent sign to distinguish them from users.  The label for this is “Runas_Alias.”

The “requested command” is the last portion of the policy entry, and it  contains a list of commands to allow or deny, depending on whether you are white-listing the command, or black-listing it.  In all cases, assume that black-listing is not even an option.  I will explain why in a later post as we cover things more in depth.  The label for this is “Cmnd_Alias.”

There is one more thing do consider for a standard policy entry, and that’s the modifier TAG that can come before a Cmnd_Alias to change its behavior.  By default, sudo prompts the user for their password when calling it to execute a command, but if you put the NOPASSWD: tag in front, it doesn’t prompt.  This should technically be used sparingly, and we’ll cover what the pitfalls are with some of these tags as we go along.

Finally, understand that the default settings compiled into sudo may be overridden with a different kind of policy entry called the “Defaults” label.  There are ways to also modify any of the “*_Alias” labels with options from the “Defaults” list, but we won’t cover those until much later in this series.

So for now, the most basic way to look at a policy line is like this:

User_Alias Host_Alias=(Runas_Alias) TAG: Cmnd_Alias

We’ll cover each of these components more in depth each week, but know that as soon as I get the GnuPG/Yubikey/SSH situation finally stable, repeatable, and without bugs, I’ll be returning to the SSH Start to Finish Mastery series to finish it up before returning to this new series overall.

Thanks again for reading, and leave a comment if you enjoyed this content!

Sudo Policy Fixes and Fails – Importance of auditing your sudoers policy files.

Wait, what?  Yes.  That is correct.  This week is skipping the SSH series to begin discussions on Sudo policy review.  We will return to the SSH series soon, but I really, really, really want the next post to cover the use of token devices (using the Yubikey 4 for the first discussion on this,) and I’m simply not prepared for it.  I’ve run into several road blocks on getting my new key programmed, all of which will be covered when I write that post.

Since I don’t want to skip a day of content, I decided to introduce the next major topic I’ll go into after wrapping up the SSH series.

Sudo isn’t the only privilege elevation policy engine available.  It isn’t even the only open source privilege elevation policy engine.  It is, however, one of the most popular, and powerful in its own right.  However, it is also one of the most easily misconfigured tools, and this can (and does) lead to very dangerous policy.

For our first foray into what does and does not constitute “Bad Sudo,” we will look at one of the most overlooked utilities provided by the sudo package: sudoedit.

Before we discuss “sudoedit,” we should look at the problem of granting elevated privileges to editors in general.

Let’s start with the classic “edit a file with vi” entry.

Cmnd_Alias EDITFILE = /usr/bin/vi /path/to/file
someuser ALL=(ALL) EDITFILE

The user “someuser” now has permission to call /usr/bin/vi to edit the file /path/to/file as root.  When this happens, sudo calls the /usr/bin/vi command as the root user.  Why is this dangerous?  The “vi” editor allows for calling out to the shell to execute commands.  You can do this by hitting the escape key, then “:!/path/to/bad/command.”

There is a tag that is often used to try to suppress this command from being able to do harm.  The NOEXEC tag can often be seen in configurations where this was attempted.

Cmnd_Alias EDITFILE = /usr/bin/vi /path/to/file
someuser ALL=(ALL) NOEXEC: EDITFILE

Unfortunately, this is a bandaid that may or may not work as intended.  It relies on interrupting a program that was built with dynamically linked libraries.  It also relies on the sudo command itself being compiled with the NOEXEC support built in.  If the command in question were “ed” instead of “vi” (or on older systems, “/bin/vi” instead of “/usr/sbin/vi” where “/bin/vi” was often statically compiled so that it could be used to help repair a bad /etc/fstab for boot issues, and /usr wasn’t mounted properly) this would be useless.  The tag would do nothing to prevent calling out to the shell.

Now let’s look at the correct way to handle this.

Cmnd_Alias EDITFILE = sudoedit /path/to/file
someuser ALL=(ALL) EDITFILE

As you can see, instead of calling “/usr/bin/vi” we call “sudoedit” to modify that file.  What does this do?  It’s simple.  The user will call “sudoedit /path/to/file” instead of “sudo vi /path/to/file.”  The sudoedit command will then obtain elevated privilege to access the file, make a copy of it to a temp file, then open an editor for the user AS THAT USER.  This means in our example, that the editor will be called as if “someuser” had called it himself.  The user can call out to the shell for command execution all day, but it will only run those commands without any more privilege than the user already had.

How does sudoedit know which editor to use?  From the man pages:

The editor specified by the policy is run to edit the temporary files.  The sudoers policy uses the SUDO_EDITOR, VISUAL and EDITOR environment variables (in that order).  If none of SUDO_EDITOR, VISUAL or EDITOR are set, the first program listed in the editor sudoers(5) option is used.

So we need to be sure to set a policy of allowing a handful of editors for our users, and then clean up any policy that is granting access directly to editors to fix bad policy.  In order to do this, we need to set a Defaults directive to list the available permissible editors.

Defaults editor="/usr/bin/vi:/usr/bin/emacs:/bin/ed:/usr/bin/nano"
Cmnd_Alias EDITFILE = sudoedit /path/to/file
someuser ALL=(ALL) EDITFILE

This will let users use either vi, emacs, ed, or nano, depending on preference. There is an “env_editor” option, but if it is set, it lets users set any editor they like. This could be something that isn’t actually an editor, so the safe thing to do is just give the list of approved editors in the option I listed, instead.

I will cover some more “gotchas” like this eventually, but I’m hoping I get all of the kinks worked out of my Yubikey adventure before next week, so I can get back to the SSH posts, first.

I hope this was useful to some of you!