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