Chapter 10. Korn Shell Administration

System administrators use the shell as part of their job of setting up a system-wide environment for all users. In this chapter, we discuss the Korn shell’s features that relate to this task from two perspectives: customization that is available to all users and system security. We assume that you already know the basics of Unix system administration.[123]

Installing the Korn Shell as the Standard Shell

As a prelude to system-wide customization, we want to emphasize something about the Korn shell that doesn’t apply to most other shells: you can install it as if it were the standard Bourne shell, i.e., as /bin/sh. Just save the real Bourne shell as another filename, such as /bin/bsh, in case anyone actually needs it for anything (which is doubtful), then rename (or link) your Korn shell as /bin/sh.

Many installations have done this with absolutely no ill effects. Not only does this make the Korn shell your system’s standard login shell, but it also makes most existing Bourne shell scripts run faster, and it has security advantages that we’ll see later in this chapter.

As we will see in Appendix A, the Korn shell is backward-compatible with the Bourne shell except that it doesn’t support ^ as a synonym for the pipe character |. Unless you have an ancient Unix system, or you have some very, very old shell scripts, you needn’t worry about this.

But if you want to be absolutely sure, simply search through all shell scripts in all directories in your PATH. An easy way to do this is to use the file(1) command, which we saw in Chapter 5 and Chapter 9. file prints “executable shell script” when given the name of one. (The exact message varies from system to system; make sure that yours prints this message when given the name of a shell script. If not, just substitute the message your file command prints for “shell script” in the following example.) Here is a script that looks for ^ in shell scripts in every directory in your PATH:[124]

dirs=$(print -- $PATH |
	sed -e 's/^:/.:/' -e 's/::/:.:/' -e s'/:$/:./' -e 's/:/ /g')
for d in $dirs
do
	print "checking $d:"
	cd "$d"
	scripts=$(file * | grep 'shell script' | cut -d: -f1)
	grep -l '^' $scripts /dev/null
done

The first statement of this script pulls $PATH apart into separate directories, including handling the several cases of empty separators which signify the current directory. The sed(1) program is a stream editor that performs editing operations on its input, and prints the changed contents on its output. The result is assigned to dirs, which is then used as the item list in the for loop. For each directory, it cds there and finds all shell scripts by piping the file command into grep and then, to extract the filename only, into cut. Then it searches each script for the ^ character. The -l option to grep simply lists all filenames that match the pattern, without printing the matching lines. The grep command has /dev/null on the end of the list of files in case $scripts happens to be empty. If you’re adventurous, you could do all the work on one line:

grep -l '^' $(file * | grep 'shell script' | cut -d: -f1) /dev/null

If you run this script, you will probably find several occurrences of ^, but these should be used within regular expressions in grep, sed, or awk commands, not as pipe characters. Assuming this is the case, it is safe for you to install the Korn shell as /bin/sh.

Environment Customization

Like the Bourne shell, the Korn shell uses the file /etc/profile for system-wide customization. When a user logs in, the shell reads and runs /etc/profile before running the user’s .profile.

We don’t cover all the possible commands you might want to put in /etc/profile. But the Korn shell has a few unique features that are particularly relevant to system-wide customization; we discuss them here.

We’ll start with two built-in commands that you can use in /etc/profile to tailor your users’ environments and constrain their use of system resources. Users can also use these commands in their .profile, or at any other time, to override the default settings.

umask

umask, like the same command in most other shells, lets you specify the default permissions that files have when users create them. With ksh, it takes the same types of arguments that the chmod command does, i.e., absolute (octal numbers) or symbolic permission values.

The umask contains the permissions that are turned off by default whenever a process creates a file, regardless of what permission the process specifies.[125] Another way to think of this is as a bitwise borrow-free subtraction: actual permissions = requested permissions - the umask.

We’ll use octal notation to show how this works. As you should know, the digits in a permission number stand (left to right) for the permissions of the owner, the owner’s group, and all other users, respectively. Each digit, in turn, consists of three bits, which specify read, write, and execute permissions from left to right. (If a file is a directory, the “execute” permission becomes “search” permission, i.e., permission to cd to it, and to traverse it as part of a pathname.)

For example, the octal number 640 equals the binary number 110 100 000. If a file has this permission, then its owner can read and write it; users in the owner’s group can only read it; everyone else has no permission on it. A file with permission 755 (111 101 101 in binary) gives its owner the right to read, write, and execute it and everyone else the right to read and execute (but not write).

022 is a common umask value. This implies that when a file is created, the most permission it could possibly have is 755 — which is the usual permission of an executable that a compiler might create. A text editor, on the other hand, might create a file with 666 permission (read and write for everyone), but the umask forces it to be 644 instead.

The -S option to umask causes it to work with symbolic values instead of with octal numbers. When used without an argument, umask -S prints the umask in symbolic form. With an argument, the mask is changed. In both cases, a symbolic mask represents the permissions to keep for a file. (This ends up being the bitwise complement of the traditional octal umask, which represents permissions to remove.) If you’re confused, some examples should clear things up:

$ umask                       
                  Print the current umask, in octal
0022
$ umask -S                    
                  Print the current umask, in symbolic form
u=rwx,g=rx,o=rx
$ umask -S u=rwx,g=r,o=       
                  Change the umask using the symbolic form
$ umask -S                    
                  Print it back out symbolically
u=rwx,g=r,o=
$ umask                       
                  Print it in octal
0037

ulimit

Early Unix systems didn’t impose any limits on what resources a process could use. If a program wanted to run forever, it could. If a program wanted to create large files and fill up a disk, it could. And so on.

As Unix developed and matured, it became possible to explicitly control, or limit, a variety of different system resources, such as CPU time and disk space. The ulimit command is the Korn shell’s interface for viewing and changing the limits on system resources. Table 10-1 lists the options it accepts and the corresponding resources. Not all options are available on all Unix systems. Many won’t be available on non-Unix systems.

Table 10-1. ulimit resource options
OptionResource limitedOptionResource limited
-a All (for printing values only) -n File descriptors
-c Core file size (½ kb blocks) -p

Pipe buffer size (½ kb blocks)[a]

-d Process data segment (kb) -s Process stack segment (kb)
-f File size (½ kb blocks) -t Process CPU time (seconds)
-m Physical memory (kb) -v Virtual memory (kb)

[a] Most Unix systems don’t have this feature.

Each takes a numerical argument that specifies the limit in units shown in the table. (You may use an arithmetic expression for the limit; ksh automatically evaluates the expression.) You can also give the argument “unlimited” (which may actually mean some physical limit), or you can omit the argument, in which case it prints the current limit. ulimit -a prints the limits (or “unlimited”) for all types. You can only specify one type of resource at a time. If you don’t specify any option, -f is assumed.

Some of these options depend on operating system capabilities that don’t exist in older Unix versions. In particular, some older versions have a fixed limit of 20 file descriptors per process (making -n irrelevant), and some don’t support virtual memory (making -v irrelevant).

The -d and -s options have to do with dynamic memory allocation, i.e., memory for which a process asks the operating system at runtime. It’s not necessary for casual users to limit these, though software developers may want to do so to prevent buggy programs from trying to allocate endless amounts of memory due to infinite loops.

The -v option is similar; it puts a limit on all uses of memory. You don’t need this unless your system has severe memory constraints or you want to limit process size to avoid thrashing.

You may want to specify limits on file size (-f and -c) if you have constraints on disk space. Sometimes users actually mean to create huge files, but more often than not, a huge file is the result of a buggy program that goes into an infinite loop. Software developers who use debuggers like gdb and dbx should not limit core file size, because core dumps are often helpful for debugging.

The -t option is another possible guard against infinite loops. On single-user systems, a program that is in an infinite loop but isn’t allocating memory, writing files, or using the network is not particularly dangerous; it’s better to leave this unlimited and just let the user kill the offending program. However, on shared server systems, such programs definitely degrade the overall environment. The problem in that case is that it’s difficult to know what limit to set: there are important and legitimate uses for long-running programs.

In addition to the types of resources you can limit, ulimit lets you specify hard or soft limits. Hard limits can be lowered by any user but only raised by the superuser (root); users can lower soft limits and raise them — but only as high as the hard limit for that resource.

If you give -H along with one (or more) of the options above, ulimit sets hard limits; -S sets soft limits. Without either of these, ulimit sets both. For example, the following commands set the soft limit on file descriptors to 64 and the hard limit to unlimited:

ulimit -Sn 64
ulimit -Hn unlimited

When ulimit prints current limits, it prints the soft limits unless you specify -H.

Types of Global Customization

The best possible approach to globally available customization would be a system-wide environment file that is separate from each user’s environment file — just like /etc/profile is separate from each user’s .profile.

Unfortunately, the Korn shell doesn’t have this feature. If you assign a filename to the ENV environment variable, it could be overridden in a user’s .profile. This allows you to make a default environment file available for users who don’t have their own, but it doesn’t let you have a system-wide environment file that runs in addition to the users’. Furthermore, the environment file is only run for interactive shells, not all shells.

Nevertheless, the shell gives you a few ways to set up customizations that are available to all users at all times. Environment variables are the most obvious; your /etc/profile file will undoubtedly contain definitions for several of them, including PATH and TERM.

The variable TMOUT is useful when your system supports dialup lines. We have already seen that it affects the read command and the select menu loop. When set to a number N, if a user doesn’t enter a command within N seconds after the shell last issued a prompt, the shell prints the warning message shell will timeout in 60 seconds due to inactivity. If, after a further 60 seconds, the user does not enter anything, the shell terminates. This feature is helpful in preventing people from “hogging” the dialup lines. Just make sure you set it to a reasonable value!

You may want to include some more complex customizations involving environment variables, such as the prompt string PS1 containing the current directory, user name, or hostname (as seen in Chapter 4).

You can also turn on options, such as emacs or vi editing modes, noclobber to protect against inadvertent file overwriting, and perhaps ignoreeof to keep people from logging off by accident when they type too many CTRL-D characters. Any shell scripts you have written for general use also contribute to customization.

Unfortunately, it’s not possible to create a global alias. You can define aliases in /etc/profile, but there is no way to make them part of the environment so that their definitions will propagate to shell subprocesses.

However, you can set up global functions. These are an excellent way to customize your system’s environment, because functions are part of the shell, not separate processes. For example, you might wish to make pushd and popd (see Chapter 4 through Chapter 6) globally available.

The best way to create global functions is to use the built-in variable FPATH for autoloading of functions that we introduced in Chapter 4. Just define FPATH as a function library directory, perhaps /usr/local/functions, and make it an environment variable by exporting it. Then make sure that the directory listed in FPATH is also included in PATH. In other words, put this or similar code in /etc/profile:

FPATH=/usr/local/functions
PATH=$PATH:$FPATH
export FPATH PATH

Then put each global function’s definition in a file in that directory with the same name as the function.

In any case, we suggest using global functions for global customization instead of shell scripts. Given how cheap memory is nowadays, there is no reason why you shouldn’t make generally useful functions part of your users’ environment.

Customizing the Editing Modes

As we saw in Chapter 2, you have your choice of either emacs or vi editing modes when editing your command line. Besides the commands available in each mode, you can customize the behavior of the editing modes to suit your needs or environment.

Appendix A discusses a number of third party shells based on the Bourne and Korn shell design. Those shells generally provide command-line editing, as well as the ability to customize the editor via a special built-in command, a special start-up file, or both.

The Korn shell’s approach is different. It is based on a paradigm where you program the behavior you want from the shell. This is accomplished via a fake trap, named KEYBD. If it exists, the trap set for KEYBD is evaluated when ksh processes normal command-line input characters.[126] Within the code executed for the trap, two special variables contain the text of the command line and the text being entered that caused the trap. Additional special variables allow you to distinguish between emacs- and vi-modes and indicate the current position on the input line. These variables are listed in Table 10-2.

Table 10-2. Special editing variables
VariableMeaning
.sh.edchar

The character or escape sequence entered by the user that caused the KEYBD trap. The value of .sh.edchar at the end of the trap is then used to direct the actions of the built-in editor.

.sh.edcol

The position of the cursor on the current input line.

.sh.edmode

Equal to ESC in vi-mode, empty otherwise. (Use [[ -n ${.sh.edmode} ]] to test it.)

.sh.edtext

The text of the current input line.

Upon entering the KEYBD trap, the contents of .sh.edchar will be either a single character, ESC followed by a single character, or ESC, [, and a single character. You can assign a new value to .sh.edchar to change the input that the current editing mode receives. Thus, the KEYBD trap allows you to interpose a “filter” between what the user enters and what the shell editing modes actually process. The following example is from page 98 of The New KornShell Command and Programming Language.[127] It presents a keybind function that allows you to bind new actions to input key sequences, similar to the built-in bind command of many other shells.

      # Quoted from Page 98 of
      # The New KornShell Command and Programming Language

 1    typeset -A Keytable
 2    trap 'eval "${Keytable[${.sh.edchar}]}"' KEYBD
 3    function keybind # key [action]
 4    {
 5         typeset key=$(print -f "%q" "$2")
 6         case $# in
 7         2)      Keytable[$1]=' .sh.edchar=${.sh.edmode}'"$key"
 8                 ;;
 9         1)      unset Keytable[$1]
10                 ;;
11         *)      print -u2 "Usage: $0 key [action]"
12                 return 2 # usage errors return 2 by default
13                 ;;
14         esac
15    }

This is an interesting function. Let’s go through it line by line. Line 1 creates an associative array to act as a table of key/action pairs. Line 2 sets the KEYBD trap. It gets the action out of the associative array and then executes it using eval. Line 3 starts the keybind function, which takes one or two arguments. With two arguments, the second argument is first quoted appropriately (line 5 — the key variable would have been better-named action). Line 7 then creates the entry in the array, using $1 (the user key sequence) as the index, and quoted action as the value to assign to .sh.edchar. Note how ${.sh.mode} is also included. This has the effect of forcing a switch to command mode for the vi editing mode. It is this generated assignment statement that is evaled every time the trap executes.

The rest of the function is mostly bookkeeping: with one argument (line 9), the given entry in the Keytable array is removed. If more than two arguments (line 11), keybind prints a message and then returns the (false) value 2.

While somewhat unusual, the KEYBD trap mechanism for dealing with user input is both general and extensible; you can do whatever you want, as just a Simple Matter of Programming. With other shells, you’re limited to whatever built-in facilities they provide.

System Security Features

Unix security is a problem of legendary notoriety. Just about every aspect of a Unix system has some security issue associated with it, and it’s usually the system administrator’s job to worry about this issue.

Note

This is not a textbook on Unix system security. Be aware that this section merely touches the tip of the iceberg and that there are myriad other aspects to Unix system security besides how the shell is set up. See the end of the chapter for one book that we recommend.

We first present a list of “tips” for writing shell scripts that have a better chance of avoiding security problems. Next we cover the restricted shell, which attempts to put a straitjacket around the user’s environment. Then we present the idea of a “trojan horse,” and why such things should be avoided. Finally we discuss the Korn shell’s privileged mode, which is used with shell scripts that run as if the user were root.

Tips for Secure Shell Scripts

Here are some tips for writing more secure shell scripts, courtesy of Professor Eugene (Gene) Spafford, the director of Purdue University’s Center for Education and Research in Information Assurance and Security:[128]

Don’t put dot in PATH

This issue was described in Chapter 3. This opens the door wide for “trojan horses,” described in the next section.

Protect bin directories

Make sure that every directory in $PATH is writable only by its owner and by no one else. The same applies to all the programs in the bin directories.

Design before you code

Spend some time thinking about what you want to do and how to do it; don’t just type stuff in with a text editor and keep hacking until it seems to work. Include code to handle errors and failures gracefully.

Check all input arguments for validity

If you expect a number, verify that you got a number. Check that the number is in the correct range. Do the same thing for other kinds of data; the shell’s regular expression facilities are particularly useful for this.

Check error codes from all commands that can return errors

Things you may not expect to fail might be mischievously forced to fail to cause the script to misbehave. For instance, it is possible to cause some commands to fail even as root if the argument is a NFS-mounted disk or a character-oriented device file.

Don’t trust passed-in environment variables

Check and reset them to known values if they are used by subsequent commands (e.g., TZ, FPATH, PATH, IFS, etc.). The Korn shell automatically resets IFS to its default upon startup, ignoring whatever was in the environment, but many other shells don’t. In all cases it’s an excellent idea to explicitly set PATH to contain just the system bin directories.

Start in a known place

Explicitly cd to a known directory when the script starts so that any subsequent relative pathnames are to a known location. Be sure the that cd succeeds:

cd app-dir || exit 1

Use full pathnames for commands

Do this so you know which version you are getting, regardless of $PATH.

Use syslog(8) to keep an audit trail

Log the date and time of invocation, username, etc.; see logger(1). If you don’t have syslog, create a function to keep a log file:

function logsys {
   print -r -- "$@"  >>  /var/adm/logsysfile
}
logsys "Run by user "  $(/bin/whoami)  "($USER) at "  $(/bin/date)

(whoami(1) prints the login name of the effective user ID, a concept described later in this chapter.)

Always quote user input when using that input

E.g., "$1" and "$*". This prevents malicious user input from being further evaluated and executed.

Don’t use eval on user input

Beside quoting user input, don’t hand it to the shell to reprocess with eval. If the user reads your script and sees that it uses eval, it’s easy to subvert the script into doing almost anything.

Quote the results of wildcard expansion

There are several nasty things you can do to a system administrator by creating files with spaces, semicolons, back-quotes, and so on in the filenames. If administrative scripts don’t quote the filename arguments, the scripts can trash — or give away — the system.

Check user input for metacharacters

Look for metacharacters such as $ or ` (old-style command substitution) if using the input in an eval or $(...).

Test your code and read it critically

Look for assumptions and mistakes that can be exploited. Put yourself into a nasty mood, and read your code with the intent of trying to figure out how to subvert it. Then fix whatever problems you find.

Be aware of race conditions

If an attacker can execute arbitrary commands between any two commands in your script, will it compromise security? If so, find another way to do it.

Suspect symbolic links

When chmoding or editing a file, check it to be sure it is a file and not a symbolic link to a critical system file. (Use [[ -L file ]] or [[ -h file ]] to test if file is a symbolic link.)

Have someone else review your code for mistakes

Often a fresh pair of eyes can spot things that the original author of a program misses.

Use setgid instead of setuid, if possible

These terms are discussed later in this chapter. In brief, by using setgid, you restrict the amount of damage that can be done to the group that is compromised.

Use a new user instead of root

If you must use setuid to access a group of files, consider making a new, non-root user for that purpose, and setuid to it.

Limit setuid code as much as possible

Make the amount of setuid code as small as you can. Move it into a separate program, and invoke that from within a larger script when necessary. However, be sure to code defensively as if the script can be invoked by anyone from anywhere else!

Chet Ramey, the maintainer of bash, offers the following prologue for use in shell scripts that need to be more secure:

# reset IFS, even though ksh doesn't import IFS from the environment,
# $ENV could set it
IFS=$' 	
'

# make sure unalias is not a function, since it's a regular builtin
# unset is a special builtin, so it will be found before functions
unset -f unalias

# unset all aliases
# quote unalias so it's not alias-expanded
unalias -a

# make sure command is not a function, since it's a regular builtin
# unset is a special builtin, so it will be found before functions
unset -f command

# get a reliable path prefix, handling case where getconf is not
# available (not too necessary, since getconf is a ksh93 built-in)
SYSPATH="$(command -p getconf PATH 2>/dev/null)" 
if [[ -z "$SYSPATH" ]]; then
        SYSPATH="/usr/bin:/bin"         # pick your poison
fi
PATH="$SYSPATH:$PATH"

Restricted Shell

The restricted shell is designed to put the user into an environment where his or her ability to move around and write files is severely limited. It’s usually used for guest accounts. When invoked as rksh (or with the -r option), ksh acts as a restricted shell. You can make a user’s login shell restricted by putting the full pathname to rksh in the user’s /etc/passwd entry. The ksh executable file must have a link to it named rksh for this to work.

The specific constraints imposed by the restricted shell disallow the user from doing the following:

  • Changing working directories: cd is inoperative. If you try to use it, you will get the error message ksh: cd: restricted.

  • Redirecting output to a file: the redirectors >, >|, <>, and >> are not allowed. This includes using exec.

  • Assigning a new value to the environment variables ENV, FPATH, PATH, or SHELL, or trying to change their attributes with typeset.

  • Specifying any pathnames of commands with slashes (/) in them. The shell only runs commands found along $PATH.

  • Adding new built-in commands with the builtin command. (This very advanced feature is outside the scope of this book.)

These restrictions go into effect after the user’s .profile and environment files are run. This means that the restricted shell user’s entire environment is set up in .profile. This lets the system administrator configure the environment as she sees fit.

To keep the user from overwriting ~/.profile, it is not enough to make the file read-only by the user. Either the home directory should not be writable by the user, or the commands in ~/.profile should cd to a different directory.

Two common ways of setting up such environments are to set up a directory of “safe” commands and have that directory be the only one in PATH, and to set up a command menu from which the user can’t escape without exiting the shell. In any case, make sure that there is no other shell in any directory listed in $PATH; otherwise the user can just run that shell and avoid the restrictions listed earlier.

Warning

Although the ability to restrict the shell has been available (if not necessarily compiled in or documented) since the original Version 7 Bourne shell, it is rarely used. Setting up a usable yet correctly restricted environment is difficult in practice. So, caveat emptor.

Trojan Horses

The concept of a trojan horse was introduced briefly in Chapter 3. A trojan horse is something that looks harmless, or even useful, but which contains a hidden danger.

Consider the following scenario. User John Q. Programmer (login name jprog) is an excellent programmer and has quite a collection of personal programs in ~jprog/bin. This directory occurs first in the PATH variable in ~jprog/.profile. Since he is such a good programmer, management recently promoted him to system administrator.

This is a whole new field of endeavor, and John — not knowing any better — has unfortunately left his bin directory writable. Along comes W. M. Badguy, who creates the following shell script, named grep, in John’s bin directory:

/bin/grep "$@"
case $(whoami) in                  Check effective user ID name
root)    nasty stuff here          
               Danger Will Robinson, danger!
         rm ~/jprog/bin/grep       Hide the evidence
         ;;
esac

In and of itself, this script can do no damage when jprog is working as himself. The problem comes when jprog uses the su(1) command. This command allows a regular user to “switch user” to a different user. By default, it allows a regular user to become root (as long as that user knows the password, of course). The problem is that normally, su uses whatever PATH it inherits.[129] In this case, $PATH includes ~jprog/bin. Now, when jprog, working as root, runs grep, he actually executes the trojan horse version in his bin. This version runs the real grep, so jprog gets the results he expects. But it also silently executes the nasty stuff here part, as root. This means that Unix will let the script do anything it wants to. Anything. And to make things worse, by removing the trojan horse when it’s done, there’s no longer any evidence.

Writable bin directories open one door for trojan horses, as does having dot in PATH. Having writable shell scripts in any bin directory is another door. Just as you close and lock the doors of your house at night, you should make sure that you close any doors on your system!

Setuid and Privileged Mode

Many problems with Unix security hinge on a Unix file attribute called the setuid (set user ID) bit. This is like a permission bit (see the earlier discussion of umask): when an executable file has it turned on, the file runs with an effective user ID equal to the owner of the file. The effective user ID is distinct from the real user ID of the process, and Unix applies its permission tests to the process’s effective user ID.

For example, suppose you’ve written a really nifty game program that keeps a private score file showing the top 15 players on your system. You don’t want to make the score file world-writable, because anyone could just come along and edit the file to make themselves the high scorer. By making your game setuid to your user ID, the game program can update the file, which you own, but no one else can update it. (The game program can determine who ran it by looking at its real user ID and using that to determine the login name.)

The setuid facility is a nice feature for games and score files, but it becomes much more dangerous when used for root. Making programs setuid root lets administrators write programs that do certain things that require root privilege (e.g., configure printers) in a controlled way. To set a file’s setuid bit, the superuser can type chmod 4755 filename; the 4 is the setuid bit.

A similar facility exists at the group level, known (not surprisingly) as setgid (set group ID). Use chmod 2755 filename to turn on setgid permissions. When you do an ls -l on a setuid or setgid file, the x in the permission mode is replaced with an s; for example, -rws--s--x for a file that is readable and writable by the owner, executable by everyone, and has both the setuid and setgid bits set (octal mode 6711).

Modern system administration wisdom says that creating setuid and setgid shell scripts is a very, very bad idea. This has been especially true under the C shell, because its .cshrc environment file introduces numerous opportunities for break-ins. In particular, there are multiple ways of tricking a setuid shell script into becoming an interactive shell with an effective user ID of root. This is about the best thing a cracker could hope for: the ability to run any command as root.

Note

There is an important difference between a setuid shell script and a setuid shell. The latter is a copy of the shell executable, which has been made to belong to root and had the setuid bit applied. In the previous section on trojan horses, suppose that the nasty stuff here was this code:

cp /bin/ksh ~badguy/bin/myls
chown root ~badguy/bin/myls
chmod 4755 ~badguy/bin/myls

Remember, this code executes as root, so it will work. When badguy executes myls, it’s a machine-code executable file, and the setuid bit is honored. Hello shell that runs as root. Goodbye security!

Privileged mode was designed to protect against setuid shell scripts. This is a set -o option (set -o privileged or set -p), but the shell enters it automatically whenever it executes a script whose setuid bit is set, i.e., when the effective user ID is different from the real user ID.

In privileged mode, when a setuid Korn shell script is invoked, the shell runs the file /etc/suid_profile. This file should be written so as to restrict setuid shell scripts in much the same way as the restricted shell does. At a minimum, it should make PATH read-only (typeset -r PATH or readonly PATH) and set it to one or more “safe” directories. Once again, this prevents any decoys from being invoked.

Since privileged mode is an option, it is possible to turn it off with the command set +o privileged (or set +p). But this doesn’t help the potential system cracker: the shell automatically changes its effective user ID to be the same as the real user ID — i.e., if you turn off privileged mode, you also turn off setuid.

In addition to privileged mode, ksh provides a special “agent” program, /etc/suid_exec, that runs setuid shell scripts (or shell scripts that are executable but not readable).

For this to work, the script should not start with #! /bin/ksh. When the program is invoked, ksh attempts to run the program as a regular binary executable. When the operating system fails to run the script (because it isn’t binary, and because it doesn’t have the name of an interpreter specified with #!), ksh realizes that it’s a script and invokes /etc/suid_exec with the name of the script and its arguments. It also arranges to pass an authentication “token” to /etc/suid_exec indicating the real and effective user and group IDs of the script. /etc/suid_exec verifies that it is safe to run the script and then arranges to invoke ksh with the proper real and effective user and group IDs on the script.

Although the combination of privileged mode and /etc/suid_exec allows you to avoid many of the attacks on setuid scripts, actually writing scripts that can safely be run setuid is a difficult art, requiring a fair amount of knowledge and experience. It should be done very carefully.

In fact, the dangers of setuid and setgid shell scripts (at least for shells besides ksh) are so great that modern Unix systems, meaning both commercial Unix systems and freeware clones (4.4-BSD-derived and GNU/Linux), disable the setuid and setgid bits on shell scripts. Even if you apply the bits to the file, the operating system does not honor them.[130]

Although setuid shell scripts don’t work on modern systems, there are occasions where privileged mode is still useful. In particular, there is a widely used third party program named sudo, which, to quote the web page, “allows a system administrator to give certain users (or groups of users) the ability to run some (or all) commands as root or another user while logging the commands and arguments.” The home page for sudo is http://www.courtesan.com/sudo/. A system administrator could easily execute sudo /bin/ksh -p in order to get a known environment for performing administrative tasks.

Finally, if you would like to learn more about Unix security, we recommend Practical UNIX & Internet Security by Simson Garfinkel and Gene Spafford. It is published by O’Reilly & Associates.



[123] A good source of information on system administration is Essential System Administration by Æleen Frisch. It is published by O’Reilly & Associates.

[124] This script will fail if your PATH has directories whose names contain spaces. Consider fixing this problem as an advanced exercise for the reader.

[125] If you know C, C++, or Java, and are comfortable with bitwise operations, the umask operation works like this: actual_permission = requested_permission & (~ umask).

[126] Characters for search strings and numeric arguments to vi- and emacs-mode commands do not trigger the KEYBD trap.

[127] This is the book on ksh93 written by David Korn and Morris Bolsky and published by Prentice Hall.

[129] Get in the habit of using su - user to switch to user as if the user were doing a real login. This prevents the importing of the existing PATH.

[130] MacOS X seems to be a notable exception. Be extra careful if you run one or more such systems!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.189.129