16.2 Rules to Code By

only for RuBoard - do not distribute or recompile

16.2 Rules to Code By

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

16.2.1 General Principles for Writing Secure Scripts

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. 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.

  2. Show the specification to another person. Before you start writing code, show the specification that you have written to another programmer. Make sure they can understand the specification and that they think it will work. If you can't convince another programmer that your paper design will work, you should go back to the design phase and make your specification clearer. The time you spend now will be repaid many times over in the future.

  3. Write and test small sections at a time. As you start to write your program, start small and test frequently. When you test your sections, test them with both expected data and unexpected data. Where practical, functions should validate their arguments and perform reasonable actions (such as exiting with an error message or returning an error code) when presented with unreasonable data. A large number of security-related programs are simply bugs that have exploitable consequences. By writing code that is more reliable, you will also be writing code that is more secure.

  4. Check all values provided by the user. 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 scripts always check and validate all of their arguments. Argument checking will not noticeably slow your 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.

    For further information on this topic, see Section 16.3, later in this chapter.

    The Seven Design Principles of Computer Security

    In 1975, Jerome Saltzer and M. D. Schroeder described seven criteria for building secure computing systems.[a] These criteria are still noteworthy today. They are:

    Least privilege

    Every user and process should have the least set of access rights necessary. Least privilege limits the damage that can be done by malicious attackers and errors alike. Access rights should be explicitly required, rather than given to users by default.

    Economy of mechanism

    The design of the system should be small and simple so that it can be verified and correctly implemented.

    Complete mediation

    Every access should be checked for proper authorization.

    Open design

    Security should not depend upon the ignorance of the attacker. This criterion precludes back doors in the system, which give access to users who know about them.

    Separation of privilege

    Where possible, access to system resources should depend on more than one condition being satisfied.

    Least common mechanism

    Users should be isolated from one another by the system. This limits both covert monitoring and cooperative efforts to override system security mechanisms.

    Psychological acceptability

    The security controls must be easy to use so that they will be used and not bypassed.

    [a] Saltzer, J. H. and Schroeder, M. D., "The Protection of Information in Computer Systems," Proceedings of the IEEE, September 1975. As reported in Denning, Dorothy, Cryptography and Data Security (Addison-Wesley).

    1. Check arguments that you pass to operating system functions. 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 shouldn't open the file.

    2. Check all return codes from system calls. 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, react appropriately. If you can't think of anything appropriate to do, have your program delete all of its temporary files and exit.

    3. Have internal consistency-checking code. If you think that a variable inside your program can only have the values 1, 2, or 3, check 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.)

    4. Include lots of logging. 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 (under Unix), 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 name of the program being run

      • 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, both hostname and IP address

    5. Some information should not be logged. Other information should only be logged in specially restricted or encrypted log files. Be especially careful with passwords, both those that are valid and those that are not valid. Logging valid usernames and passwords makes it possible for anybody who accesses the log file to compromise your user's accounts. Logging failed username/password attempts may give a person who has access to the log file enough information to break in, because many failed password attempts are simply one- or two-character typos.

    6. Make the critical portion of your program as small and as simple as possible.

    7. Read through your code. 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?

    8. Always use full pathnames for any filename argument, for both commands and data files.

    9. Rather than depending on the current directory, set it yourself.

    10. Test your completed program thoroughly. Be sure to test it with both expected data and unexpected data.

    11. Be aware of race conditions. These can be manifest as a deadlock or as failure of two calls to execute in close sequence:

      Deadlock conditions

      Remember that 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 atomically. 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.

    12. Don't have your program dump core except during your testing. 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.

    13. Do not create files in world-writable directories. If your script needs to run as the nobody user, then have the directory in which it needs to create files owned by the nobody user. Give each script, or at the very least each subsystem, its own namespace for temporary files. (You can do this by giving each script its own directory for temporary files, or else by having each script prepend its temporary files with its own name.) Do not store temporary files in the /tmp directory if the web server is also used as a general host for Unix shell activities.

    14. Don't place undue reliance on the source IP address in the packets of connections you receive. Such items may be forged, altered, or hijacked with proxy servers.

    15. Include some form of load shedding or load limiting in your server to handle cases of excessive load. For example, you can have the script check the load and exit with a polite error message if the load is over 5. This will make it harder for an attacker to launch a denial-of-service attack against your server by repeatedly calling the same script. It will also protect your server from a particular failure mode in which hundreds of users all hit the "reload" button on a slow-running script in an effort to make it run faster.

    16. Put reasonable time-outs on the clock time used by your script while it is running. Your program may become blocked for any number of reasons; for example, a read request from a remote server may hang or 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.

    17. Put reasonable limits on the CPU time used by your CGI script while it is running. 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.

    18. Do not require the user to send a reusable password in plaintext over the network connection to authenticate herself. 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. If your users access an Internet Information Server web server through Internet Explorer, then you can use the NT challenge/response (NTLM), a Microsoft proprietary modification to the HTTP protocol. Finally, you can use HTTP Digest Authentication, which has an MD5 MAC to verify a shared password between the web server and the web browser. Apache 2.0 and above support Digest-based authentication with the mod_auth_digest module; support in many browsers is increasing. The primary disadvantage of digest authentication is that it requires the web server to maintain an essentially unencrypted copy of each user's password. For details on digest authentication, search for the AuthDigestFile directive in the Apache documentation, or look at http://httpd.apache.org/docs-2.0/mod/mod_auth_digest.html.

    19. Have your code reviewed by another competent programmer (or two, or more). 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?"

    20. Whenever possible, reuse code. Don't write your own CGI library when you can use one that's already been debugged. But beware of reusing code that contains Trojan horses.

    Remember, most security flaws are actually programming faults. In a way, this is good news for programmers. When you make your program more secure, you'll simultaneously be making it more reliable.

only for RuBoard - do not distribute or recompile


Web Security, Privacy & Commerce
Web Security, Privacy and Commerce, 2nd Edition
ISBN: 0596000456
EAN: 2147483647
Year: 2000
Pages: 194

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net