Mac OS X Reference Library Apple Developer
Search

Shell Script Security

Security is often overlooked when writing shell scripts. Many programmers ignore shell script security under the assumption that anything an attacker can do by attacking a script can be achieved more easily by simply executing the commands themselves. This is not true, however, when the script takes input from an untrusted third party:

Further, most security problems are also correctness bugs even if someone is not trying to attack your code.

This chapter describes a few common mistakes in scripting, shows how these vulnerabilities can be exploited, and explains how to prevent these attacks in your scripts.

This chapter also describes how UNIX permissions and POSIX access control lists (ACLs) affect your scripts and how to manipulate those permissions and ACLs in your scripts.

Environment Attacks

Environment variable attacks are the most common way to manipulate script behavior. By manipulating the environment of a script, you can change its behavior if the script depends on particular values for those environment variables.

Although they are less harmful for scripts these days (because scripts cannot be run setuid in any modern OS), they can still cause incorrect behavior. For setuid binaries, they are even more dangerous. These attacks can also be harmful in a multiuser setting if one user gains the ability to modify the login scripts of another user through a bug or incorrect configuration.

The most common environment attack is modifying the PATH environment variable. This variable controls what gets executed when you type a command without giving the full path.

Consider the following code:

#!/bin/sh
 
ls /tmp

The attack:

Create an executable binary or script that does something harmful and name it “ls”. Then do this:

export PATH=/path/to/malicious/binary:$PATH
/path/to/above/script

Because the path to the malicious binary is first in the search path, the malicious ls command gets executed instead of the real one.

Mitigation:

Always specify absolute paths when executing binaries or other scripts. If your script runs other scripts or binaries that do not use absolute paths internally, you should explicitly set the value of the PATH environment variable in your scripts to prevent problems.

Injection Attacks

The most common type of attack in shell scripts is the injection attack. This type of attack occurs when arguments stored in user-provided variables are passed to commands without proper quoting.

Simple Example

Consider the following example:

read FOO
read BAR
if [ x$FOO = xfoo ] ; then
    echo $FOO
    eval $BAR
fi

This code has two security holes. Can you spot them?

Subtle Example

The following example is more subtle. Instead of running eval, it writes data to a script, but does so without protecting the values:

#!/bin/sh
 
read FOO
 
# ...
 
echo ls $FOO >> myscript.sh
 
# ...
 
chmod a+x myscript.sh
./myscript.sh

The attack:

Pass the value “; rm randomfile” to cause this script to delete a file.

Mitigation:

To fix this bug, change the echo line to read:

echo ls "\"$FOO\"" >> myscript.sh

Backwards Compatibility Example

The following example is not dangerous in modern shells, but is dangerous in older bourne shells:

#!/bin/sh
 
read FOO
echo $FOO

The attack:

Pass the value “; rm randomfile” to cause this script to delete a file in older shells.

Most modern shells parse the statement prior to any variable substitution, and are thus unaffected by this attack. However, for proper security when your script is run on older systems (not to mention avoiding a syntax error if the filename contains spaces), you should still surround the variable with double quotes.

Mitigation:

To fix this bug, change the echo line to read:

echo "$FOO"

Authentication Attacks

In general, you should not rely on a script to determine whether a user does or does not have permission to do something. It is clumsy and error-prone. It is possible to do so, however, and there are right and wrong ways to do it.

The wrong way:

if [ $UID = 100 -a $USER = "myusername" ] ; then
    cd $HOME
fi

This code has three security bugs, and they’re all caused by using variables in ways that are unsafe. For historical compatibility, the OS provides the UID, USER, and HOME environment variables. They are quite useful as long as you aren’t using them for security reasons.

The attack:

$ tcsh
% setenv UID 100
% setenv USER myusername
% setenv HOME $HOME/.ssh
% /path/to/script.sh

Even though most modern Bourne shells protect against modifying UID, the USER variable is unprotected, and not all shells protect the UID variable, either.

Fortunately, the script just changed into a directory. Combined with another exploitable attack such as an injection attack, however, this could be exploited in bad ways.

Mitigation:

To obtain the user ID:

# Effective UID
MYEUID="$(/usr/bin/id -u)"
 
# Real UID
MYUID="$(/usr/bin/id -u -r)"

To obtain the username:

MYUID="$(/usr/bin/id -u -n)"

To obtain the actual home directory:

HOMEDIR="$(dscl . -read /Users/dg NFSHomeDirectory | sed 's/^NFSHomeDirectory: //')"

Note that this method for obtaining the home directory is specific to Mac OS X.

Permissions and Access Control Lists

Mac OS X uses the UNIX permission model, extended by POSIX access control lists. These permission models are described in detail in the “Mac OS X File System Security” in Security Overview section of Security Overview. This documentation assumes that you are already familiar with the permissions model.

Examining File Permissions

UNIX permissions are visible to users in Terminal and in the Finder’s Get Info window. In Terminal, you can easily look at the permissions in a human-readable format by using the ls command as follows:

$ ls -ld filename dirname
drwxr-xr-x  2 username  groupname  68 Jun 16 13:40 dirname
-rw-r--r--  1 username  groupname   0 Jun 16 13:40 filename

The left character indicates whether the file system object is a file (-), directory (d), symbolic link (l), block (b) or character (c) special file, named pipe (p), or UNIX domain socket (s).

The next three characters show the Owner permissions, followed by the Group permissions, and finally, the Other permissions as listed in the following table:

Permissions flag

Octal Bit Value

Meaning

-

n/a

No permission

r

4

Read permission

w

2

Write permission

x

1

Execute permission

s

In the optional first octal digit:

  • 4‚Äîsetuid

  • 2‚Äîsetgid

Setuid or setgid with execute permission

S

See above.

Setuid or setgid without execute permission

t

In optional first octal digit:

1

Sticky bit

The complete set of permissions is often expressed in octal, as defined by the bits in the table above. The first digit includes the sticky bit and setuid and setgid bits. If zero, you may omit it when passing the value to most commands. The remaining three digits contain the Owner, Group, and Other permissions, respectively.

For example, a file that is setuid and setgid, with read/write/execute Owner permissions and read/execute Group and Other permissions, the octal equivalent is 6755:

To show the UNIX permissions of a file, use the stat command as follows:

stat -f "%p" filename

Ignore all but the last four digits returned.

Changing File Ownership and Permissions

The ability to change file ownership and permissions is limited by the operating system for security and quota reasons. Users can:

Non-root users cannot:

The root user can change permissions and ownership arbitrarily except when blocked by BSD file system flags.

With those restrictions in mind, the sections that follow describe how to change permissions and change user and group ownership of files and directories.

Use chown and chgrp to Change User and Groups Ownership

You can change the owner of a file or directory with the chown command:

# Change the owner of a file or directory
sudo chown newowner filename_or_dirname
 
# Change the owner of a directory and everything in it recursively
sudo chown -R newowner dirname

You can change the group for a file with either the chown command or the chgrp command:

# Change the group by itself
chown :newgroup filename_or_dirname
chgrp newgroup filename_or_dirname
 
# Change the group of a directory and everything in it recursively
chown -R :newgroup dirname
chgrp -R newgroup dirname

You can also change both owner and group simultaneously:

# Change the owner and the group
sudo chown newowner:newgroup filename_or_dirname
 
# Change the group of a directory and everything in it recursively
sudo chown -R newowner:newgroup dirname

For more information, see the manual pages for chown and chgrp.

Use chmod to Change File and Directory Permissions

Mac OS X (and other UNIX-based operating systems) provide the chmod command for changing the permissions of files and directories.

The chmod command, short for “change mode”, is so named because it allows you to modify file or directory modes. A mode is a three-digit or four-digit octal representation of the UNIX permissions for a file (or 4-5 digits in languages that require a leading zero, such as C).

There are two basic ways you can use the chmod command: numeric modes and human-readable flags.

Most users use chmod in its human-readable form:

chmod a+rw world_writable_file

This command tells chmod to add read (r) and write (w) access to the existing set of permissions for all users (a). So if the permissions were originally r-x--x-w-, the resulting permissions would be rwxrwxrw-.

You can also add and subtract permissions for the owning user (u), the group (g), or other users (o) separately. For example, to add read (r), write (w), and execute (x) permission for the owning user and take it away from members of the owning group and everyone else, you could issue either of the following commands:

chmod u+rwx,g-rwx,o-rwx filename
chmod u+rwx,go-rwx filename
chmod a-rwx,u+rwx filename

Similarly, you can set the User, Group, or Other permissions without regard to what bits were set before by using equals. For example, to set group permissions to read, no-write, no-execute, you could issue the following command:

chmod g=r filename

Finally, to make an executable run setuid (u+s) and setgid (g+s), you might execute a command like one of the following:

chmod a+rx,ug+s filename
chmod a+rxs filename    # Note: o+s is ignored.

Alternatively, if you know the numeric file mode you want to apply (see “Examining File Permissions” for details), you can pass the chmod command either a three-digit or four-digit mode value:

chmod 666 world_writable_file
chmod 0666 world_writable_file

The chmod command can also be used to modify POSIX access control lists (ACLs). This use is described later, in “Use chmod to Modify Access Control Lists.”

Use chflags to Set Special File Permission Flags

In addition to the standard permission flags, Mac OS X has a few special permission flags that can be set using the chflags or lchflags command (or with the chflags or fchflags API in C). These flags are described in the “Mac OS X File System Security” in Security Overview section of Security Overview.

The permissions flags set with chflags take precedence over any permissions granted by normal UNIX permissions or access control lists.

The usage of the chflags command is fairly straightforward. For example, to make a file immutable (so that it cannot be moved, renamed, deleted, or modified), you can issue one of the following commands:

chflags uchg filename # user flag
sudo chflags schg filename # system flag

Notice that the flag comes in two variants: the user flag and the system flag. The user flag can be changed by the file’s owner and root (just like normal permissions). The system flag can be changed solely by root.

To undo this change, you would issue one of the following commands:

chflags nouchg filename # user flag
sudo chflags noschg filename # system flag

For cross-platform compatibility and readability reasons, Mac OS X supports two other variations on each of these flags: uchange, uimmutable, schange, and simmutable. These variants behave identically to their shortened forms.

There are several other flags you can set with the chflags command, the most common being the user and system append-only flags (uappnd/uappend and sappnd/sappend, respectively).

For more information, read the chflags and lchflags manual pages and the “Mac OS X File System Security” in Security Overview section of Security Overview.

Use chmod to Modify Access Control Lists

The chmod(1) command is most commonly known for its ability to modify UNIX permissions. However, in Mac OS X, it also does double duty, providing the scripting interfaces for modifying a file’s POSIX access control lists (ACLs).

The basic concept of ACLs is fairly straightforward. An access control list is a list of rules (access control entries, or ACEs).

This is a greatly simplified explanation; for full details, read the “Mac OS X File System Security” in Security Overview section of Security Overview.

Each ACL entry looks like this:

username grant rightname
groupname grant rightname
username deny rightname
groupname deny rightname

where username and groupname are the names of a user or group, respectively, and rightname is the name of an access right (read, for example).

You can add an access control entry with the +a flag to chmod. For example, to deny read access on a file to the MySQL user, you would type:

chmod +a "_mysql deny read" filename

To see the results of your changes, type:

ls -le filename

By default, new access control list entries are appended to the end of the list. If you need to insert an access control elsewhere in the list, you can use the +a# flag. For example, to insert a new rule at position zero (the top of the list), you would issue a command like this one:

chmod +a# 0 "_www deny read" filename

You can delete an access control entry with the -a flag like this:

chmod -a "_mysql deny read" filename

This command deletes any entry that is an exact match for the specified rule.

Finally, you can replace an entry with another entry using the =a# flag. For example, to change the username in the rule inserted above from _www to _mdnsresponder, you would type:

chmod =a# 0 "_mdnsresponder deny read" filename

In addition to the basic rules described above, the ACL system in Mac OS X supports inheritance. Any inherited ACL entries for a directory are automatically copied to any new files created within that directory at the time of creation.

You can specify:

You can specify any combination of these flags in an access control entry for a directory by passing the flags as part of the rights list.

For example:

chmod +a "_www deny list,search,directory_inherit" dirname

This rule prevents the _www user from listing the directory’s contents. It also prevents the _www user from accessing any files within the specified directory even with an exact name lookup (search). The rule is inherited by any new directory created inside the specified directory (and any directory created inside that one, and so on), but is not inherited by ordinary files.

Note: Inheritance flags apply exclusively to access control entries for directories. You cannot set these flags on files.

Cross-platform Compatibility Note: Command-line tools behavior for modifying access control lists is not standardized. For tips on handling this across multiple platforms, see ‚ÄúAccess Control List (ACL) Management‚Äù in ‚ÄúDesigning Scripts for Cross-Platform Deployment.‚Äù

For more information about the ACL scheme in Mac OS X is described in “Mac OS X File System Security” in Security Overview section of Security Overview. For more information about the command-line flags for getting and setting ACLs, see the manual page for chmod(1).

Securing Temporary Files

Because the temporary directories in Mac OS X and other UNIX-based operating systems are world-writable, you must take care to ensure that you are modifying the file you think you are modifying.

For example, the following code has two serious bugs:

if [ ! -f /tmp/mytempfile ] ; then
    # Race condition here
 
    touch /tmp/mytempfile
 
    chmod u=rw,og= /tmp/mytempfile
    # Missing error check here
 
    echo My secret password is omnibus > /tmp/mytempfile
fi

An application that happens to get the timing right can create a file called /tmp/mytempfile right after the script checks for its existence, wait for the script to write data into it, and subsequently steal the password. The chmod command would produce an error in this case, but because the script doesn’t check the result code, the error is moot.

To solve this problem, always use the mktemp command to create temporary files. The mktemp command creates files with initial permissions of 0600, and never returns an existing file. (Using mktemp also provides an easy way to obtain a known-unique filename, potentially avoiding unexpected behavior caused by temp file collisions.)

Important: Although Mac OS X does not use a privileged helper to clean up temporary files (except during a reboot), some operating systems do. If a script could potentially take a long time to execute without modifying a temporary file, such privileged cleanup helpers can open up a security vulnerability by deleting the existing temp file out from under your script.

Because of this risk, system-provided temporary directories should only be used to store sensitive data briefly. You should do as little work as possible between creating the file and using it, and should clean up the file as soon as possible afterwards.

Further, if you suspend your scripts for any significant period of time, your scripts must create any sensitive temporary files in a non-world-writable directory.

You should avoid writing senstive data out to temporary files at all if you can possiby avoid it.




Last updated: 2010-06-18

Did this document help you? Yes It's good, but... Not helpful...