From: jmason2@gpu.utcs.utoronto.ca (Jamie Mason)
Newsgroups: comp.unix.wizards
Subject: Re: SUID shell scripts
Summary: Why to *NEVER* *EVER* user set-uid scripts!
Message-ID: <1991Sep19.125455.20375@gpu.utcs.utoronto.ca>
Date: 19 Sep 91 12:54:55 GMT
References: <1991Sep19.072220.206@dde.dk>

In article <1991Sep19.072220.206@dde.dk>, ct@dde.dk writes:
>Some say that shell scripts with the Set-User-ID bit set (on systems
>that allow it) are a security hazard. Why? If the script sets
>up PATH and IFS, is it not safe?

    NO.


    There are two reasons why not.  I'll tell you the easy one first.
Assume that there is a set-uid script owned by root (more fun that way :-)
called /local/bin/passwd (for instance).  Say that it is a bourne shell
script, and so the first line is

#!/bin/sh

    Most shells will run as a login shell of the first character of
their argv0 (first argument) starts with a '-'.  This is how login
manages to give you a login shell.  It calls csh as '-csh'.  One of the
things that a login shell does is read your .profile or .cshrc.

    On some systems, the shell is stupid enough to read and run
$HOME/.profile even if it is running set-uid (effective uid != real uid).

    So on these systems, set-uid shell scripts are VERY VERY easy
to break, and so are a big security hole from hell.  If you, or a
cracker, felt like root access, you (or they) could try:

% cat > .profile
	whoami  # to make sure.  id(1) would be fine as well.
	cp /bin/sh $HOME/SU
	chown root.wheel $HOME/SU
	chmod 6755 $HOME/SU
^D
% ln -s /local/bin/passwd -gotcha
% ./-gotcha
root
% ls -l SU
-rwsr-sr-x  1 root	wheel      106496 Oct 11  1990 SU
% ./SU
# whoami
root
# exit

    Now you have a set-uid root version of sh to play with.  It's
setgid wheel too, just for fun.  :-)



    Now for the second security hole -- on almost all #! systems...
When the kernel execs a file, it looks for a magic number in the first
two bytes.  If the magic number is '#!', then it takes the next one or
two tokens (up to 32 chars, usually), and tacks the full pathname of the
script on as an argument.  So if /u/joeuser/foo stared with:

#!/bin/sh
    then the kernel, in the process of loading this, would do:
/bin/sh /u/joeuser/foo
    Sh would have /u/joeuser/foo as $0 for the script.  If it was
#!/bin/csh -f
    then the kernel would execute
/bin/csh -f /u/joeuser/foo

    The important thing to note here is that the shell re-opens the
file for itself.  The kernel does not hand it an open file descriptor.

    So let's imagine that /local/bin/passwd example again.  If
you ran /local/bin/passwd, then the kernel would set uid to root.  Then
    it would call
/bin/sh /local/bin/passwd
    and /local/bin/passwd would do its stuff and exit.  No security hole
here, unless /local/bin/passwd is a badly written script that does not
set, for instance, $PATH.

    Ok.  But now lets try confusing things with a symlink, a
little different from before...

% ln -s /local/bin/passwd mylink
% cat > nasty_commands
	whoami  # to make sure.  id(1) would be fine as well.
	cp /bin/sh $HOME/SU
	chown root.wheel $HOME/SU
	chmod 6755 $HOME/SU
^D
% ./mylink

    So the kernel stat()s $HOME/mylink.  stat() follows the link and
sees the set-uid bit set and the owner being root on the other end of the
link (i.e. /local/bin/passwd).  So the kernel sets uid to root.  Then it
executes the following command:

/bin/sh /u/joeuser/mylink
    The shell opens /u/joeuser/mylink.  The open() follows the link
and opens the file at the other end (i.e. /local/bin/passwd) and executes
the commands from it.  Still no security hole.

    But what if while the kernel was doing this, you did:
% rm mylink; ln -s /u/joeuser/nasty_commands $HOME/mylink
    Now when the kernel followed mylink, it found /local/bin/passwd.
So it set-uid to root.  But my the time the SHELL followed mylink to
open it, it finds $home/nasty_commands, which it executes...  As root.

    Now you will almost certainly NOT win such a race with the kernel
>From the shell.  But you could do it in a C program.  You could
fork, then nice the parent into the dirt (i.e. 20) on a loaded system.
(If the system isn't loaded, load it.  Start some X applications :-)
The parent can then exec() your symlink.
    Meanwhile, in the child, you snip the symlink and make a new one.
Most of the time, you will lose the race.  But not EVERY TIME.  And if
the kernel loses the race JUST ONCE, then you win.  And you WIN BIG.

    Note that we put the same commands in nasty_commands as we put in
..profile in the last example.  I was trying to show what a cracker would
do.  If someone was trying to crack your system, then that is the type
of command they might issue.  They might put the suid sh in /tmp though,
or somewhere else not traceable to them.  Since they now have root access,
they can put it anywhere.  It may be lots of fun to find.  Once someone
has broken root on your system JUST ONCE, it is very hard to get rid of
them.  If they are smart, you may not even discover them.  If you do, you
may not ever find all the little goodies they left around the system.



    So no, set-uid shell (or anything) scripts are not secure, ever.

    If it was only set-uid to a user, rather than root, or it was only
set-gid to a special group, then the attacker will only be able to gain
the privileges of the user or group rather than root.  And the 
chown will have to come out of the attacker's script.  It might get
replaced by a chgrp.

