blank.gif  
blank.gif
blank.gif


21 Rules for Writing Secure CGI Programs

Simson Garfinkel
Aug. 8,1997

Web Security & CommerceSimson Garfinkel, a journalist and Internet service provider on Martha's Vineyard, and Gene Spafford, a professor at Purdue University, have collaborated on several books for O'Reilly that help adminstrators identify and fix potential security problems. Now, in Web Security and Commerce, they look at how Web sites and online transactions are vulnerable and how you can protect them. This article, excerpted from the book, presents some guidelines for writing secure CGI programs (or programs that interface with a Web server through an API). Even if you aren't a programmer, but manage a site that uses such programs, you'll want to be sure that your CGI programs have been designed using the guidelines listed here.

Most security-related bugs in computer programs are simply that: bugs. For whatever reason, these faults keep your program from operating properly.

Over the years, we have developed a list of general principles by which to code. What follows is an excerpt from that list, edited for its particular relevance to CGI and API programs: 

  1. Carefully design the program before you start.

  2. Be certain that you understand what you are trying to build. Carefully consider the environment in which it will run, the input and output behavior, files used, arguments recognized, signals caught, and other aspects of behavior. List all of the errors that might occur, and how your program will deal with them. Write a code specification in English (or your native language) before writing the code in the computer language of your choice.

  3. Check all values provided by the user. 

  4. An astonishing number of security-related bugs arise because an attacker sends an unexpected value or an unanticipated format to a program or a function within a program. A simple way to avoid these types of problems is by having your CGI programs always check all of their arguments. Argument checking will not noticeably slow your CGI scripts, but it will make them less susceptible to hostile users. As an added benefit, argument checking and error reporting will make the process of catching nonsecurity-related bugs easier. 

    When you are checking arguments in your program, pay extra attention to the following:

    • Filter your arguments, selecting the characters that are appropriate for each application. 
    • Check the length of every argument. 
    • If you use a selection list, make certain that the value provided by the user was one of the legal values.
  5. Check arguments that you pass to operating system functions.

  6. Even though your program is calling the system function, you should check the arguments to be sure that they are what you expect them to be. For example, if you think that your program is opening a file in the current directory, you might want to use the index( ) function in C or Perl to see if the filename contains a slash character (/). If the file contains a slash, and it shouldn't, the program should not open the file. 

  7. Check all return codes from system calls. 

  8. The POSIX programming specification (which is followed by both C and Perl) requires that every system call provide a return code. Even system calls that you think cannot fail, such as write ( ), chdir ( ), or chown ( ), can fail under exceptional circumstances and return appropriate return codes. When a call fails, check the errno variable to determine why it failed. Have your program log the unexpected value and then cleanly terminate if the system call fails for any unexpected reason. This approach will be a great help in tracking down both programming bugs and security problems later on. 

    If you think that a system call should not fail and it does, do something appropriate. If you can't think of anything appropriate to do, then have your program delete all of its temporary files and exit. 

  9. Have internal consistency-checking code. 

  10. If you think that a variable inside your program can only have the values 1, 2, or 3, check it to ensure that it does, and generate an error condition if it does not. (You can do this easily using the assert macro if you are programming in C.) 

  11. Include lots of logging. 

  12. You are almost always better off having too much logging rather than too little. Rather than simply writing the results to standard error, and relying on your web server's log file, report your log information to a dedicated log file. It will make it easier for you to find the problems. Alternatively, consider using the syslog facility, so that logs can be redirected to users or files, piped to programs, and/or sent to other machines. (Remember to do bounds checking on arguments passed to syslog( ) to avoid buffer overflows.) 

    Here is specific information that you might wish to log:

    • The time that the program was run 
    • The process number (PID) 
    • Values provided to the program 
    • Invalid arguments, or failures in consistency checking 
    • The host from which the request came; log both hostname and IP address
  13. Make the critical portion of your program as small and as simple as possible.
  14. Read through your code. 

  15. Think of how you might attack it yourself. What happens if the program gets unexpected input? What happens if you are able to delay the program between two system calls? 

  16. Always use full pathnames for any filename argument, for both commands and data files. 
  17. Rather than depending on the current directory, set it yourself. 
  18. Test your program thoroughly. 
  19. Be aware of race conditions. 

  20. These can be manifest as a deadlock or as failure of two calls to execute in close sequence.

    • Deadlock conditions: Remember, more than one copy of your program may be running at the same time. Use file locking for any files that you modify. Provide a way to recover the locks in the event that the program crashes while a lock is held. Avoid deadlocks or "deadly embraces," which can occur when one program attempts to lock file A and then file B, while another program already holds a lock for file B and then attempts to lock file A. 
    • Sequence conditions: Be aware that your program does not execute atomatically. That is, the program can be interrupted between any two operations to let another program run for a while-including one that is trying to abuse yours. Thus, check your code carefully for any pair of operations that might fail if arbitrary code is executed between them.
    In particular, when you are performing a series of operations on a file, such as changing its owner, stat ing the file, or changing its mode, first open the file and then use the fchown( ), fstat( ), or fchmod( ) system calls. Doing so will prevent the file from being replaced while your program is running (a possible race condition). Also avoid the use of the access( ) function to determine your ability to access a file: using the access( ) function followed by an open( ) is a race condition, and almost always a bug. 
  21. Don't have your program dump core except during your testing. 

  22. Core files can fill up a filesystem. Core files can contain confidential information. In some cases, an attacker can actually use the fact that a program dumps core to break into a system. Instead of dumping core, have your program log the appropriate problem and exit. Use the setrlimit( ) function to limit the size of the core file to 0. 

  23. Do not create files in world-writable directories. 

  24. If your CGI script needs to run as the nobody user, then have the directory in which it needs to create files owned by the nobody user. (This also applies to the /tmp directory.) 

  25. Don't place undue reliance on the source IP address in the packets of connections you receive. 

  26. Such items may be forged or altered. 

  27. Include some form of load shedding or load limiting in your server to handle cases of excessive load. 

  28. What happens if someone makes a concerted effort to direct a denial-of-service attack against your server? One technique is to have your web server stop processing incoming requests if the load goes over some predefined value. 

  29. Put reasonable time-outs on the real time used by your CGI script while it is running. 

  30. Your CGI script may become blocked for any number of reasons: a read request from a remote server may hang. The user's web browser may not accept information that you send to it. An easy technique to solve both of these problems is to put hard limits on the amount of real time that your CGI script can use. Once it uses more than its allotted amount of real time, it should clean up and exit. Most modern systems support some call to set such a limit. 

  31. Put reasonable limits on the CPU time used by your CGI script while it is running. 

  32. A bug in your CGI script may put it in an infinite loop. To protect your users and your server against this possibility, you should place a hard limit on the total amount of CPU time that the CGI script can consume. 

  33. Do not require the user to send a reusable password in plaintext over the network connection to authenticate himself or herself. 

  34. If you use usernames and passwords, use a cryptographically enabled web server so that the password is not sent in plaintext. Alternatively, use client-side certificates to provide authentication. 

  35. Have your code reviewed by another competent programmer (or two, or more). 

  36. After they have reviewed it, "walk through" the code with them and explain what each part does. We have found that such reviews are a surefire way to discover logic errors. Trying to explain why something is done a certain way often results in an exclamation of "Wait a moment . . . why did I do that?" 

  37. "Whenever possible, steal code." 

  38. Don't write your own CGI library when you can use one that's already been debugged. But beware of stealing code that contains Trojan horses. 

    Remember, many security bugs are actually programming bugs, which is good news for programmers. When you make your program more secure, you'll simultaneously be making it more reliable. 

If you're writing scripts in Perl, you'll want to read 5 Specific Rules for Programming in Perl.
This excerpt is from Web Security and Commerce, by Simson Garfinkel with Gene Spafford, ©1997 by O'Reilly and Associates.