14

I have seen advice in several places to use the following shebang line

#!/usr/bin/env bash

instead of

#!/usr/bin/bash

My knee-jerk reaction is, "what if somebody substitutes this executable for their own in say ~/.local/bin ?" That directory is often set up in the user's path before the system-wide paths. I see this raised as a security issue often as a side note rather than anything to take seriously, but I wanted to test the theory.

To try this out I did something like this:

echo -e "#!/usr/bin/python\nprint 'Hacked!'" > $HOME/.local/bin/bash
chmod 755 $HOME/.local/bin/bash
PATH=$HOME/.local/bin env bash

This yields

/usr/bin/env: ‘bash’: No such file or directory

To check whether it was picking up anything at all I also did

echo -e "#!/usr/bin/python\nprint 'Hacked!'" > $HOME/.local/bin/perl
chmod 755 $HOME/.local/bin/perl
PATH=$HOME/.local/bin env perl

which prints, as I expected,

Hacked!

Can someone explain to me why the substitute bash is not found, but the substitute perl is? Is this some sort of "security" measure that (from my point of view) misses the point?

EDIT: Because I have been prompted: I am not asking how /usr/bin/env bash is different from using /bin/bash. I am asking the question as stated above.

EDIT2: It must have been something I was doing wrong. Tried again today (using explicit path to env instead of implicit), and no such "not found" behaviour.

tk-noodle
  • 510
  • 1
    I'm wondering which env program your command is picking up, since /usr/bin is not in PATH anymore. I suspect bash's hashing mechanism is at play here. Bash remembers the full pathnames of commands. See help hash for more info. – Johan Myréen Apr 10 '17 at 12:30
  • amended the examples. I did mean $HOME/.local/bin and not $HOME/bin – tk-noodle Apr 10 '17 at 13:22
  • 2
    @taifwa: Your examples with bash and perl work identically for me, except that I get env: command not found so I have to use PATH=$HOME/bin /usr/bin/env bash. – Nick Matteo Apr 10 '17 at 16:21
  • @Christopher no it is not. Question edited to highlight the actual ask. – tk-noodle Apr 10 '17 at 23:33
  • See if it works with zsh/csh/sh. Sometimes Bash refuses to do things for highly contextual security situations, like for example you can't setuid root a copy of /bin/bash and expect it to actually work. See: http://unix.stackexchange.com/questions/74527/setuid-bit-seems-to-have-no-effect-on-bash – Tim G Apr 11 '17 at 00:17
  • 2
    I'm unable to reproduce your bash test. I need to give the full path to env because of the explicit PATH setting, and when I do, the command prints Hacked!. – Rob Kennedy Apr 11 '17 at 04:48
  • See this related question. On the security side, the attack is only possible if the user has added to their PATH a directory that's writable by untrusted others - in that case, almost any interactive shell is equally vulnerable, too. – Toby Speight Apr 11 '17 at 13:24
  • Thanks Toby - that reminded me of why I asked in the first place - the setXid caveat is the relevant piece – tk-noodle Apr 11 '17 at 13:29

3 Answers3

17

"what if somebody substitutes this executable for their own in say ~/.local/bin ?

Then the script doesn't work for them.

But that doesn't matter, since they could conceivably break the script for themselves in other ways, or run another program directly without messing with PATH or env.

Unless your users have other users' directories in their PATH, or can edit the PATH of other users, there's really no possibility of one user messing another one.


However, if it wasn't a shell script, but something that grants additional privilege, such as a setuid wrapper for some program, then things would be different. In that case, it would be necessary to use an absolute path to run the program, place it in a directory the unprivileged users cannot modify, and clean up the environment when starting the program.

ilkkachu
  • 138,973
  • on the one hand I intended to mean "what if someone maliciously substitutes the executable", but I see your point. And if someone maliciously got in there are so many other issues that this point becomes moot.... – tk-noodle Apr 10 '17 at 13:25
  • 5
    @taifwa, yep, if you can put something on my path, you can just put a modified ls there. I'll probably run it quite soon. – ilkkachu Apr 10 '17 at 19:52
  • 1
    "However, if you have a setuid wrapper for a script or another program" - If setuid is involved, then generally speaking a shebang is not involved, at least on most modern Unices. Setuid does nothing on shebanged scripts. – Kevin Apr 10 '17 at 21:14
  • 1
    @Kevin, yeah, I meant that a shell script isn't problematic in this since it can't have any more privilege than the running user. – ilkkachu Apr 10 '17 at 21:24
  • Oh and I remember why I asked this in the first place. If a script with setuid or setgid uses this env technique, I can get root access that way. So the caveat really is: don't let scripts with an env shebang have set*id – tk-noodle Apr 11 '17 at 13:28
  • @taifwa, yeah, as far I understand, systems that allow scripts to be setuid are an exception. The answer here has a description of the reasons why, and also states that OpenBSD, NetBSD and OS X can optionally allow it (they have a workaround for the most obvious problem). But yes, if your system allows setuid scripts, or if you run a script through a setuid wrapper (be it sudo or a custom one), make sure you don't depend on the PATH supplied by the running user. – ilkkachu Apr 11 '17 at 13:59
4

As for the difference in behavior between the shell and perl, perl unlike the shell inspects the first line to determine what to run:

$ cat tcl
#!/usr/bin/env tclsh
puts "TCL running as [pid]"
$ perl tcl
TCL running as 39689
$ cat sbcl
#!/opt/local/bin/sbcl --script
(format t "~a running as ~a~%" 'lisp (sb-unix:unix-getpid))
$ perl sbcl
LISP running as 39766
$ 

Uses of this feature include writing tests in some other language besides perl so one can have TAP-compatible scripts in other languages and a prove or whatnot will run them correctly.

thrig
  • 34,938
  • That's an interesting tidbit about Perl, but if the setup in the question is working as one might naively expect, the system perl should never be run at all, so its interpretation of shebang lines shouldn't be relevant here. The test is set up so that the only file named perl that env finds is the one in $HOME/.local/bin. Running that file should run (only) python, shouldn't it? – Rob Kennedy Apr 11 '17 at 04:56
  • @RobKennedy if env manages to somehow run perl (it does on Mac OS X, but not Linux for me...), then the exec-other-program feature of perl will come into play. This is not so if env manages to run instead the shell (unless that shell has a similar feature...). – thrig Apr 11 '17 at 14:11
1

There's no security risk here. /usr/bin/env is supposed to select the appropriate binary according to the user's environment. So if the user has installed their own bash in ~/.local/bin, then /usr/bin/env should indeed attempt to use it (they may, for example, have compiled a version with extra features that aren't available in the system-wide version, and would prefer to use it in place of the system-wide version). The same goes for any other binary/interpreter. There's no security risk, because the user won't be able to run anything that they wouldn't have been able to run anyway.

As for why the substitute is failing in your case, I doubt it has anything to do with /usr/bin/env. Try running PATH=~/.local/bin bash, PATH=~/.local/bin bash <path-to-script> (as it would be called when you run the script) and PATH=~/.local/bin /usr/bin/env bash and see which of those fail. That should give a clue as to what the "file not found" error is referring to.