Skip to main content

PHP script to upload file securely

How to Write a Secure PHP Script for File Uploads

File uploads are a common feature in web applications, but they can introduce significant security risks if not handled properly. In this article, we'll walk through the steps to securely upload files to a server using PHP. We'll cover key security measures such as file validation, limiting file types, setting file size limits, and managing file storage. We will also create reusable functions to handle the upload process.

1. Create the HTML Form

First, we need an HTML form that allows users to select and upload a file. Ensure that the form uses the POST method and includes the enctype="multipart/form-data" attribute.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Secure File Upload</title>
</head>
<body>
    <form action="upload.php" method="post" enctype="multipart/form-data">
        <label for="file">Choose a file:</label>
        <input type="file" name="file" id="file" required>
        <input type="submit" value="Upload">
    </form>
</body>
</html>
    

2. The PHP Upload Script

Next, we'll create upload.php to handle the file upload. This script will include various security checks to ensure safe file uploads and will use reusable functions to manage these tasks.

Step 1: Define the Upload Function

We'll define a function to handle the file upload, performing all necessary validations and returning any errors.

Check for Upload Errors

First, check if a file has been uploaded and handle any errors that might occur during the upload process.

function uploadFile($file, $upload_dir = '/path/to/upload/directory/', $max_file_size = 2 * 1024 * 1024, $allowed_mime_types = ['image/jpeg', 'image/png', 'application/pdf'], $allowed_extensions = ['jpg', 'jpeg', 'png', 'pdf']) {
    $errors = [];

    if (!isset($file) || $file['error'] != UPLOAD_ERR_OK) {
        $errors[] = 'File upload failed.';
        return ['success' => false, 'errors' => $errors];
    }
}
    

Check File Size

Set a limit on the file size to prevent large files from being uploaded.

if ($file['size'] > $max_file_size) {
    $errors[] = 'File is too large.';
}
    

Check MIME Type

Restrict the types of files that can be uploaded to prevent malicious files from being uploaded. Use MIME types for this purpose.

$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime_type = $finfo->file($file['tmp_name']);
if (!in_array($mime_type, $allowed_mime_types)) {
    $errors[] = 'Invalid file type.';
}
    

Check File Extension

Verify the file extension as an additional security measure.

$file_extension = pathinfo($file['name'], PATHINFO_EXTENSION);
if (!in_array(strtolower($file_extension), $allowed_extensions)) {
    $errors[] = 'Invalid file extension.';
}
    

Sanitize File Name

Sanitize the file name to prevent directory traversal attacks and other issues.

$filename = basename($file['name']);
$filename = preg_replace('/[^a-zA-Z0-9-_\.]/', '_', $filename);
    

Ensure Upload Directory Exists

Define a secure directory for storing the uploaded files. Ensure this directory is outside the web root to prevent direct access.

if (!is_dir($upload_dir)) {
    mkdir($upload_dir, 0777, true);
}

$destination = $upload_dir . $filename;
    

Move the Uploaded File

Finally, move the uploaded file to the destination directory and handle any errors.

if (empty($errors) && move_uploaded_file($file['tmp_name'], $destination)) {
    return ['success' => true, 'filename' => $filename];
} else {
    $errors[] = 'File upload failed.';
    return ['success' => false, 'errors' => $errors];
}
}
    

3. Complete upload.php Script

Here's the complete upload.php script incorporating the reusable upload_file function and handling errors properly.

<?php

function uploadFile($file, $upload_dir = '/path/to/upload/directory/', $max_file_size = 2 * 1024 * 1024, $allowed_mime_types = ['image/jpeg', 'image/png', 'application/pdf'], $allowed_extensions = ['jpg', 'jpeg', 'png', 'pdf']) {
    $errors = [];

    if (!isset($file) || $file['error'] != UPLOAD_ERR_OK) {
        $errors[] = 'File upload failed.';
        return ['success' => false, 'errors' => $errors];
    }

    if ($file['size'] > $max_file_size) {
        $errors[] = 'File is too large.';
    }

    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mime_type = $finfo->file($file['tmp_name']);
    if (!in_array($mime_type, $allowed_mime_types)) {
        $errors[] = 'Invalid file type.';
    }

    $file_extension = pathinfo($file['name'], PATHINFO_EXTENSION);
    if (!in_array(strtolower($file_extension), $allowed_extensions)) {
        $errors[] = 'Invalid file extension.';
    }

    $filename = basename($file['name']);
    $filename = preg_replace('/[^a-zA-Z0-9-_\.]/', '_', $filename);

    if (!is_dir($upload_dir)) {
        mkdir($upload_dir, 0777, true);
    }

    $destination = $upload_dir . $filename;

    if (empty($errors) && move_uploaded_file($file['tmp_name'], $destination)) {
        return ['success' => true, 'filename' => $filename];
    } else {
        $errors[] = 'File upload failed.';
        return ['success' => false, 'errors' => $errors];
    }
}

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    $result = uploadFile($_FILES['file']);

    if ($result['success']) {
        echo 'File uploaded successfully. Filename: ' . htmlspecialchars($result['filename']);
    } else {
        echo 'Errors occurred:';
        foreach ($result['errors'] as $error) {
            echo '<p>' . htmlspecialchars($error) . '</p>';
        }
    }
} else {
    echo 'Invalid request method.';
}
?>
    

Additional Security Measures

In addition to the validations performed in the script, it's important to have software on the server to scan uploaded files for malware. Examples of such software include:

  • Linux: ClamAV, Sophos, and Bitdefender
  • Windows: Windows Defender, McAfee, and Symantec

Conclusion

Secure file uploads in PHP require careful validation and sanitization to mitigate potential security risks. By using the reusable upload_file function, you can handle file uploads safely in your PHP applications. Always keep security in mind and regularly review and update your code to address new threats. Additionally, using malware detection software on your server can help protect against malicious files.

Comments

Popular posts from this blog

PHP Code Review Guidelines

General  The code works  The code is easy to understand  Follows coding conventions  Names are simple and if possible short  Names are spelt correctly  Names contain units where applicable  There are no usages of magic numbers  No hard coded constants that could possibly change in the future  All variables are in the smallest scope possible  There is no commented out code  There is no dead code (inaccessible at Runtime)  No code that can be replaced with library functions  Variables are not accidentally used with null values  Variables are immutable where possible  Code is not repeated or duplicated  There is an else block for every if clause even if it is empty  No complex/long boolean expressions  No negatively named boolean variables  No empty blocks of code  Ideal data structures are used  Constructors do not accept null/none values  Catch clause...

Multiple Checkboxes Validation

This is a regular problem and solution is hardly available on google. When we use multiple checkboxes of same group (same name with square brackets) and you've to read the values from server side, then below solution will help you. Here I'm using jQuery (https://jquery.com/) and jQuery Validate plugin (https://jqueryvalidation.org/) For an example, I've to ask a user which of the listed book they are interested to know about <form id="BooksForm" method="post" name="BooksForm"> <p>Books you are interested in </p> <input class="Books" id="Book1" name="Books[]" type="checkbox" value="1" /> The Inner Game of Tennis <br /> <input class="Books" id="Book2" name="Books[]" type="checkbox" value="1" /> Monk who sold his ferrari <br /> <input class="Books" id="Book3" name=...