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.
- The file needs to be an image.
- The file must be smaller than 5MB.
- The file must have a valid name.
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.