[comp.unix.wizards] setuid shell scripts

gamiddleton@watmath.waterloo.edu (Guy Middleton) (05/25/88)

The following recently showed up in comp.bugs.4bsd.ucb-fixes:

	From: bostic@OKEEFFE.BERKELEY.EDU (Keith Bostic)
	Subject: setuid/setgid shell scripts are a security risk
	Index: sys/kern_exec.c 4.3BSD

	Description:
		Setuid/setgid shell scripts have inherent problems that
		may be used to violate security.  These problems cannot
		be fixed without completely revising the semantics of
		executable shell scripts.
	Fix:
		Panel your office in asbestos, and apply the following patch
		to sys/kern_exec.c.

	[ followed by a patch to disable setuid shell scripts ]

This seems unnecessarily drastic action.  We know what the problems with
setuid shell scripts are; there is a simple kernel change to fix them (or
at least, it fixes the problems we are aware of).  Why not fix the problem,
instead of removing a useful feature from the system?

 -Guy Middleton, University of Waterloo Institute for Computer Research
 gamiddleton@math.waterloo.edu, watmath!gamiddleton	"nobody uses it, anyway"

bostic@ucbvax.BERKELEY.EDU (Keith Bostic) (06/02/88)

In article <19045@watmath.waterloo.edu>, gamiddleton@watmath.waterloo.edu (Guy Middleton) writes:
> The following recently showed up in comp.bugs.4bsd.ucb-fixes:
> 
> 	From: bostic@OKEEFFE.BERKELEY.EDU (Keith Bostic)
> 	Subject: setuid/setgid shell scripts are a security risk
> 	Index: sys/kern_exec.c 4.3BSD
>
> This seems unnecessarily drastic action.  We know what the problems with
> setuid shell scripts are; there is a simple kernel change to fix them (or
> at least, it fixes the problems we are aware of).  Why not fix the problem,
> instead of removing a useful feature from the system?

The kernel fix that you (and other people) are proposing does not fix
this particular problem.

--keith

-
-
-
-
-
-

ka@june.cs.washington.edu (Kenneth Almquist) (10/20/88)

John Chambers asks:

> To be more specific, I'd sure like to know what it is about the
> shell programming languages (Bourne, C, K, T, etc...) that make
> them more risky than a C program.

Here is a possibly incomplete list of problems:

1.  The behavior of shell scripts can depend upon the environment variables
    passed in to them.  The fix to explicitly set every shell variable
    that your shell procedure uses.  Explicitly set shell variables to
    the empty string rather than assuming they will be initialized to
    the empty string by default.  More important, explicitly set all shell
    variables that the shell refers to implicitly.  This includes things
    like PATH and IFS.

2.  The #! passes the name of the shell script as argv[1], but the shell
    does not understand about this convention and will interpret argv[1]
    as an option rather than as a file name if the first character of
    argv[1] is a minus sign.  The fix is to write a little stub program
    in C for each setuid shell procedure.  The stub program is setuid, and
    simply does an exec of the shell with argv[1] set to the full path
    name of the actual shell procedure.  Since the path name of the shell
    procedure is hard coded into the stub, it is guaranteed not to start
    with a minus sign.  The file containing the shell procedure should not
    be either setuid or executable, to ensure that all user invoke the
    stub rather than running the shell procedure directly.

Aside from problems 1 and 2, there are also some more general issues:

3.  People tend to use the shell for quick hacks which work right in most
    cases.  But being a little bit insecure is like being a little bit
    pregnant.  The fix is to put as much thought into writing a setuid
    shell procedure as you would into writing a setuid C program.  In fact,
    writing a setuid shell procedure is *more* difficult that writing a
    setuid C program because the semantics of C are simpler.

4.  When you write a shell procedure your code depends upon a large body
    of software:  the shell and also all the programs that your shell
    procedure invokes.  These programs change.  Usually the changes are
    backward compatible enough for most purposes, but they may not achieve
    the level of backward compatibility required to preserve the security
    of a setuid shell script.  For example, csh doesn't have IFS.  If IFS
    were added to csh, most csh shell scripts would continue to work, but
    setuid csh scripts would have to be modified to set IFS to a standard
    value.  The fix is to write setuid programs in C.

OK, that last sentence deserves half a ":-)", but most of the time it's
not worth the trouble to write a setuid program as a shell procedure when
you have C available.  An exception is when you need to invoke an operation
(such as ps(1)) which requires you to run a separate program.  This is
enough of a pain to do without the shell (note that popen invokes the shell)
that I might use a setuid shell procedure.

In any case, this discussion started with the claim that System V Release 4
would ignore the setuid bits when interpreting "#!".  This affects various
interpreted languages like ICON, but as I explained in point 2 above, setuid
shell procedures implemented using a C language stub (which is the only way
I know of to implement setuid shell procedures if you want securely) don't
use the #! facility at all.
				Kenneth Almquist

guy@auspex.UUCP (Guy Harris) (10/21/88)

>2.  The #! passes the name of the shell script as argv[1], but the shell
>    does not understand about this convention and will interpret argv[1]
>    as an option rather than as a file name if the first character of
>    argv[1] is a minus sign.  The fix is to write a little stub program
>    in C for each setuid shell procedure.

Or, if the script is a Bourne shell script, put

	#! /bin/sh -

rather than

	#! /bin/sh

as the first line.  This causes the shell to be passed a "-" as an
argument before the script name; all versions of the Bourne shell that I
know of will stop processing option arguments when they see the "-".

I think the same applies to the Korn shell.

If it's a C shell script, the fix is to 1) get a version of the C shell
on your system that supports the "-b" flag (like the 4.3BSD C shell
does; SunOS, for instance, has had that since 3.2), and put

	#! /bin/csh -b

as the first line.  "-b" has much the same effect.

If it's neither a C nor a Bourne nor a Korn shell script, you're on your
own.

Note also that there's another problem with the first character of the
script beginning with "-"; this problem was fixed in 4.3BSD's kernel,
and that fix was in SunOS since 3.2 as well.

Nevertheless:

	1) there is another problem with the "#!" mechanism that makes
	   set-UID scripts insecure; it is not a problem with a
	   particular shell, so it potentially affects all shells

and

	2) even if you fix that (there are ways to fix it if your kernel
	   has a certain mechanism, which unfortunately most don't have,
	   yet), the other arguments against set-UID scripts still apply
	   - you're relying on a large, complicated piece of software,
	   namely the interpreter running your script, and there may be
	   sneaky little back doors like IFS that you don't even know about.

It's hard enough to make sure a C program is secure when run set-UID....

henry@utzoo.uucp (Henry Spencer) (10/22/88)

There are actually two problems here.  One is with the #! machinery,
and is the one that Guy is referring to.  That can probably be fixed if
one is sufficiently clever, and/or if one accepts a speed penalty.

The other is the general problem with setuid shell scripts:  the semantics
of the shell are quite complex and there is little control over low-level
details, which makes it relatively difficult to write cracker-proof shell
scripts.  This problem is solvable in principle, but it's one of those
cases where there have been so many problems found that nobody is at all
confident that there aren't any more.
-- 
The meek can have the Earth;    |    Henry Spencer at U of Toronto Zoology
the rest of us have other plans.|uunet!attcan!utzoo!henry henry@zoo.toronto.edu

greg@cantuar.UUCP (G. Ewing) (11/03/88)

Under how many of the following conditions does the problem
still exist:

   (A)	The shell checks the owner and set{u,g}id bits of the
	script it is about to execute to make sure it's okay.

   (B)	The "shell" isn't a shell or interpreter at all, and
	doesn't execute the script as a list of commands.

   (C)	The "shell" consists of the following program:

		main() {
		}

If any of these things prevent the problem, then I submit that
removing the setuid-#! facility is wrong.

Greg Ewing				Internet: greg@cantuar.uucp
Spearnet: greg@nz.ac.cantuar		Telecom: +64 3 667 001 x8357
UUCP:	  ...!{watmath,munnari,mcvax,vuwcomp}!cantuar!greg
Post:	  Computer Science Dept, Univ. of Canterbury, Christchurch, New Zealand
Disclaimer: The presence of this disclaimer in no way implies any disclaimer.

maart@cs.vu.nl (Maarten Litmaath) (11/05/88)

In article <850@cantuar.UUCP> greg@cantuar.UUCP (G. Ewing) writes:
\Under how many of the following conditions does the problem
\still exist:
\
\   (A)	The shell checks the owner and set{u,g}id bits of the
\	script it is about to execute to make sure it's okay.

Safe.

\   (B)	The "shell" isn't a shell or interpreter at all, and
\	doesn't execute the script as a list of commands.

Safe.

\   (C)	The "shell" consists of the following program:
\
\		main() {
\		}

Special case of 2.

\If any of these things prevent the problem, then I submit that
\removing the setuid-#! facility is wrong.

Questionable; every interpreter would have to take care of things, while
it should be the kernel who's getting them straight.

\Greg Ewing				Internet: greg@cantuar.uucp

Family?
-- 
George Bush:                          |Maarten Litmaath @ VU Amsterdam:
             Capt. Slip of the Tongue |maart@cs.vu.nl, mcvax!botter!maart

dik@uva.UUCP (Casper H.S. Dik) (11/07/88)

Hi there,

I might be wrong. But in SunOS 3.4 modifying your setuid-scripts:

from

#!<shell>

to 

#!<shell> <full pathname of script>
shift # throw away excess argument.

should close the gap.
This should work on all un*x systems whose kernel interprets an optional first
argument. This method guarantees the correct argument will be supplied to
the shell. It breaks, however, if the script can be removed/renamed by somebody
who isn't the owner or the superuser.

To find out wether your kernel does or doesn't allow for an extra argument
try the script:

#!/bin/echo yes

If this script echoes  'yes <scriptname>' you're in luck.
(It should echo <scriptname> in other cases, of course)
(It seems to work in 4.3BSD as well, but I couldn't find it in the docs)

____________________________________________________________________________
Casper H.S. Dik
University of Amsterdam     |		      dik@uva.uucp
The Netherlands             |                 ...!uunet!mcvax!uva!dik

maart@cs.vu.nl (Maarten Litmaath) (11/08/88)

In article <563@uva.UUCP> dik@uva.UUCP (Casper H.S. Dik) writes:
\#!<shell> <full pathname of script>
\shift # throw away excess argument.
\
\should close the gap.

Very neat solution, but there's a problem: the total length of shell name +
argument should not exceed 32 chars :-(
(very C64-like indeed!)
Then there's SunOS' csh, which expects a `-b' flag to be the first argument,
instead of the full path name, when executing a setuid script.
Of course both problems could be fixed easily.
Furthermore there's the increased difficulty in maintaining setuid shell
scripts: when you move one, you mustn't forget to edit it...

\... It breaks, however, if the script can be removed/renamed by somebody
\who isn't the owner or the superuser.

Indeed, but that would be a strange situation (dumb mistake) in itself.
-- 
George Bush:                          |Maarten Litmaath @ VU Amsterdam:
             Capt. Slip of the Tongue |maart@cs.vu.nl, mcvax!botter!maart

greg@cantuar.UUCP (G. Ewing) (11/14/88)

Sigh... confusion still abounds. I have received various replies
of the form:

Maarten Litmaath (maart@cs.vu.nl) writes:
>In article <850@cantuar.UUCP> greg@cantuar.UUCP (G. Ewing) writes:
>\   (A)	The shell checks the owner and set{u,g}id bits of the
>Safe.
>\   (B)	The "shell" isn't a shell or interpreter at all, and
>Safe.
>\   (C)	The "shell" consists of the following program:
>Special case of 2.

On the other hand, I've also had replies such as
(sorry, I don't know the sender's name in real life):

>From: <watmath!clyde!ulysses!smb>
>None of those things prevent the bug, I'm afraid, not singly, and not
>in combination.

and Chris Torek indicated in an earlier posting that there was a
problem that was *completely independent* of shell semantics.
Presumably this means that it doesn't matter if the shell isn't
a shell.

Maarten Litmaath again:
>\removing the setuid-#! facility is wrong.
>Questionable; every interpreter would have to take care of things, while
>it should be the kernel who's getting them straight.

I'd be quite happy for the kernel to do it right. I was just saying that
disabling the facility altogether might be overkill.

Or it might not.

Can you shed any light, Chris?

Greg Ewing				Internet: greg@cantuar.uucp
Spearnet: greg@nz.ac.cantuar		Telecom: +64 3 667 001 x8357
UUCP:	  ...!{watmath,munnari,mcvax,vuwcomp}!cantuar!greg
Post:	  Computer Science Dept, Univ. of Canterbury, Christchurch, New Zealand
Disclaimer: The presence of this disclaimer in no way implies any disclaimer.

chris@mimsy.UUCP (Chris Torek) (11/15/88)

In article <855@cantuar.UUCP> greg@cantuar.UUCP (G. Ewing) writes:
>and Chris Torek indicated in an earlier posting that there was a
>problem that was *completely independent* of shell semantics.

I hope that this is a paraphrase, for I did not mean that.  If the
interpreter does nothing with the script, there are no setid problems.

>Presumably this means that it doesn't matter if the shell isn't
>a shell.

It must pay some attention to its arguments.  Moreover, it must interpret
argv[1] or argv[2] as a file name.

>Maarten Litmaath again:
>>\removing the setuid-#! facility is wrong.
>>Questionable; every interpreter would have to take care of things, while
>>it should be the kernel who's getting them straight.

>I'd be quite happy for the kernel to do it right. I was just saying that
>disabling the facility altogether might be overkill.

It might; but there are no known uses for the (now disallowed) kernel
invocation of set-id #! scripts that are also secure.  ksh can be made
to interpret set-id scripts, but it works without #! doing the ID setting;
one installs ksh itself setuid root instead.  Similar changes could be
made to sh and csh.

Again: if your kernel implements `#!', but ignores set-id bits, or
if you have no set-id scripts, or if you do not have `#!' at all,
you are safe.  A safe way to run some script set-ID is to write a
C program that is set-id that runs `execl("/path/to/interpreter",
"interpreter", "-options", "/path/to/script", (char *)0)', and then
exits if that fails.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

greg@cantuar.UUCP (G. Ewing) (11/17/88)

Chris Torek (chris@mimsy.UUCP) writes:
>In article <855@cantuar.UUCP> greg@cantuar.UUCP (G. Ewing) writes:
>>and Chris Torek indicated in an earlier posting that there was a
>>problem that was *completely independent* of shell semantics.
>
>I hope that this is a paraphrase, for I did not mean that.  If the
>interpreter does nothing with the script, there are no setid problems.

Thanks for the clarification. I don't remember your exact words, but
whatever they were, they gave me that impression, causing great
confusion.

>there are no known uses for the (now disallowed) kernel
>invocation of set-id #! scripts that are also secure.

Just because nobody is using it now doesn't mean that there is
no use for it!

An interpreter for some programming language could be written
that was careful to check the mode and owner of any file that it
was about to execute, and if it was setu(g)id, refuse to continue
if its owner(group) didn't match the process's effective u(g)id.

Correct me if I'm wrong, but as things stand, this ought to be
safe, oughtn't it?

If so, disabling setuid #! files in the kernel removes a potentially
useful facility unnecessarily, and seems to me an excessively
drastic action to take.

Greg Ewing				Internet: greg@cantuar.uucp
Spearnet: greg@nz.ac.cantuar		Telecom: +64 3 667 001 x8357
UUCP:	  ...!{watmath,munnari,mcvax,vuwcomp}!cantuar!greg
Post:	  Computer Science Dept, Univ. of Canterbury, Christchurch, New Zealand
Disclaimer: The presence of this disclaimer in no way implies any disclaimer.

lwall@jpl-devvax.JPL.NASA.GOV (Larry Wall) (11/18/88)

In article <862@cantuar.UUCP> greg@cantuar.UUCP (G. Ewing) writes:
: An interpreter for some programming language could be written
: that was careful to check the mode and owner of any file that it
: was about to execute, and if it was setu(g)id, refuse to continue
: if its owner(group) didn't match the process's effective u(g)id.
: 
: Correct me if I'm wrong, but as things stand, this ought to be
: safe, oughtn't it?

Nope, sorry.  Still definitely unsafe.

If there was any way to do it, I'd have done it with perl.  I gave up and
disabled #! in my kernel, and now perl emulates set-id when necessary.
(Quite a trick disabling set-id #! in a binary only system!  I managed
it on a Vax by changing a branch, but the Sun's another story.  I'm trying
to wheedle the patch out of Sun but they're still thinking about it.
At least I hope they're still thinking about it...)

Not until a particular feature is hacked into the kernel will set-id #!
be secure again.  It may also be necessary to modify interpreters, though
there are ways to avoid that if they work it right.

Larry Wall
lwall@jpl-devvax.jpl.nasa.gov

greg@cantuar.UUCP (G. Ewing) (11/21/88)

Larry Wall (lwall@jpl-devvax.JPL.NASA.GOV) writes:
>In article <862@cantuar.UUCP> greg@cantuar.UUCP (G. Ewing) writes:
>: Correct me if I'm wrong, but as things stand, this ought to be
>: safe, oughtn't it?
>
>Nope, sorry.  Still definitely unsafe.

Why?

I'm probably being thick, but it seems to me that if you can find
or manufacture a fake script with the right owner and the setuid bit on,
then you can wreak havoc in any case. What am I missing here?

>I gave up and
>disabled #! in my kernel, and now perl emulates set-id when necessary.
>(Quite a trick disabling set-id #! in a binary only system!

>I'm trying
>to wheedle the patch out of Sun but they're still thinking about it.

Why go to all this bother? There seems to be agreement that you're
safe if you never create any setid scripts. So, why not just warn
your users not to do so?

(I still feel that nobbling the kernel is wrong, even if there really
is no use for it, but I'm willing to agree to differ on that.)

Greg Ewing				Internet: greg@cantuar.uucp
Spearnet: greg@nz.ac.cantuar		Telecom: +64 3 667 001 x8357
UUCP:	  ...!{watmath,munnari,mcvax,vuwcomp}!cantuar!greg
Post:	  Computer Science Dept, Univ. of Canterbury, Christchurch, New Zealand
Disclaimer: The presence of this disclaimer in no way implies any disclaimer.

dmcanzi@watdcsu.waterloo.edu (David Canzi) (11/21/88)

In article <3545@jpl-devvax.JPL.NASA.GOV> lwall@jpl-devvax.JPL.NASA.GOV (Larry Wall) writes:
>In article <862@cantuar.UUCP> greg@cantuar.UUCP (G. Ewing) writes:
>: An interpreter for some programming language could be written
>: that was careful to check the mode and owner of any file that it
>: was about to execute, and if it was setu(g)id, refuse to continue
>: if its owner(group) didn't match the process's effective u(g)id.
>: 
>: Correct me if I'm wrong, but as things stand, this ought to be
>: safe, oughtn't it?
>
>Nope, sorry.  Still definitely unsafe.

Okay, how about this?

The interpreter takes the command name it was called by and:
(1) derives a full pathname for it, that starts at root and
    contains no symlinks.  (I've thought over somewhat what's
    involved in this... one important thing is to be prepared
    to handle *anything* the user can put into $PATH.)
(2) checks that all directories in the path are searchable by
    the invoker, owned only by root or bin, and modifiable only
    by owner.
(3) checks that the file itself is executable by the invoker,
    and modifiable only by owner.  (Identity of invoker is to be
    determined from real uid of process, *not* controlling tty
    or environment variables.)
(4) If the interpreter's real and effective uids differ, the file
    is checked to make sure that it is indeed setuid to the effective
    uid of the process.  A similar check is make for gids.
(5) Open the input file, using the carefully checked full pathname
    and check its first line, to be sure that it contains
    "#!/usr/local/para-sh" or whatever it should contain.
(6) If any of the above steps fail, print "I feel insecure" and exit.
(7) Proceed to interpret the file's contents.  (This may mean calling
    a shell with the checked pathname.)

I may write a program to do this.  In addition, before invoking any
shell, I may build an all-new environment containing only a PATH
variable listing only trusted directories and a USER variable
containing the user's name, as determined from the real uid of the
process.

Is there anything I've left out?  Is there any hole in this through
which a clever user can extract some illicit advantage?  Should I give
up on computers (especially trying to understand security well enough
to implement some) and go into organic farming?

-- 
David Canzi

chris@mimsy.UUCP (Chris Torek) (11/22/88)

In article <5300@watdcsu.waterloo.edu> dmcanzi@watdcsu.waterloo.edu
(David Canzi) writes:
>Okay, how about this?
>(2) checks that all directories in the path are searchable by
>    the invoker, owned only by root or bin, and modifiable only
>    by owner.

Since you can check only one path component at a time, this is still
susceptible to spoofing.

(The `access()' syscall has the same problem.  The only way to be
*sure* that user 1234 has the permission to do something is to be user
1234 and do the something.  setreuid() does the trick, as does a
correct implementation of saved setuid [i.e., not the one in SysV].)
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

lwall@jpl-devvax.JPL.NASA.GOV (Larry Wall) (11/22/88)

David Canzi writes:
: The interpreter takes the command name it was called by and:
: (1) derives a full pathname for it, that starts at root and
:     contains no symlinks.  (I've thought over somewhat what's
:     involved in this... one important thing is to be prepared
:     to handle *anything* the user can put into $PATH.)

This would be quite a feat.  In fact, I believe it's impossible without a
kernel mod.

But even if you could do this in user mode, it's not good enough to prevent
the break.

: (2) checks that all directories in the path are searchable by
:     the invoker, owned only by root or bin, and modifiable only
:     by owner.

You've just outlawed . in anyone's PATH.

: (3) checks that the file itself is executable by the invoker,
:     and modifiable only by owner.  (Identity of invoker is to be
:     determined from real uid of process, *not* controlling tty
:     or environment variables.)

No quarrel here.  Perl does these things during set-id emulation.

: (4) If the interpreter's real and effective uids differ, the file
:     is checked to make sure that it is indeed setuid to the effective
:     uid of the process.  A similar check is make for gids.

You've just prevented any set-id program from running a script as a
subprocess.  All the scripts that people put setuid C wrappers around
because they didn't want their scripts setuid now blow up.

: (5) Open the input file, using the carefully checked full pathname
:     and check its first line, to be sure that it contains
:     "#!/usr/local/para-sh" or whatever it should contain.

Fine.  Suidperl does similarly.

: (6) If any of the above steps fail, print "I feel insecure" and exit.

If you wanna save a gob of CPU time just print "I feel insecure" to begin
with.  :-)

: (7) Proceed to interpret the file's contents.  (This may mean calling
:     a shell with the checked pathname.)
: 
: I may write a program to do this.  In addition, before invoking any
: shell, I may build an all-new environment containing only a PATH
: variable listing only trusted directories and a USER variable
: containing the user's name, as determined from the real uid of the
: process.

You give me a program just like that and I'll bust security with it.

[Boy, that sounds hubriscious.  Hubrisly.  Hubritical.  Whatever the
blamed adjective is.  Anyway, I'm not trying to be that way.  I think.
I'll be glad to discuss this more openly by mail.]

Larry Wall
lwall@jpl-devvax.jpl.nasa.gov