Cover V09, I09


Managing SUID/SGID Files

David Totsch

During the daily operation of your system, you may observe some mild-mannered files that have an ominous bit or two set. Under normal circumstances, these bits grant extraordinary powers. Under other circumstances, the power of the bits may be turned against the unsuspecting system. Normally, the SUID bit gives ordinary users the ability to perform functions like changing their own passwords. However, a cracker may expend significant effort and patience to set the same bit on a covert copy of a shell. As sys admins, we need to discern between the mild-mannered and sinister files. The ability to distinguish the well-intended from the malicious begins with identifying the special powers. Instead of the ordinary “x” execute permissions for the file user and group, a long listing reveals an “s”.

An “s” in the user execute position tells us the file is SUID. When a user runs the program, the program runs as though the owner of the file started the program. The effective UID of the process becomes that of the file owner, instead of the user that made the request. When an “s” appears in the execute position for the group, the file is an SGID file. The process runs with an effective group ID that reflects the file group, instead of the user’s active group. On your system, you may find files that are only SUID, some that are only SGID, and some that are both.

When these special power bits are displayed by ls(1), we see only nine bits (read, write, and execute for the file user, group, and other). However, the real bits that hold SUID and SGID privileges are stored in the high-order bits of the file mode. We can see this when trying to set the bits for ourselves. When using chmod(1) to set mode with numeric information, the first of four numbers is used to indicate SUID and SGID. For instance, to set the mode of a file to “r-sr-x-r-x” on the program testprog, you would use:

chmod 4555 testprog
In that leading position, a six asks that the file be both SUID and SGID, a four indicates SUID, a two indicates SGID, and a one indicates the sticky bit (a.k.a., the text bit — a discussion better left for another occasion).

Just because we can set the S[UG]ID bits on a file does not mean that we should. Homegrown S[UG]ID programs are a poor idea, and shell scripts with the privilege are even worse. Before the SUID or SGID capability is given to a program, be sure to have a good reason for doing so. Raise your level of concern if the program has a shell escape capability or is designed to call another program or shell script. If the program allows a shell escape, it could be used to abuse the more powerful privileges of the SUID user, or masquerade as the SUID user while poking around looking for security holes. If the program is designed to call another program or shell script, exercise care to shut down additional opportunities for security breaches. A careless programmer can make it simple to divert the SUID program to run an unintended copy of a program by simply changing the PATH before running the SUID program. When an SUID program runs another program, there is a whole set of similar environmental concerns to address before the program is released into production.

Recognizing potential SUID problems from development specifications can be difficult. Let’s say there is a C program (called rptwrtr) on your system written in-house, and it allows users to create ad-hoc reports. At present, the accessibility of this program is:

r-xr-xr-x  1  rdbms  rdbms ... rptwrtr
On this system, the user and group rdbms are used to hold everything associated with this software. Anyone on the system can run rptwrtr. Additionally, more than one application on the system has an access requirement to use rptwrtr; users from more than one group will be using the program.

rptwrtr is an interactive program that walks the user through the process of specifying report parameters. One nice feature of this program is the ability to make last-minute changes to report definitions by invoking vi(1). The latest release, however, is designed to track the requested number and type of reports by saving information in a single file. This program makes it into production just in time for users to have problems with the new feature: the program outputs file permission errors when it tries to save the statistical information, but not all users are reporting the error messages. After a few moments of investigation, it is determined that the stats file itself is the culprit because it has the following permissions:

rw-rw----  1  jj  acctng ... rptwrtr.stats
The permissions on the usage statistics file reflect the first user to have their information saved. The developers had the foresight to set the file creation mask to prevent all users on the system from messing with the data. However, only the user “jj”, or users active in the group “acctng”, will be successful in having their usage statistics saved. The developers confer for a few minutes and produce the following solution: make rptwrtr SUID to rdbms and change the permissions on rptwrtr.stats to:

rw-rw----  1  rdbms  rdbms ... rptwrtr.stats
These changes will work just fine. Since rptwrtr will be running as the user rdbms, the usage statistics will be saved no matter who invoked the program.

The solution seems logical and reasonable. However, remember that feature that allows the user to make last-minute changes to report definitions by starting up vi(1)? After the SUID permissions are set, users that choose to make last-minute changes to the report definition file will be running vi(1) as rdbms. Users that have some vi(1) knowledge can now run commands as rdbms. I hope that none of those users decide to run rm(1) in directories that contain vital data or programs owned by rdbms. Everything owned by the user rdbms is at risk.

A bit of a compromise is to make the program SGID to rdbms. The risk of the SGID solution would be in how much exposure access to the group yields. A better solution is to make rptwrtr SGID to a special group set up to be the group of rptwrtr.stats only.

In the above case, S[UG]ID is an attractive solution because it does not require code changes. There was a hidden pitfall in features of the program that exposed a security risk when granting S[UG]IG privilege. Exposure to risk is not limited to complied programs, though.

Shell scripts are a definite, no compromise “no” when S[UG]ID privileges are requested. Securing a shell script from subversion is an insurmountable task. Yes, you can set up your own environment for the shell script: PATH, EDITOR, IFS, everything, yet there is no guarantee that you have closed down all avenues of subversion. Your script should also trap and handle all signals or the user could try to break out of the script and have a copy of the shell running as the SUID user. The shell syntax itself may also open up a security hole. The script should also not use any program that has shell escape capability (such as rptwrtr). Even worse, the script may be invoked under peculiar circumstances that induces unpredicted behavior (that a cracker may be more than willing to introduce to your system).

I know you would not write a script like this, but take a look at the following shell fragment:

ls -1 | sed "s/^/wc -l /" | sh
The desire here is to obtain wc(1) output for all files in the current working directory. There are better ways, but this one works. Now, let’s say the following file is in the current working directory when this script runs:

What will the shell receive for this file name? The shell will be sent the syntax:

wc -l x;gotcha
The shell will run wc(1) against the file x, and then go looking for the program gotcha. If you have taken the care to provide a PATH that does not search the current working directory or world-writable directories, you are probably safe. But, what if one of the files in the current working directory is named like this:

Now you have a problem! I have convinced your script fragment to set the PATH and run my program gotcha.

These circumstances are very contrived and not particular to vulnerabilities only from setting scripts to be S[UG]ID. What I wanted to get across is that it is very difficult to predict all of the situations your scripts may encounter. Your best defense is to have a simple, staunch policy that prohibits S[UG]ID shell scripts.

On occasion, you may have a requirement to grant non-root users extended powers (probably to run those scripts they originally wanted to make S[UG]ID). My recommendation is to load sudo(1), or investigate generating a restricted copy of your administrative interface. sudo(1) is generally available as freeware; check your OS vendor-specific sites for a copy. sudo(1) is widely accepted as a secure, easily managed mechanism to give users access to privileged function without compromising the entire system. Another alternative, one that you will have to evaluate for yourself, is to use your administrative interface to generate an instance of itself that provides limited access to privileged capabilities. In my experience, the administrative interface route is desirable when company policy shuts off the sudo(1) avenue with an anti-freeware policy.

Now that I have taken the stance that SUID programs are suspect, I had better provide some criteria for identifying acceptable instances. For the most part, you can trust your UNIX vendor (but you will not embarrass me if you challenge them). For example, passwd(1) is a very handy, well-written program that is SUID to root that you really would not want to remove from your system. Third-party software vendors are the next group that might introduce S[UG]ID files onto your system. Challenge them, too. In his book UNIX Security: A Practical Tutorial (Unix/C), Derek Arnold recommends removing the privilege and waiting to see if functionality breaks. That is a hard stance, but the provider is probably not going to allow you to peruse their source code to satisfy your security concerns. In-house developers should be able to document clear needs for the functionality and accompanying programming practices. Basically, the criteria for acceptable S[UG]ID files is that you are satisfied in understanding the requirements that drive the necessity.

The most proactive stance you can take is to defend your system against the power of the bits. One defensive mechanism is to monitor your system for the appearance of new S[UG]ID files. This is not to say you cannot trust the software change process, but you want to know when new ones appear on your system. There are some preventative measures to take, as well. A cracker just loves the playground you create when setting up a directory with write permissions.

First, let’s see how to monitor a system for new S[UG]ID files. For a simple way to make a list of them, turn to your trusty workhorse find(1). We want find(1) to list files that are SUID, SGID, or both. find(1) has the perfect option to detect the mode of a file: -perm. The -perm option accepts two kinds of mode arguments: one for exact mode match, and one for relative matching. Choose the relative matching since permissions other than S[UG]ID are not of interest. The relative mechanism is invoked by preceding the mode with a dash. Our find(1) statement will look like this:

find / \( -perm -4000 -o -perm -2000 \)
Start from the root of the directory structure. This could generate quite a load on your system. Any place you skip intentionally is a place where an S[UG]ID file could appear and you would not spot it. You may also ask why the OR statement is used instead of -perm with an argument of “6000” — such a statement would list only files that are both SUID and SGID, silently ignoring files that are one or the other.

I suggest that you print out the list that is currently on your system. Any files you do not understand off of the top of your head are candidates for investigation. You will probably find that many of them, like passwd(1), are part of your standard UNIX release and can, therefore, be trusted. Others may be part of third-party software packages. Call the vendor and find out why they require the privilege. Have your in-house developers defend their programs, too. When you are satisfied that you understand the S[UG]ID files on your system, you have a base-line list.

Once you have a base-line list, you need to be able to detect changes. I have already mentioned that we are looking for new files. You might also be interested if a special powers file is removed. Changes to the file size, owner, group, and date might also interest you when it comes to S[UG]ID files. Just the path names will not be sufficient. Add the -exec option and ask for a long listing of each file. Your list should begin to look something like this:

-r-sr-xr-x   1 root   bin   65536 \
   Jun 10 1996 /usr/bin/login
-r-sr-xr-x   1 root   bin   98304 \
   Jun 10 1996 /usr/bin/passwd
-r-sr-xr-x   1 root   bin   20480 \
   Jun 10 1996 /usr/bin/su
-r-sr-xr-x   1 root   bin   45056 \
   Feb 18 1999 /usr/bin/at
-r-sr-xr-x   1 root   bin   24576 \
   Feb 18 1999 /usr/bin/crontab
-r-sr-sr-x   2 root   mail  36864 \
   Jun 10 1996 /usr/bin/mail
-r-sr-sr-x   2 root   mail  36864 \
   Jun 10 1996 /usr/bin/rmail
-r-sr-xr-x   1 root   bin   20480 \
   Jun 10 1996 /usr/bin/chfn
-r-sr-xr-x   1 root   bin   16384 \
   Jun 10 1996 /usr/bin/chsh
-r-xr-sr-x   1 bin    sys   16384 \
   Jun 10 1996 /usr/bin/ipcs
-r-sr-xr-x   1 root   bin   16384 \
   Jun 10 1996 /usr/bin/newgrp
-r-xr-sr-x   2 bin    sys   16384 \
   Jun 10 1996 /usr/bin/uptime
-r-xr-sr-x   2 bin    sys   16384 \
   Jun 10 1996 /usr/bin/w
-r-xr-sr-x   1 bin    tty   16384 \
   Jun 10 1996 /usr/sbin/wall
-r-sr-xr-x   1 root   bin   45056 \
   Apr 3 1998  /usr/sbin/lpadmin
-r-sr-xr-x   1 lp     bin   20480 \
   May 30 1996 /usr/sbin/lpfence
-r-sr-xr-x   1 lp     bin   28672 \
   Apr 3 1998  /usr/sbin/lpmove
-r-sr-xr-x   1 root   bin   49152 \
   Apr 3 1998  /usr/sbin/lpsched
-r-sr-xr-x   1 lp     bin   12288 \
   May 30 1996 /usr/sbin/lpshut
-r-sr-xr--   1 root   lp    24576 \
   Apr 3 1998  /usr/sbin/rlp
The output of ls(1) gives us exactly what we will need to detect various changes. The first set of characters allows us to detect any changes in permissions on the files (added or deleted). We also have the user and group information on each file (so you can see that an SUID to mail file suddenly changed to SUID root). The file size information will indicate if anyone swapped the contents of a real S[UG]ID file with their own. The file creation date is also captured. This will alert us if someone has been monkeying with the file. Many of these attributes will also alert us if the file was changed during an upgrade or recovery (some extra noise to tolerate). Finally, we get the full path name to the S[UG]ID file.

It is arguable that a checksum would be a vast improvement over a simple file size comparison. (A crafty cracker could replace a program with a script of the same size.) If what(1) returns a revision string, you might want to compare that, too. Both of these are improvements that I will leave to the reader. If you are running HP-UX, you might want to take a look at pdf(4). HP-UX supports several commands that assist in the creation of product description files and simplify the checking process. You should also be forewarned that pdf(4) functionality will become obsolete in deference to the same functionality within HP’s Software Distributor.

Now that we have our list, we need to be able to recognize changes. There are two methods I have put into practice: 1) using a check sum on the list and comparing it to the known good value and 2) using diff(1) to detect differences between the current list and an acceptable list. First of all, both mechanisms demand that you make a listing of known acceptable S[UG]ID files. To keep a cracker from meddling with the list, it should be kept in a secure, off-line location (like a tape, locked in your desk drawer). Your detection mechanism should account for the fact that a cracker will be expecting you to scan for new or changed S[UG]ID files. However, a cracker could still mess with your checksum file. My suggestion is to encode the checksum comparison into a very simple C program. Just be careful to have the program calculate the good numbers for comparison so that a cracker cannot scan the executable for strings and discover the checksum. Keep your known good listing on tape for comparison when your monitor script detects changes. How often should you scan? Once per day? Once per week? Once per month? How long do you want a cracker to enjoy the privileges that creating a new S[UG]ID file will give them before you detect it?

In my opinion, simply monitoring for changes to the list of S[UG]ID files is not enough. I want to defend my system in every way that is available. One way to shut down S[UG]ID files that may exist in a file system, and thwart the power of new ones that are created, is to visit your mount(1M) options. Most variants of mount(1M) provide an option that disables SUID execution. For example, the HP-UX mount(1M) command for both hfs and vxfs file system types have the option nosuid. As expected, the nosuid option disables existing S[UG]ID files and any new ones that are created. You will be able to set the bits, but the special execution request will not be granted. It might be prudent to mount all data file systems (or any file system that will not have executables) with the nosuid option. Now you have some mount points that find(1) can skip when searching for S[UG]ID files.

Managing SUID and SGID files is, fortunately, a task that can be automated. In a few minutes, you can have mechanisms in place that quietly monitor your mild-mannered systems for files that have the extraordinary powers granted by this pair of bits.

About the Author

David enjoys UNIX, but he would rather be camping with his family in the near-by Front Range. He can also be found expounding on the virtues of UNIX at the HPWorld conferences ( David can be found at