Classic Shell Scripting - Arnold Robbins [218]
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 pattern-matching 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 an 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, PATH, IFS, etc.). ksh93 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 and IFS to space-tab-newline.
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 that the cd succeeds:
cd app-dir || exit 1
Use full pathnames for commands
Do this so that 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 the manual pages for logger(1). If you don't have logger, create a function to keep a log file:
logger( ) {
printf "%s\n" "$*" >> /var/adm/logsysfile
}
logger "Run by user " $(id -un) "($USER) at " $(/bin/date)
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
Even after 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
You can do several nasty things to a system administrator by creating files with spaces, semicolons, backquotes, 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 chmod-ing or editing a file, check it to be sure that 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 missed.
Use setgid rather than 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 rather than root
If you must use setuid to access a group of files, consider making a new, non-root user for that purpose, and setuid