Main Page Content
Unix File Permissions And Setuid Part 2
In the last article I hinted, well I more than hinted, that you could run your CGI programs under a user other than the one that the Web server runs as. This practice is usually called setuid on executing or simply setuid. Setuid will allow you to run the program as the owner rather than as the Web user. So, if your program is owned by 'tom' it will execute as the user 'tom' rather than as user 'nobody.'
chmod g+s - set the setgid bit.
chmod u+t - set the sticky bit or directories or numerically: chmod 4777 - setuid and read/write/execute for everyone.
chmod 2777 - setgid and read/write/execute for everyone.
chmod 1777 - set sticky bit and read/write/passthrough on a directory. You can, of course, add the numbers up to combine permissions: chmod 6777 - setuid/setgid and read/write/execute for everyone. When you list your files with ls -l, setuid will show up as an 's' where the owner's execute permission would be, setgid will show up as an 's' where the group's execute permission would be, and the sticky bit will show up as a 't' where the other's execute permission would be. So you'd see something like:
Why would I want to do this?
Usually the Web server runs as user 'nobody' which is an under-privileged user. This is okay as long as the files, scripts, programs, libraries, etc. you are using in your CGI program are accessible to everybody. However, there are times when you don't want these things publicly accessible. For example, let's say that you have a file called my_diary.txt in your UNIX account that you want update from the Web. To allow the user 'nobody' to update this file, it will have to be world readable and writable. You can, of course, limit access to this file from the Web by setting up HTTP authentication (check out Anthony's .htaccess tutorial on how to do this). However, anyone on your UNIX server may be able to get at your file and update it as well. Probably not what you want. Ideally, you'd like to set the permissions on your my_diary.txt file to be only readable and writable by you and the Web server. A common way to do this is by setting the permissions on the file to be readable and writable for the owner only and having your program run as setuid.Cool, now how do I do this?
When last we looked at permissions on UNIX, we had three groups of permissions, one for the owner, one for the group, and one for everybody else. However, there's a fourth set of 'permissions' that we can apply to a file and these are general attributes to be associated with the file or directory. The general attributes are: set user id on executing (setuid), set group id on executing (setgid), and the sticky bit. (I've only every seen the sticky bit applied to directories and so I'll skip it in this article.) These attributes can be set with the command chmod using either method discussed last time. Using the symbolic method we have: chmod u+s - set the setuid bit.chmod g+s - set the setgid bit.
chmod u+t - set the sticky bit or directories or numerically: chmod 4777 - setuid and read/write/execute for everyone.
chmod 2777 - setgid and read/write/execute for everyone.
chmod 1777 - set sticky bit and read/write/passthrough on a directory. You can, of course, add the numbers up to combine permissions: chmod 6777 - setuid/setgid and read/write/execute for everyone. When you list your files with ls -l, setuid will show up as an 's' where the owner's execute permission would be, setgid will show up as an 's' where the group's execute permission would be, and the sticky bit will show up as a 't' where the other's execute permission would be. So you'd see something like:
-rws--S--x 1 dmah staff 6335 Jul 12 09:49 my_cgi
The first 's' means setuid. The second 's' means setgid. And if the sticky bit was set, you'd see a 't' in the last position of the permission string instead on an 'x'. The second 's' is capitalized because the group does not have execute permission on the file. So without the setuid and setgid bits, the permissions would be read/write/execute for the owner and execute for everyone else, or (-rwx-----x). So the above program would run as user 'dmah' when the Web server executed it and not as user 'nobody.' Caveats!
Now that you know how to do it, let me give you a few warnings. Running programs as setuid can be dangerous! The power that you get from running as a regular user rather than an under-privileged user also opens the program up for abuse and is a potential security risk. Setuid allows your program to potentially be able to do anything that you can do from the command line as a normal user. That includes something like rm -fr * in your home directory. It's one of the reasons that a lot of ISPs will not provide you with your own CGI access. So I wouldn't write a setuid program without examining your program carefully for potential security holes as well as at least checking out the World Wide Web Security FAQ. The FAQ contains a lot of good information and some pointers to programs that will help protect you from potential pitfalls. But there's no substitute for careful code inspection by you and knowledgeable others you trust.Fine, I understand the warning, but after setting setuid my scripts don't run anymore.
There's a couple of possible problems here that come up due to the security issues involved in running setuid scripts. In the above examples, I tried hard to talk only about setuid programs and by programs I mean binary executables. So if you had a C program that you compiled and ran as a CGI program, setuid should work as long as the program worked before. With scripts, there's a couple things to watch out for. In most UNIX kernels there exists what is called a 'race condition' when executing scripts. Scripts are pieces of code which are interpreted by, strangely enough, interpreters. Common examples of interpreters are perl, sed, and awk. So when you have in your perl code #!/usr/local/bin/perl it tells the operating system to start executing the perl interpreter with the current script as input. Between the time that the perl interpreter starts executing and the time that it reads in your script the 'race condition' exists. At this time, a mischievous person could 'win the race' and be able to replace your script with another. And if your script is running as setuid, that person's script would run as your user! So their script could do anything that you could do from the command line. As a result, most UNIX kernels will disable users from running scripts as setuid. The most common way around this is to create a wrapper program around your script. A wrapper, in this context, is a small program, possibly written in C, that when executed will simply run your script. The 'race condition' does not exist for real executables and so you won't be thwarted by the kernel itself. The second problem is that when running a perl script as setuid, you aren't really running normal perl. Perl recognizes that your script is setuid and will run a special version of itself called taintperl (you can force perl to run in this mode with the -t flag). Taintperl tries to protect you from doing things that might compromise security. Any data that comes from outside your program, like a Web form, is considered tainted. This data, or data derived from it, can't be used to affect things outside of your program. If you need to do this, you will have to de-taint your data before using it. This is a little outside the scope of this article. I'd recommend that if you don't already have it, invest in the Programming Perl book from O'Reilly.That's it?!
Yup, that's all there is to it! ;) Running your programs or scripts as setuid is far from a trivial task. There are a lot of potentials risks and a lot of coding/debugging that will have to be done in order to get things to work properly. So, I'd recommend that unless you are confident in your programming skills that you out-source this type of activity to a professional programmer. But if you do decide to go it alone, good luck, let me know if I can help, and please don't hold me responsible if anything goes wrong. :)