How to securely allow file uploads using the Stanford Web Application Toolkit

From Web Services Wiki

Revision as of 12:49, 11 March 2009 by Ddonahue (Talk | contribs)
(diff) ←Older revision | Current revision (diff) | Newer revision→ (diff)
Jump to: navigation, search

Contents

Problem

You want to safely allow file uploads through an HTML form on your website.

Solution

Use the StanfordFileUpload class of the Stanford Web Application Toolkit.

Basic Usage

The following code explains the most basic usage of the class. It is a one-script solution which displays a simple form and handles the subsequent file upload by checking it for errors and saving it to a permanent location on success. The script is configured to allow only GIF, JPG, and PNG images less than or equal to one megabyte.

// Include the class
include_once("stanford.fileupload.php");
 
// Create a new StanfordFileUpload using the name of the form field associated with the file
$file = new StanfordFileUpload("my_file");
 
// Set the max filesize (in KB) and allowed extensions
$file->set_max_filesize(1024); // 1 MB
$file->set_allowed_extensions("gif", "jpg", "jpeg", "png");
 
// Default setting is to display the form
$display_form = true;
 
// If the file has been uploaded
if($file->has_been_uploaded() == true) {
 
  // Set the save directory (directory must exist)
  $file->set_save_directory(dirname(__FILE__) . "/../upload/");
 
  // Try to save the file
  if($file->save() == true) {
 
    // Display a success message
    echo "<p>File upload complete!</p>\n";
 
    // Don't display the form
    $display_form = false;
 
  }
 
  // If the file did not pass validation, it was not saved..
  else {
 
    // Display the list of errors
    $file->display_errors();
  }
}
 
// Display form
if($display_form == true) {
 
  // Must set the enctype of the form to multipart/form-data
  echo "<form action='{$_SERVER['PHP_SELF']}' method='post' enctype='multipart/form-data'>\n";
 
  // Set the MAX_FILE_SIZE value (in bytes) -- in this case, we are setting it to 1 megabyte
  echo "<input type='hidden' name='MAX_FILE_SIZE' value='" . $file->get_max_filesize_in_bytes() . "' />\n";
 
  // Create a file input field -- in this case, we are calling it "my_file" (corresponding with our StanfordFileUpload)
  echo "<p><label>Please choose an image file to upload:<br/>\n";
  echo "<input type='file' name='my_file' /></label></p>\n";
 
  // Create a submit button
  echo "<p><input type='submit' name='submit' value='Upload File' /></p>\n";
 
  // End the form
  echo "</form>\n";
 
}

Configuration

StanfordFileUpload may be customized to fit the needs of your application. The developer may choose to limit the size of uploaded files or set up restrictions on file extensions. Some of the available settings are shown below.

// Set the directory in which to save the file (must be writable by the CGI principal - should not be a web accessible location)
$file->set_save_directory("/path/to/my/uploaded_files/");
 
// Set the maximum file size in kilobytes
$file->set_max_filesize(1024);  // 1 MB
 
// Set which file extensions to accept (not case sensitive)
$file->set_allowed_extensions("gif", "jpg", "jpeg", "png");
 
// Change the filename (in this case, we are using a variable called $id to ensure the file has a unique name)
$file->set_filename("uploaded_picture-$id.gif");

Handling errors

Call the save function to try to save the file. If any errors occurred, the file will not be saved and the function will return false. Get a list of error messages to display to the user by calling get_errors.

// Try to save the file
if($file->save() == true) {
  echo "<p>File saved successfully!</p>";
}
 
// Handle errors
else {
  // Display error message(s)
  $file->display_errors();
 
  // Alternatively, get the list of error messages and display them yourself
  $errors = $file->get_errors();
 
  foreach($errors as $error_message) {
    // Output $error_message
  }
}

Getting information about the uploaded file

You may get information about an uploaded file by calling the functions below.

// Get the error code set by PHP
$file->get_error_code();
 
// Get the list of error messages which prevented the file from being saved
$file->get_errors();
 
// Get the file's extension
$file->get_extension();
 
// Get the name of the file to be stored on the server
$file->get_filename();
 
// Get the browser-reported MIME type
$file->get_mime_type();
 
// Get the original name of the file on the client machine
$file->get_original_filename();
 
// Get the full path of the file saved on the server
$file->get_save_location();
 
// Get the size of the file in bytes
$file->get_size();
 
// Get the temporary location of the uploaded file
$file->get_temp_location();

Discussion

Adding StanfordFileUpload to an existing form

Adding StanfordFileUpload to an existing form is a three step process:

  • Set the enctype of the form to multipart/form-data.
  • Set the hidden MAX_FILE_SIZE form field to the appropriate value in bytes.
  • Add a file input field whose name corresponds to the parameter sent to the constructor of StanfordFileUpload. For example:
// Create a new StanfordFileUpload
$file = new StanfordFileUpload("name_of_form_field");
 
// Display form field
echo "<input type='file' name='name_of_form_field' />\n";

How do I support a variable number of file uploads?

If you want to give your users the option of uploading a variable number of files at once, create an array by adding brackets after the field names in the form. In the example below, the name of the array is my_files.

<p><label>File 1:<br/>
<input type="file" name="my_files[]" /></label></p>
 
<p><label>File 2:<br/>
<input type="file" name="my_files[]" /></label></p>
 
<p><label>File 3:<br/>
<input type="file" name="my_files[]" /></label></p>

Once the form has been submitted, use the static method StanfordFileUpload::get_file_uploads($array_name) to get the StanfordFileUpload objects associated with each of the files.

// Check if the form has been submitted
if($_POST['submit'] == true) {
 
  // Get the files
  $files = StanfordFileUpload::get_file_uploads("my_files");
 
  // Loop through the files
  foreach($files as $file) {
 
    // Set restrictions, attempt to save the file, etc.
 
  }
}

Can I customize the output of display_errors?

Yes. Errors are displayed using the following template:

<div class='{error_css_class}'>
  <p>{error_heading}</p>
  <ul>
    <li>Error 1</li>
    <li>Error 2</li>
    <li>Error 3</li>
  </ul>
</div>

There are two functions you may use to customize the output: set_error_heading and set_error_css_class.

// Customize error output
$file->set_error_heading("Oops!  The following errors prevented your file from being saved:");
$file->set_error_css_class("my_css_class");
 
// Display errors
$file->display_errors();

You may then use a stylesheet to customize colors, fonts, margins, etc.

Why is it dangerous to allow files to be uploaded with PHP?

Adding file upload capabilities to a web application results in a lot of potential security vulnerabilities. A user could upload and execute malicious code or overwrite important data, use your script as their own personal file server, or simply send data that is not relevant to the application. A wide variety of error checks must be made in order to filter out possible attacks.

What checks does StanfordFileUpload perform on uploaded files?

StanfordFileUpload performs the following checks on each file that it attempts to save on the server:

  • The size of the file (if limit is provided)
  • The file's extension (if list is provided)
  • If PHP generated an error code
  • If the file already exists
  • If an attempt is made to save above the specified folder
  • If the file is being saved in the cgi-bin directory
  • Makes sure that the file was uploaded through a form and not provided through other means

What can I do to prevent my form from being exploited?

StanfordFileUpload is useful in that it automates a lot of checks, but it requires careful configuration in order to provide a safe solution.

Never allow uploads to cgi-bin

For security reasons, you should never allow users to upload files within the cgi-bin directory. A malicious user may upload a script and execute it using the privileges of your CGI principal, giving them the same access that your own scripts have to the file system. No amount of error checking or filtering is sufficient to justify allowing uploaded files to be placed in the cgi-bin directory. By default, StanfordFileUpload does not allow files to be saved in or under any directory containing the string cgi-bin. Use the function set_allow_cgi_bin($value) to turn the setting on (true) or off (false, the default).

When possible, do not allow uploads to be web-accessible

We do not recommend allowing uploads to be web-accessible. It is suggested that you create a directory outside of the web root to be used for file uploads whenever possible. Sometimes this cannot be easily avoided, depending on the purpose of the application. If user-submitted files must be made available online, place them under the WWW folder, not cgi-bin.

The ideal solution is to allow indirect access to uploaded files using a script that reads the raw data from a file and outputs it to the browser with the appropriate headers. Read more about secure file uploads.

Set permissions on upload folder

Your CGI principal must have insert access on the folder used for uploads. Create a dedicated upload folder as follows:

# Change to the home directory:
cd ~
 
# Make a new folder:
mkdir uploads
 
# Set insert permissions:
fs sa uploads/ [your_cgi_principal] i

Visit IT Services for more information on setting AFS permissions.

Allow only files that meet certain criteria

StanfordFileUpload performs validation on the maximum file size and allowed file extensions. Make use of these features by setting reasonable parameters restricting uploaded files (e.g. only images under 500k).

Unfortunately, filtering files based on extensions and MIME types, which can be modified by malicious users, only provides limited protection. It is difficult to determine the true type of a file using only one method or function. Check the PHP documentation and use Google to find methods of validating specific types of files (i.e. images, Word documents, audio files, etc).

What PHP settings are relevant to file uploads?

At the time of this writing, file uploads are limited to 200 megabytes on Stanford's web servers. To check the current limit and find other useful information, create a phpinfo file and look at the fields listed below.

<?php
// phpinfo.php
echo phpinfo();
?>
  • upload_max_filesize - maximum size of file uploads
  • post_max_size - maximum size of POST requests
  • upload_tmp_dir - location where uploaded files are initially stored
  • file_uploads - must be set to On
  • max_input_time - maximum time allowed for a form submission

To change these settings, create a custom php.ini file in your cgi-bin directory. Please note that the PHP function ini_set has no effect on any of the settings listed.

References

Personal tools