Sudo security

Sudo is a great tool for selectively granting access, and is installed by default in most Linux distributions. Unfortunately it's also very easy to shoot yourself in the foot with Sudo. This post is going to cover some common mistakes and how to correct them.

Editing files

A naive approach to allowing a user to edit /etc/motd, might look something like this:

user ALL=(root) /bin/vi /etc/motd

This works fine, however it's also possible for the user to start a root shell from vi:

$ sudo /bin/vi /etc/motd
:!bash

You can prevent processes being spawned with the NOEXEC option:

user ALL=(root) NOEXEC: /bin/vi /etc/motd

This will prevent bash being executed with the following message:

Cannot execute shell /bin/bash

However it doesn't stop the user changing the file they are editing:

:e /root/.bashrc

Using sudoedit

There is however another option, sudoedit:

user ALL=(root) sudoedit /etc/motd

Unlike normal sudo rules, sudoedit runs through the following steps (see SUDO(8) for more detail):

  1. Temporary copies are made of the files to be edited with the owner set to the invoking user.

  2. 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.

  3. If they have been modified, the temporary files are copied back to their original location and the temporary versions are removed.

This is great because it means if the user does shell out, they will just be running as themselves. They can also pick which editor they want to use.

Note: unlike most commands, you should not use a full path to sudoedit!

Reading files

One very common command used to read files is less:

user ALL=(root) /bin/less /var/log/messages

However it's actually possible to start a shell from less by typing !sh:

[user@somehose ~]$ sudo less /var/log/messages
sh-4.2# whoami
root

Like vi it's also possible to open a new file by typing :e:

Examine: /etc/shadow

A simple solution to this problem is to change the sudo rule to use a command like cat:

user ALL=(root) /bin/cat /var/log/messages

The user can then pipe stdout into less:

$ sudo cat /var/log/messages | less

Alternatively you could look at setting the LESSSECURE environment variable before less is executed.

Config files

It's fairly common to delegate managing configuration files using sudo. For example:

user ALL=(root) sudoedit /etc/logrotate.d/httpd

As a general rule, if the config is read by a root process you should be very careful. In the example above logrotate can be configured to run arbitrary commands using the postrotate directive:

/var/log/httpd/*log {
    missingok
    notifempty
    sharedscripts
    delaycompress
    postrotate
        /bin/systemctl reload httpd.service > /dev/null 2>/dev/null || true
        /tmp/evil_script.sh
    endscript
}

tcpdump

To capture network traffic tcpdump needs to be run as root. A sudo rule similar to the following could be used to grant access to tcpdump:

user ALL=(root) /sbin/tcpdump

Unfortunately tcpdump actually lets you run arbitrary commands using the -z option:

Used in conjunction with the -C or -G options, this will make tcpdump run " postrotate-command file " where file is the savefile being closed after each rotation. For example, specifying -z gzip or -z bzip2 will compress each savefile using gzip or bzip2.

This can be used to run arbitrary code as root with a command similar to the following:

sudo tcpdump -w /dev/null -G 1 -z /tmp/evil_script.sh -Z root

The -G option tells tcpdump to rotate the file it's dumping to, /dev/null, every second. /tmp/evil_script.sh will then be called after each rotation. The NOEXEC option can be used to prevent the script being executed:

user ALL=(root) NOEXEC: /sbin/tcpdump

If NOEXEC is set, trying to run a command with -z will fail:

$ sudo tcpdump -w /dev/null -G1 -z /tmp/evil_script.sh -Z root
tcpdump: listening on enp0s3, link-type EN10MB (Ethernet), capture size 65535 bytes
compress_savefile:execlp(/tmp/evil_script.sh, /dev/null): Permission denied
compress_savefile:execlp(/tmp/evil_script.sh, /dev/null): Permission denied

Wildcards

Sudo does support wildcards, for example:

user ALL=(root) /bin/grep * /var/log/httpd/*

Unfortunately the * character will match anything, including ../../../. This potentially allows directory traversal:

$ sudo grep root /var/log/httpd/../../../etc/shadow
root:$6$ptJc.Gfb$ldpLqMu1BpgLMPZzkxGxaY.V9k71u3t45gdZIpYXVbG7qwDB9q3qhFGeUoIr5Kkg/yNKZ4bqlkOU1eEsblu5d0:17182:0:99999:7:::

Sadly sudo rules can't use regex. As a result it's very hard to prevent directory traversal when wildcards are used.

Environment variables

By default environment variables are reset before any commands are run via sudo. It is however possible to whitelist additional environment variables using env_keep:

Defaults!/bin/yum env_keep += "PYTHONPATH"
user ALL=(root) /bin/yum update httpd

Environment variables like PYTHONPATH however are generally not safe:

$ whoami
user
$ cat /tmp/yum.py
import subprocess
subprocess.call('/bin/bash')
$ sudo PYTHONPATH=/tmp yum update httpd
[root@somehost tmp]#

Be very careful if you do decide to whitelist environment variables!

Conclusion

With sudo it's very easy to accidentally grant more access than you intended. If you're giving sudo access to run a command, look closely at all of the options and think carefully before setting up a new rule.