How To Upload An Image With PHP

In this quick tutorial we set up a basic HTML form to upload images with PHP, we also explore how to secure our PHP script so it can’t be abused by malicious users.

Setting Up An Image Upload Form

We start with a basic form.

The form contains a submit button and has an action attribute. The action attribute points to the page the form will post all its contents to when the submit button is clicked.

<form action="upload.php" method="POST">
    <button type="submit">Upload</button>
</form>

The form posts to a PHP file called upload.php. This file will handle the image upload. We’ll get to that in a minute.

To allow users to select a file we add a file input field.

<form action="upload.php" method="POST" enctype="multipart/form-data">
    <input type="file" name="image" accept="image/*" />
    <button type="submit">Upload</button>
</form>

At the same time we’ve also added the enctype form attribute and set it to "multipart/form-data". This is needed if we have a file input field in our form.

Because we’re only uploading images we add the accept attribute to the file input element and set it to image/* telling it to only accept files that have a mimetype that starts with image, like image/jpeg or image/png.

Let’s write the PHP image upload handler next.

Handling the PHP Image Upload

We’ll create a new file called upload.php in the same directory as the page that contains our form.

Next we create a directory called "images", also in the same directory. This is where our script will store uploaded image files using the move_uploaded_file function.

<?php

// Get reference to uploaded image
$image_file = $_FILES["image"];

// Image not defined, let's exit
if (!isset($image_file)) {
    die('No file uploaded.');
}

// Move the temp image file to the images/ directory
move_uploaded_file(
    // Temp image location
    $image_file["tmp_name"],

    // New image location, __DIR__ is the location of the current PHP file
    __DIR__ . "/images/" . $image_file["name"]
);

We’re done. This is all that’s needed to make it work.

Unfortunately not everyone on the internet is a saint. Our script currently allows anyone to upload anything, this is a security risk. We need to make sure people upload valid images and are not trying to harm our server.

Securing The Image Upload Process

We’re going to make extra sure these three things are in order.

Making Sure The File Is An Image

Let’s start by making sure the uploaded file is indeed an image.

Using exif_imagetype we can get the image mimetype, the function returns false when the file is not an image.

<?php

// Get reference to uploaded image
$image_file = $_FILES["image"];

// Exit if no file uploaded
if (!isset($image_file)) {
    die('No file uploaded.');
}

// Exit if is not a valid image file
$image_type = exif_imagetype($image_file["tmp_name"]);
if (!$image_type) {
    die('Uploaded file is not an image.');
}

// Move the temp image file to the images/ directory
move_uploaded_file(
    // Temp image location
    $image_file["tmp_name"],

    // New image location
    __DIR__ . "/images/" . $image_file["name"]
);

Next we need to stop users from uploading very large files.

Limiting The File Size

In the example below we limit the image file size to 5MB, of course you can choose your own limit, but it’s a good idea to at least cap it at a certain point to prevent users from uploading gigabytes of data in an attempt to DoS attack your server.

In the directory of our upload.php script we create a .htaccess file and set its contents to the following.

LimitRequestBody 5242880

The LimitRequestBody prevents a request from exceeding 5MB, note that this includes other fields in the form.

Now let’s make sure users can’t uploading 0 byte files. We use filesize to read out the actual file size instead of rely on the ["size"] reported by $_FILES as that can be modified by the user.

<?php

// Get reference to uploaded image
$image_file = $_FILES["image"];

// Exit if no file uploaded
if (!isset($image_file)) {
    die('No file uploaded.');
}

// Exit if image file is zero bytes
if (filesize($image_file["tmp_name"]) <= 0) {
    die('Uploaded file has no contents.');
}

// Exit if is not a valid image file
$image_type = exif_imagetype($image_file["tmp_name"]);
if (!$image_type) {
    die('Uploaded file is not an image.');
}

// Move the temp image file to the images/ directory
move_uploaded_file(
    // Temp image location
    $image_file["tmp_name"],

    // New image location
    __DIR__ . "/images/" . $image_file["name"]
);

Let’s now make sure the file name of the image is valid.

Guarding Against Malicious File Names

Malicious actors can try to upload an image with named '../index.php', our PHP script would now store this “image” in 'images/../index.php' possibly overwriting our own index.php file.

We can try to sanitize the image file name, but a safer approach is to generate our own file name.

We’ll generate some random_bytes and use those as the name for our image.

<?php

// Get reference to uploaded image
$image_file = $_FILES["image"];

// Exit if no file uploaded
if (!isset($image_file)) {
    die('No file uploaded.');
}

// Exit if image file is zero bytes
if (filesize($image_file["tmp_name"]) <= 0) {
    die('Uploaded file has no contents.');
}

// Exit if is not a valid image file
$image_type = exif_imagetype($image_file["tmp_name"]);
if (!$image_type) {
    die('Uploaded file is not an image.');
}

// Get file extension based on file type, to prepend a dot we pass true as the second parameter
$image_extension = image_type_to_extension($image_type, true);

// Create a unique image name
$image_name = bin2hex(random_bytes(16)) . $image_extension;

// Move the temp image file to the images directory
move_uploaded_file(
    // Temp image location
    $image_file["tmp_name"],

    // New image location
    __DIR__ . "/images/" . $image_name
);

Our last step is to prevent code from being executed in our images directory.

Preventing Code Execution In The Images Directory

In our images directory we create a .htaccess file and set its contents to the following to prevent any uploaded PHP files from being run.

This make sure that PHP code in this folder won’t execute. So if someone still manages to upload a PHP file, for example a PHP file disguised as an image, it still won’t run.

php_flag engine off

Our file upload process is now secure. 🎉

Conclusion

We’ve written a basic form with file input element, to handle the upload we created a PHP file that moves the uploaded image to a directory on the server. To secure the upload process, we made sure the filename is valid, the file size is not too high, and the uploaded file is actually an image.

If you’re also looking to add image editing functionality to the file upload field you can use the <pintura-input> element. This Pintura powered web component automatically opens a powerful image editor when an image is added to the field and enables your users to edit images before upload.

I share web dev tips on Twitter, if you found this interesting and want to learn more, follow me there

More articles More articles