Protecting Web (and other server) applications requires developers to practice common-sense coding techniques. Although most programmers do not intentionally introduce bugs into their work, they are often pressured into turning out code faster than it can be appropriately error-checked. The result is often disastrous.
For example, a reasonably large commercial site ($20,000/day) that I was requested to audit was launched with a serious error that easily exposed credit card information to anyone with the slightest bit of Web savvy. The system provided a way for administrators to view invoices, including the billing information, simply by providing a username as an argument to an orphaned ( unlinked ) page on the site. Although this was a bad idea to start, it was made even worse by the fact that they forgot to protect their server so that it wouldn't display directory contents when a directory was requested rather than a specific page. Within hours of bringing a revised version of their site online, the files had been discovered by someone typing in the wrong URL. Luckily, the customer was kind enough to call and report the problem. (She had viewed another customer's credit card information.) If the page had been discovered by other less scrupulous individuals, it could have resulted in a serious compromise of customer information and a public relations nightmare for the company.
As scary as it may sound, the actual problems with this site were extremely small, but all it takes is one slipup to place your server and information at risk.
The following are steps you can take to avoid similar problems in your coding projects.
Plan your development projects with the appropriate security precautions , rather than adding security as an afterthought. Sites that deal with sensitive information (such as patient records) should consider using mod_ssl for the all transactions, not just logins. Keep in mind that non-SSL-encrypted traffic is extremely easy to sniff. Be sure to plan for growth and future expansion. It isn't easy to retrofit an unprotected site with security features.
Administrative functions such as accounting and reporting should not be housed on Internet-accessible servers. Although it's tempting to use a single server for everything , the preferred approach is to move all administrative functions (account editing, creation, auditing) to an internal (intranet) machine that does not have a direct connection to the Internet.
Database servers should not be made accessible via the Internet. Databases are the "holy grail" of the cracker: By cracking a database, an attacker can gain access to virtually all the sensitive information on the Web site. As an administrator, you should take every precaution to make your database servers inaccessible to anything except your Web server. Of course, the database server is still at risk if the Web server is compromised, but another potential entryway is eliminated.
There's no such thing as "overprotection." Apache provides a number of protection features to lock resources based on hostname, network, or HTTP AUTH. Using these controls to protect against unauthorized access of critical data provides a secondary level of protection that can be effective even if your Web applications can be compromised. Likewise, MySQL should be configured to accept usernames and logins only from trusted hosts .
No matter how much your users complain, do everything you can to keep Web applications contained to a single location. Although it's tempting to enable ExecCGI for your entire server, this will inevitably lead to applications being installed without your knowledge and your server being exposed to the security holes they may bring.
When coding, use absolute paths ( /Users/jray/Sites/myfiles/myfile.txt ); relative paths ( myfiles/myfile.txt ) should be avoided. If an intruder can exploit the Web application or server process to change the current working directory, he or she may be able to overwrite or read from files that should not be accessible.
As seen earlier in this appendix, user input should never be used directly when opening a file or pipe or providing arguments to an external process. If user input is accepted, it should be processed and stripped of potentially harmful characters before the Web application uses it internally. Languages such as PHP make this easy by providing functions such as escapeshellcmd() and escapeshellarg() , which escape and quote strings being used as arguments or shell commands. After they are escaped, the strings cannot be used to trick external processes into running arbitrary commands. Perl scripts can be started with #!/usr/bin/perl -T to turn on tainting checks, which prevent user input from being used directly in commands that affect files or programs outside your script.
You've already got enough to worry about with your own application without having to deal with the possibility of executing external applications. If possible, you should avoid using functions such as system() , exec () , and the Perl/PHP `` backticks. Unfortunately, sometimes this isn't feasible .
If user input is used in an external process that invokes the shell, one must be sure that the input doesn't contain shell metacharacters ( ; , & , * , > , and so on). To get around this, one must either verify all user input or execute code without invoking a shell. One can use the features of system() and exec() to execute commands without the shell. For example, rather than using system("/usr/bin/find - name $filename") , the code should be written as system("/usr/bin/find","-name", "$filename") . Without having to worry about metacharacters, one can safely pass user input as an argument to external programs.
Some external commands rely on the $ PATH environment variable to be set. Like paths, you should explicitly set any needed environment variables directly in your code.
When ”and if ”possible, use stored procedures for handling sensitive data. Many database servers provide access to stored procedures. A stored procedure is a function that is programmed to run on the database server and return the results as a recordset to an application. Rather than using PHP (and therefore your Web server) to handle critical calculations and risk exposing the information necessary to complete the calculations to the outside world, you can keep the information on the database server, which can perform the actions internally, then provide the final results to the Web server application.
All input should be tested at its extremes (high, low, and limit values), as performing tests on the length limits of the input ( exceeding or not meeting the required lengths). For example, if a number is expected to be between 0 and 5, input should be tested at 0, 5, values above 5, and values below zero. You should also verify what will happen when inappropriate values are provided (strings rather than numbers , and so on). To avoid buffer overflows, input length testing should also be performed.
(This is largely unneeded with scripting languages such as Perl and PHP where variables are dynamically defined.)
When reading from files that contain "authoritative" data (critical information that the program requires to make decisions), one should always verify that the file is an actual file, not a symbolic link. It's best not to trust any input that isn't hard coded into the application.
HTML and HTTP were not designed for security. Although tags exist for storing information in hidden fields ( <input type="hidden"> ) and hiding passwords ( <input type="password"> ), these do nothing to actually protect the information they contain. The data in a hidden field can easily be replaced with arbitrary information, and neither input type offers any real protection beyond simple information hiding.
Likewise, the GET method of passing parameters via the URL should never be used for sending critical session information because the remote user can easily edit it. Most modern languages support automatic session management, eliminating the need to manually pass data.
Finally, all code should undergo a formal code review and testing process before being deployed. Regardless of how late or overbudget the testing process places the project, it is absolutely critical to fully test your code before subjecting it to the masses.