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 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!