LJ Archive

CGI: Safety First

Hans de Vreught

Issue #34, February 1997

A practical guide to safe CGI.

With the current Web hype a system administrator's nightmare has come true: every user on his system wants to have a home page and wants to do CGI programming. The urge to be on the Web is too strong to keep them off. If they had some elementary knowledge of security, things would not be so bad, but this is not the case: most of those users grew up with MS-DOS, where insecurity is a de facto standard. They have a dangerous attitude toward your system.

One major problem with CGI programming is writing to a file; the web server doesn't run as the user who wrote the CGI script—when it tries to write something in a file, it gets a permission denied error. Lowering the permission of that file isn't a solution—it is just plain disastrous.

As you might know, a web server (typically called httpd) can be run by any user. However, if you want to have the server listening on the HTTP port (port no. 80 according to STD 2, the Internet standard describing “Assigned Numbers”, at this moment, RFC 1700), it must be started up as root. Normally this is done at boot time from one of the rc files located in /etc. Since running the web server as root is a serious breach of security, all web servers can be configured to run as a different user, say user “www”.

You normally would see one server, running as root, which is responsible for opening port 80 and forking off a copy of itself that is running as www. It starts up as many servers as are needed to handle your load (you can often set a minimum and maximum number of servers in the configuration files). The one running as root does no HTTP stuff—that is done by child servers running as www.

There is one inherent security risk in running CGI scripts when everybody can write CGI scripts: the scripts can be read by other users. Even if you remove read permission of others and make the script readable and executable by the server's group only, another person can write a script reading the source of your script. If it is important that the source of the program remains secure, you shouldn't be writing a script. Compiled programs don't have to be readable to be executable, whereas scripts must be both readable and executable.

Before we proceed with the real issue, just consider a few of the consequences of the server running as www. Consider the following script:

#!/bin/sh
cp /bin/sh /home/cracker/bin/sh
chmod 4755 /home/cracker/bin/sh
cat << EOF
Content-type: text/plain
There is no such thing as a secure server!
EOF

You now have created a suid www shell that any cracker can use! For this reason it is important that the user www not have a home directory and not own any important files. Another infamous trick is to send a STOP signal to all servers; that is really sneaky (do you know why?). If you find these kinds of scripts unacceptable, you should ban CGI programming for everybody on your system except a few trusted users.

It should be clear that running all your servers as root is a definite no-no. It is like removing the password entry of root from the password file. If all your servers run as root, dash to your system administrator to correct this and don't leave before it is corrected. If he is hard to convince, well, you have root privileges and maybe you should give him a demonstration of how you can correct his mistake!

Bad Things

The trouble starts when user foo wants to write something in a file. Well, just for the sake of argument we will increment an access counter in a file ~foo/www/access. For example, look at the script counter.sh (it is not very customary to use a Bourne script for CGI programming, but it does the job):

#!/bin/sh
ACCESS=
ACCESS=
rm /home/foo/www/access
echo $ACCESS > /home/foo/www/access
cat << EOF
Content-type: text/plain
You are number $ACCESS to visit this page!
EOF

A nice little script with one problem: it does not work. It would have worked if foo had executed it, but alas, www was the one who ran the script. User foo might then immediately lower his security by entering: chmod 666 access. And since he noticed his default permissions are always wrong, he makes sure he regularly does a chmod 666 *--being unaware of the option of setting his UMASK. Don't think this doesn't occur in real life—I've had several colleagues doing exactly that. Be assured, they were all dedicated MS-DOS users.

Some of them learned it is easier if he doesn't change the files individually, but rather the directories (I almost jumped out of my room when I heard this one): chmod 777 . He didn't realize the writing succeeded because the daemon was able to remove the file access. But not just www could have done it—anybody could have done it!

Quickly entering chmod 1777 may seem the right thing to do. Don't you believe it. Indeed, it isn't that bad, but still, anybody on your system can remove that file with a CGI script.

Good Things

It's time for something less depressing. Can you do it right? Sure you can—it just takes a little bit of effort. The problem is you don't really want your CGI scripts to be executed by a stranger like www. You want to have your scripts running as yourself! For the execution of programs as another user, suid and sguid were invented.

If you make a program suid (or sgid), you run it as the user (or group) who owns the file. That was exactly what you wanted in the first place. So you make your scripts suid. And still under Linux it doesn't work. Why? Because suid scripts are insecure and the Linux kernel refuses suid scripts altogether (look at the comp.os.unix FAQ for some stunning ways to become root).

But suid programs are safe to use. It only means that you have to write a wrapper, a little C program, counter.c, which you make suid:

#include <stdlib.h>
void main(int argc, char *argv[])
{
   exit(system("/home/foo/www/bin/counter.sh"));
}

Does this suid stuff always work? No—many system administrators mount /home as nosuid, meaning the suid bit will never be honored. Why does a system administrator want to do that? Well, you noted the first script in the article that made a suid shell? That wouldn't have worked if /tmp was mounted nosuid. Many system administrators mount only those file systems as suid which must contain suid programs, mounting all others nosuid; for instance, /home doesn't need suid programs for the system to run, so mounting nosuid is a pre-cautious action.

But there is another way. It's a bit like shotgun programming, but you can let sendmail and procmail do the dirty work for you. How does it work? Basically you let the daemon send you mail that will instruct you to perform a certain task. The receiving sendmail will look in your ~/.forward and see that you want your mail to be processed by procmail, which will perform the update script for you. Let me first give you the CGI script:

#!/bin/sh
ACCESS=
ACCESS=
cat <<EOF
Content-type: text/plain
You are number $ACCESS to visit this page!
EOF
echo "Subject: access.sh 1928397071" | sendmail foo

First some remarks about the mail:

  • This mail has no body, which is allowed by RFC 822 (the Internet standard describing the format of mail messages). If you want to include a body, you should precede the body by a mandatory empty line.

  • In the subject we have a script part access.sh that will hint at what script to run. It has some sort of a magic cookie (1928397071) to prevent you from processing e-mail which is accidently triggered. If you want this to be a really secure cookie, you shouldn't be writing this line as a script.

Next it is time for the ~/.forward:

"|IFS=' '&&exec /usr/bin/procmail -f-||exit 75 #bar"

The program procmail needs to look in your file ~/.procmailrc (which shouldn't be world readable, if you want to use cookies for security):

LOGFILE=$HOME/log/procmail
:0 b
* ^From www
* ^Subject: access.sh 1928397071
|$HOME/www/bin/access.sh

So if the daemon sends a mail with a subject access.sh 1928397071, procmail will run access.sh for you:

#!/bin/sh
ACCESS=
ACCESS=
rm /home/foo/www/access
echo $ACCESS > /home/foo/www/access

Mission accomplished: we have written something safely into a file. Are there any catches? Yeah, sure.

First of all, you need to assure that when two people try to access your page, they won't screw up your files. You need some sort of file locking mechanism (check out flock, which also is available in Perl).

Secondly, the sendmail in the CGI script is nonblocking: it doesn't wait until the update is actually done. In most cases, this doesn't really matter, as you are only doing a simple update. Otherwise, you can always try to implement the Perl client-server model.

Don't go for the easy insecure option; keep it safe. Your system administrator will appreciate it.

Hans de Vreught (J.P.M.deVreught@cs.tudelft.nl) is a computer science researcher at Delft University of Technology. He has been using Unix since 1982 (Linux since 0.99.13) and is a profound MS hater (all their products are Bad Things). He likes non-virtual Belgian beer, and he is a real globe-trotter (already twice round the world).

LJ Archive