Writing Secure CGI Scripts

Keep your scripts small.
Smaller scripts are easier to read and maintain.
Keep CGI scripts in one place.
This is good maintenance policy, but service providers may also limit CGI scripts to run from certain specified directories.
Understand what your script is doing.
This is especially true for scripts that you download from the WWW (in any language). Beware of guest book scripts and similar items that write data to your account! If you want to install a foreign script, study it carefully, test it, and run a web search to see if there are any bug reports.
Keep information about your site and server host limited.
Hackers will look for weak points in your system. Buggy CGI scripts are easily exploited, but the hacker has to know that they exist and where they go wrong. Don't make it too easy to inspect your programming skills!
  1. Limit access to CGI directory. The CGI directory must be accessible to everyone, or the server wouldn't be able to run your scripts. However, put a blank index page or one with a redirect header into the directory so that the directory content won't be displayed if someone points the browser at the directory.
  2. Delete back-up files of your CGI scripts. When you edit a file with emacs, the editor creates a back-up file with the same name and an added tilde, ~, in the same directory. When the server runs a CGI script, the path and name of the script will be visible in the browser. A hacker may just guess that a back-up file exists and request the back-up page by adding the tilde to the URL. If it does exist, your script will display in the browser.
Check all user provided data
  1. NEVER assume you know what the input is going to be. The server will accept just about everything and pass it to your script if the URL is correct.
  2. Check the size of the user input. Denial-of-service attacks may try to send a large amount of data to the server and try to crash the processor. You can write your own input test and check the HTTP environment for the size of the query string or content length or, even easier, use the mechanisms built into the Perl CGI module.
    The CGI module lets you define two global variables that limit the size of POST and PUT uploads. (PUT is an HTTP method used for file uploads.)
    The variables are
    $CGI::POST_MAX=1024 * 100;  # limits size of POST to 100k
    $CGI::DISABLE_UPLOADS = 1;  # creates an error if PUT is attempted
    
    and should be defined after 'use CGI' statement. They generate errors is a POST is too large or the PUT method is used, which then can be caught by the script. (Example) See the CGI module documentation for more information.
  3. Filter out meta characters from user input:

    & ; ` ' \ " | * ? ~ < > ^ ( ) [ ] { } $ \n \r

    These characters have special meaning and may be used to try tricking a CGI script into executing system commands.
  4. Never pass unchecked data to a shell or system command. In Perl, this includes
    1. system()
    2. exec()
    3. piped open()
    4. eval()
    Email addresses and sentmail are notorious cases. If you want to send an email based on user input, check for correct address format first.
  5. Don't use the environment $PATH variable to call external programs. You can never be sure what the $PATH is set to when to program is running. It is better to specify an absolute or relative path.
  6. Run Perl CGI scripts in 'taint' mode

    Perl provides a mechanism that prevents a script from passing unchecked input to variables. Any variable that is assigned data from outside the program is considered 'tainted' and can not be used to affect anything outside the program unless the variable is 'untainted'. A variable is 'untainted' by performing a pattern matching operation and extracting matched substrings. Hence, the script cannot executed an external command without explicitly looking for the command that is to be executed.

    Taint checks are turned on by running a script with a '-T' flag.

     #!/usr/local/bin/perl -T
    

    With taint checks on, the script must also set the environmental variable $PATH to any directories involved in running an external command.

    $ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin';
    

    Also, if the script use the Perl function require, the path must be included when the 'required' script is in the same directory:

    $file = "myfile.txt;
    require $file;
    

    will cause an error. Instead, write

    $file = "./myfile.txt;
    require $file;