Bash – How to prevent the caller’s shell from being used in sudo

bashenvironment-variablessudo

I'm running sudo-1.8.6 on CentOS 6.5. My question is very simple: How do I prevent SHELL from propagating from a user's environment to a sudo environment?

Usually people are going the other way- they want to preserve an environment variable. However, I am having an issue where my user "zabbix" whose shell is /sbin/nologin tries to run a command via sudo. Sudo is preserving the /sbin/nologin so that root cannot run subshells. (Update: This part is true, but it is not the SHELL environment variable. It is the shell value that is being pulled from /etc/passwd that is the problem.)

I include a test that illustrates the problem; this is not my real-world use case but it simply illustrates that the calling user's SHELL is preserved. I have a program that runs as user zabbix. It calls /usr/bin/sudo -u root /tmp/doit (the programming running as zabbix is a daemon, so the /sbin/nologin shell in the password file does not prevent it). /tmp/doit is a shell script that simply has:

#!/bin/sh
env > /tmp/outfile

(its mode is 755, obviously). In outfile I can see that SHELL is /sbin/nologin. However, at this point the script is running as root, via sudo, so it should not have the previous user's environment variables, right?

Here is my /etc/sudoers:

Defaults    requiretty
Defaults   !visiblepw

Defaults    always_set_home
Defaults    env_reset
Defaults    env_keep =  "COLORS DISPLAY HOSTNAME HISTSIZE INPUTRC KDEDIR LS_COLORS"
Defaults    env_keep += "MAIL PS1 PS2 QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE"
Defaults    env_keep += "LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES"
Defaults    env_keep += "LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE"
Defaults    env_keep += "LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY"
Defaults    secure_path = /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin:/usr/local/sbin

## Allow root to run any commands anywhere 
root    ALL=(ALL)       ALL

#includedir /etc/sudoers.d

And here is my /etc/sudoers.d/zabbix:

Defaults:zabbix !requiretty

zabbix    ALL=(root) NOPASSWD:       /tmp/doit

Edit: A little more information:

The process running the sudo is zabbix_agentd, from the Zabbix monitoring software. There is an entry in the /etc/zabbix/zabbix_agentd.d/userparameter_disk.conf file which looks like:

UserParameter=example.disk.discovery,/usr/local/bin/zabbix_raid_discovery

/usr/local/bin/zabbix_raid_discovery is a Python script. I have modified it to simply do this:

print subprocess.check_output(['/usr/bin/sudo', '-u', 'root', '/tmp/doit'])

/tmp/doit simply does this:

#!/bin/sh
env >> /tmp/outfile

I run the following on my Zabbix server to run the /usr/local/bin/zabbix_raid_discovery script:

zabbix_get -s client_hostname -k 'example.disk.discovery'

Then I check the /tmp/outfile, and I see:

SHELL=/sbin/nologin
TERM=linux
USER=root
SUDO_USER=zabbix
SUDO_UID=497
USERNAME=root
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin:/usr/local/sbin
MAIL=/var/mail/root
PWD=/
LANG=en_US.UTF-8
SHLVL=1
SUDO_COMMAND=/tmp/doit
HOME=/root
LOGNAME=root
SUDO_GID=497
_=/bin/env

That SHELL line really bugs me. The file is owned by root, so I know it's being created by the root user, but the shell is from the calling user (zabbix).

Best Answer

Then answer is that sudo has a bug. First, the workaround: I put this in my /etc/sudoers.d/zabbix file:

zabbix    ALL=(root) NOPASSWD:       /bin/env SHELL=/bin/sh /usr/local/bin/zabbix_raid_discovery

and now subcommands called from zabbix_raid_discovery work.

A patch to fix this will be in sudo 1.8.15. From the maintainer, Todd Miller:

This is just a case of "it's always been like that".  There's not
really a good reason for it.  The diff below should make the behavior
match the documentation.

 - todd

diff -r adb927ad5e86 plugins/sudoers/env.c
--- a/plugins/sudoers/env.c     Tue Oct 06 09:33:27 2015 -0600
+++ b/plugins/sudoers/env.c     Tue Oct 06 10:04:03 2015 -0600
@@ -939,8 +939,6 @@
            CHECK_SETENV2("USERNAME", runas_pw->pw_name,
                ISSET(didvar, DID_USERNAME), true);
        } else {
-           if (!ISSET(didvar, DID_SHELL))
-               CHECK_SETENV2("SHELL", sudo_user.pw->pw_shell, false, true);
            /* We will set LOGNAME later in the def_set_logname case. */
            if (!def_set_logname) {
                if (!ISSET(didvar, DID_LOGNAME))
@@ -984,6 +982,8 @@
            if (!env_should_delete(*ep)) {
                if (strncmp(*ep, "SUDO_PS1=", 9) == 0)
                    ps1 = *ep + 5;
+               else if (strncmp(*ep, "SHELL=", 6) == 0)
+                   SET(didvar, DID_SHELL);
                else if (strncmp(*ep, "PATH=", 5) == 0)
                    SET(didvar, DID_PATH);
                else if (strncmp(*ep, "TERM=", 5) == 0)
@@ -1039,7 +1039,9 @@
     if (reset_home)
        CHECK_SETENV2("HOME", runas_pw->pw_dir, true, true);

-    /* Provide default values for $TERM and $PATH if they are not set. */
+    /* Provide default values for $SHELL, $TERM and $PATH if not set. */
+    if (!ISSET(didvar, DID_SHELL))
+       CHECK_SETENV2("SHELL", runas_pw->pw_shell, false, false);
     if (!ISSET(didvar, DID_TERM))
        CHECK_PUTENV("TERM=unknown", false, false);
     if (!ISSET(didvar, DID_PATH))
Related Question