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]
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.
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, 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
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.
Option | Resource limited | Option | Resource 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.
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.
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.
Variable | Meaning |
.sh.edchar
|
The character or escape sequence entered by the user
that caused the KEYBD trap.
The value of |
.sh.edcol
|
The position of the cursor on the current input line. |
.sh.edmode
|
Equal to ESC in vi-mode,
empty otherwise.
(Use |
.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.
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.
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
.
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]
This issue was described in Chapter 3. This opens the door wide for “trojan horses,” described in the next section.
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.
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.
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.
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.
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.
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
Do this so you know which
version you are getting, regardless of $PATH
.
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.)
E.g., "$1"
and "$*"
.
This prevents malicious user input from being further evaluated and executed.
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.
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.
Look for metacharacters such as $
or `
(old-style command substitution)
if using the input in an eval or
$(
...)
.
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.
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.
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.)
Often a fresh pair of eyes can spot things that the original author of a program misses.
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.
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.
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=$' \t\n' # 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"
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.
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.
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!
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
.
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.
[128] See http://www.cerias.purdue.edu.
[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!