8

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

  • Does sudo env SHELL=/bin/sh sh provide a prompt with /bin/sh set as the SHELL variable in your system? – user79743 Oct 6 '15 at 5:25
  • @adonis - See my updated question. BTW, you're very handsome. – Mike S Oct 6 '15 at 14:24
  • @BinaryZebra - Yes I know about env_delete, but I agree the crux of the problem is that the default behavior of env_reset ...causes commands to be executed with a new, minimal environment. We have a linux system with PAM, so according to the man page, The new environment contains the ... SHELL ... (variable). As you can see from my /etc/sudoers file above, we do not allow SHELL in the env_keep. So SHELL should not be preserved; we should have the root user's SHELL. – Mike S Oct 6 '15 at 14:33
  • @BinaryZebra - I have added zabbix ALL=(root) NOPASSWD: /bin/env SHELL=/bin/sh /tmp/doit * to my /etc/sudoers/zabbix file, and it has a proper shell. Thanks, I now have a workaround. The question is, why did I need to include it? It seems dangerous (and broken) to pass the caller's SHELL but I can find no place where sudo is set to modify it. I have run find /etc/sudoers /etc/sysconfig -type f -exec grep env_ {} \; and I find no red flags; /etc/sudoers contains the only env_ string. So I don't think there is a sudoers flag interfering... – Mike S Oct 6 '15 at 14:50
  • Mike: On a first level: A simple sudo bash should start a bash shell as root and it MUST have the SHELL variable set to the value from /etc/password. You report that SHELL is being set to (or preserved as) /sbin/nologin. That is a security issue, the shell started by root must not be controlled by an environment variable set by an user. That is something you must investigate. – user79743 Oct 6 '15 at 19:14
5

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))
  • Excellent Mike !, Thanks for the detective work. – user79743 Oct 6 '15 at 19:27
  • Mike, is it possible that you put a link to the (future?) patch. – user79743 Oct 6 '15 at 19:29
  • @BinaryZebra Diff is here: sudo.ws/repos/sudo/rev/b77adbc08c91 I don't see a patch as yet. – Mike S Oct 6 '15 at 20:20
  • Mike: I believe that you are barking to the wrong tree. The key point here: Provide default values for $SHELL, $TERM and $PATH if not set. is: ... if not set.. Any value set will be preserved by sudo. Who is setting SHELL? – user79743 Oct 6 '15 at 21:18
  • @BinaryZebra - That's not how I read it. SHELL is unset (by env_reset, by default). Since it's unset, the old code says to use sudo_user's pw entry. The new code says to use the runas user's pw entry. – Mike S Oct 6 '15 at 21:56
4

The question was about where I thought the problem was, but it turns out that the issue is not what happens to the SHELL variable, but what sudo actually does. For example:

-bash-4.1$ whoami
testdude
-bash-4.1$ grep testdude /etc/passwd
testdude:x:1001:10:Test dude:/tmp:/bin/bash
-bash-4.1$ sudo env
[sudo] password for testdude: 
...
SHELL=/bin/bash
...

So far so good. ...but the problem is that sudo uses the shell of the caller instead of the callee, contrary to the docs. Indeed, if I change my shell by editing /etc/passwd, you can see that sudo follows the shell of the caller and not SHELL:

-bash-4.1$ grep root /etc/passwd
root:x:0:0:root:/root:/bin/bash
-bash-4.1$ sudo sed -i -e '/testdude/s/bash/sh/' /etc/passwd
-bash-4.1$ grep testdude /etc/passwd
testdude:x:1001:10:Test dude:/tmp:/bin/sh
-bash-4.1$ sudo env
...
SHELL=/bin/sh
...
-bash-4.1$ export SHELL=/completely/meaningless/path
-bash-4.1$ sudo env
...
SHELL=/bin/sh
...

I can't use sudo -i because I don't want to simulate an initial login. sudo -s will work, as long as I have the proper command in the sudoers file. However, the expected behavior (as reflected in the man page: "The new environment contains the TERM, PATH, HOME, MAIL, SHELL, LOGNAME, USER, USERNAME and SUDO_* variables") is for the shell to be the callee's. If you look at the PATH, HOME, LOGNAME, and USER variables for sudo env you will see root's stuff. SHELL should be root's shell as well.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.