Assignment 4, Solutions

Part of the homework for 22C:169, Spring 2011
by Douglas W. Jones
THE UNIVERSITY OF IOWA Department of Computer Science

  1. Background: Here is a shell script that was given at the end of Friday's lecture:
    # shellscript args
    # a shell script to echo its args
    # create tempfile
    /bin/echo > tempfile
    set argc = $#argv
    @ count = 1
    while ($count <= $argc)
    	/bin/echo argv[ ${count} ] = $argv[$count] >> tempfile
    	@ count = $count + 1
    #output tempfile
    cat tempfile
    rm -f tempfile

    Notes: The > operator creates a file and directs standard output of the command to that file. The >> operator appends the output of the command to the indicated file. The quote marks 'text' suppress all interpretation of the quoted text. The quote marks "text" permit $ substitution within the quoted text. The quote marks `text` cause the indicated text to be executed as a shell command with the output of the command replacing the indicated string. The shell variable $$ is the current process ID. Each time you launch a new shell, $$ takes on a new value local to that shell. The shell command eval evaluates its arguments as a shell command.

    a) Can yhou find any vulnerabilities in this version of the script? (0.5 points)

    The only potential vulnerability is on the line:

            /bin/echo argv[ ${count} ] = $argv[$count] >> tempfile

    Here, if $argv[$count] contains characters such as ; > >> < or <<, then, depending on how the shell parses its input, there will be trouble. We can try these rather quickly:

    $ ./shellscript "a;" "b>" "c<" "d>>" "e<<"
    argv[ 1 ] = a;
    argv[ 2 ] = b>
    argv[ 3 ] = c<
    argv[ 4 ] = d>>
    argv[ 5 ] = e<<

    From this, we learn that tcsh recognizes these special characters before it does dollar-sign expansion, and therefore, this shell script may be safe.

    b) This script poses a possible threat to its users because of its use of a fixed file name, tempfile. How would the threat change if it used tempfile$$? (Would the threat be eliminated? If not, would the threat be reduced?) (0.5 points)

    If the user happens to call a file tempfile, there will be trouble. Tempfile is a particularly likely name for a user to use. In contrast, if the script used tempfile$$, the result would be names like tempfile12698 on the first use, and possibly tempfile12713 on the next use. These are less likely to collide with fixed names chosen by the user, but collisions are still possible.

    c) Suppose you changed $argv[$count] to `echo $argv[$count]`. Does this create any vulnerabilities? (Experiment!) (0.5 points)

    Remarkably, no! It appears that the `echo args` construct parses its input into a sequence of arguments before it applies dollar sign substitutions, so if the argument contains a semicolon or something similar, it remains a single string, as if it is implicitly quoted with single-quotes, suppressing all further processing of the argument.

    d) Suppose you changed $argv[$count] to `eval echo $argv[$count]`. Why is this different from your answer to part c? (Experiment!) (0.5 points)

    Now, things begin to happen. Consider this:

    $ ./shellscript '$count' '$count' '$count'
    argv[ 1 ] = 1
    argv[ 2 ] = 2
    argv[ 3 ] = 3

    Or, consider this:

    ~$ ./shellscript a '; rm tempfile;' c
    argv[ 3 ] = c

  2. Background: Here is another shell script, one that is somewhat more sophisticated:
    # foreachfile command
    # a shell script to apply command to each file in the current directory
    set ListOfFiles = `ls`
    set Count = 1
    set ListLength = $#ListOfFiles
    while ($Count <= $ListLength)
            $argv $ListOfFiles[$Count]
            @ Count = $Count + 1

    For example foreachfile echo will list each file name, one per line, and foreachfile ls -dl will do almost the same thing as the ls -l command, but with rather poorer formatting. Variations on this script could be useful for such things as making backups and many other things, but there are several severe problems with this script.

    a) This script does not work correctly when file names contain blanks. What is the problem? (0.5 points)

    Here is an example of what happened when I created a file named "for each file" with spaces in it:

    $ ./foreachfile echo

    What happened? The blanks in the file name were interpreted as delimiters between file names.

    b) What is the difference between foreachfile echo and foreachfile eval echo? Does this say anything about the vulnerability of this script to shell injection attacks? (0.5 points)

    Consider the following two examples:

    $ ./foreachfile echo a ";" echo
    a ; echo foreachfile
    $ ./foreachfile eval echo a ";" echo

    This makes it clear that injecting the echo command opens the door to just about everything.

    c) Suppose your directory has a file names that are also Unix shell command names such as date and ls. Can you make this script execute those commands? If you can, this becomes another possible path for an injection attack. (0.5 points)

    The command foreachfile with no arguments at all tries to execute all of the file names in the directory, treating each string as an indivisible lump. foreachfile eval is more dangerous because it will try to parse each file name, so if a name contains a semicolon or I/O redirection character, it will be seen.

  3. Background: In answering the parts of problem 2, you no-doubt found several vulnerabilities in the shell script given in that problem. Fix them using the tools outlined in Friday's lecture. In the event that a problem is found, your modified script should echo an error message to standard error using echo errmsg >/dev/stderr (NOTE: The former version of this assignment said to use 1>&2 -- a mechanism that only works in the bash shell. Having output the error message, your modified shell should exit using the exit command. (1.5 points)

    We need to forbid blank arguments and forbid eval as an argument. Do this by adding the following before the body of the script:

    if ( "$argv" == "" ) then
            echo argument must not be blank > /dev/stderr
    else if ( "$argv[1]" == "eval" ) then
            echo argument must not be eval > /dev/stderr