maart@cs.vu.nl.UUCP (Maarten Litmaath) (12/29/88)
Posting-number: Volume 5, Issue 97 Submitted-by: "Maarten Litmaath" <maart@cs.vu.nl.UUCP> Archive-name: setuid [I don't care how "secure" you make them, I still don't trust them.... ++bsa] [As usual, any setuid gurus want to comment on the security or lack of same in this one? ++bsa] Instead of patching David's original idea, I tried a different approach. Using David's solution one must maintain a `database' of valid setuid shell scripts and ordinary users cannot create new entries unless there's an update program accessible to them too. It's /bin/secure who's setuid. Using my method it's again the shell script itself who's got the setuid bit. /bin/setuid (notice slightly different name :-) is a mere executable DOING THE RIGHT THINGS. Read the manual, examine the source, be convinced :-) Regards, Maarten Litmaath @ VU Amsterdam: maart@cs.vu.nl, mcvax!botter!maart : This is a shar archive. Extract with sh, not csh. : This archive ends with exit, so do not worry about trailing junk. : --------------------------- cut here -------------------------- PATH=/bin:/usr/bin:/usr/ucb echo Extracting 'setuid.8' sed 's/^X//' > 'setuid.8' << '+ END-OF-FILE ''setuid.8' X.\" maart@cs.vu.nl - Maarten Litmaath Fri Dec 23 10:13:51 MET 1988 X.\" X.TH SETUID 8 "Dec 23, 1988" X.UC 4 X.SH NAME X.B setuid X\- run setuid shell scripts safely X.SH SYNOPSIS X.B #! /bin/setuid X.br X.SH DESCRIPTION X.B setuid Xis never normally executed from a shell. Instead it can be used Xas the interpreter for shell scripts that need to be run setuid to someone Xelse: this is done by making the first line of the script X.PP X.ti+5n X#! /bin/setuid X.PP Xrather than the usual X.PP X.ti+5n X#! /bin/sh X.PP X.B setuid Xtries to open the script for reading. If successful it discards the first Xline (containing the \fB#! /bin/setuid\fR) and tries to read a line Xformatted as follows: X.br X.br X.PP X.ti+5n X#? absolute\-path\-of\-interpreter arguments X.PP XExample: X.PP X.ti+5n X#? /bin/csh -bf /usr/etc/setuid_script X.PP XLinking to the script in order to gain root privileges is useless, because Xthe script contains its own invocation command sequence, including its Xabsolute path: the real name of the link cannot be found in the ultimate Xcommand. X.B setuid Xwill only Xexec a pathname beginning with a '/', to improve security. XOf course X.B setuid Xcan be `fooled' by supplying dubious arguments to the interpreter, like Xrelative pathnames. Furthermore it is a mistake to let the last directory Xcomponent of the ultimate path be writable by others. In our example /usr/etc Xshould not be writable for ordinary users. X.PP XIn addition, for the sake of security, X.B setuid Xsets the PATH environment variable back to a simple default, and deletes Xthe IFS environment variable. X.SH AUTHOR XMaarten Litmaath @ VU Informatika Amsterdam (maart@cs.vu.nl) X.SH "SEE ALSO" X.BR sh (1), csh (1) X.SH BUGS XThe maintenance of setuid scripts is a bit annoying: if a script is moved, Xone must not forget to change the path mentioned in the script. Possibly Xthe editing causes the setuid bit to get turned off. + END-OF-FILE setuid.8 chmod 'u=rw,g=r,o=r' 'setuid.8' set `wc -c 'setuid.8'` count=$1 case $count in 1867) :;; *) echo 'Bad character count in ''setuid.8' >&2 echo 'Count should be 1867' >&2 esac echo Extracting 'setuid.c' sed 's/^X//' > 'setuid.c' << '+ END-OF-FILE ''setuid.c' Xchar sccsid[] = "@(#) setuid.c 1.0 88/12/24 Maarten Litmaath"; X X/* X * setuid.c X * execute setuid shell scripts safely X * see setuid.8 X */ X X#include <stdio.h> X X X#define COMMENT '#' X#define MAGIC '?' X#define MAXARGV 1024 X#define MAXLEN 256 X#define SEC_ARGC 1 X#define SEC_OPEN 2 X#define SEC_FMT 3 X#define SEC_READ 4 X#define SEC_EXEC 5 X X Xtypedef int bool; X X Xchar E_argc[] = "%s: argument expected\n", X E_open[] = "%s: cannot open ", X E_fmt[] = "%s: format error in %s\n", X E_read[] = "%s: read error in ", X E_exec[] = "%s: cannot execute ", X Defaultpath[] = "/bin:/usr/bin", X *interpreter, X *newargv[MAXARGV]; X X Xmain(argc, argv) Xint argc; Xchar **argv; X{ X FILE *fp; X int c; X char *prog, *strrchr(); X X X if (!(prog = strrchr(argv[0], '/'))) X prog = argv[0]; X else X ++prog; X X if (argc != 2) { X fprintf(stderr, E_argc, prog); X exit(SEC_ARGC); X } X X if (!(fp = fopen(argv[1], "r"))) { X fprintf(stderr, E_open, prog); X perror(argv[1]); X exit(SEC_OPEN); X } X X while ((c = getc(fp)) != '\n' && c != EOF) /* skip #! line */ X ; X X X if (!getparams(fp)) { X if (ferror(fp)) { X fprintf(stderr, E_read, prog); X perror(argv[1]); X exit(SEC_READ); X } X fprintf(stderr, E_fmt, prog, argv[1]); X exit(SEC_FMT); X } X X#ifdef DEBUG X for (c = 0; newargv[c]; c++) X printf("newargv[%d]='%s'\n", c, newargv[c]); X#endif DEBUG X X (void) unsetenv("IFS"); X (void) setenv("PATH", Defaultpath, 1); X X execv(interpreter, newargv); X X fprintf(stderr, E_exec, prog); X perror(interpreter); X exit(SEC_EXEC); X} X X Xstatic bool getparams(fp) XFILE *fp; X{ X char buf[MAXLEN], *skipblanks(), *skiptoblank(), *strrchr(); X register char *p; X register int i = 0; X X X for (;;) { X if (!fgets(buf, sizeof buf, fp) || X buf[strlen(buf) - 1] != '\n') X return 0; X X p = skipblanks(buf); X X switch (*p++) { X case '\n': X continue; X case COMMENT: X break; X default: X return 0; X } X X if (*p++ == MAGIC) X break; X } X X p = skipblanks(p); X X if (*(interpreter = p) != '/') X return 0; X X p = skiptoblank(p); X X if (*p == '\n') X return 0; X X *p++ = '\0'; X X newargv[0] = strrchr(interpreter, '/') + 1; X X while (i < MAXARGV - 1) { X p = skipblanks(p); X X if (*p == '\n') X break; X X newargv[++i] = p; X X p = skiptoblank(p); X X if (*p == '\n') X break; X X *p++ = '\0'; X } X X newargv[++i] = 0; X X /* X * we expect 2 args at least: interpreter + file X * furthermore we expect p to point to end of line by now X */ X X if (i <= 1 || *p != '\n') X return 0; X X *p = '\0'; X return 1; X} X X Xstatic char *skipblanks(p) Xregister char *p; X{ X while (*p == ' ' || *p == '\t') X ++p; X X return p; X} X X Xstatic char *skiptoblank(p) Xregister char *p; X{ X while (*p != ' ' && *p != '\t' && *p != '\n') X ++p; X X return p; X} + END-OF-FILE setuid.c chmod 'u=rw,g=r,o=r' 'setuid.c' set `wc -c 'setuid.c'` count=$1 case $count in 2671) :;; *) echo 'Bad character count in ''setuid.c' >&2 echo 'Count should be 2671' >&2 esac exit 0