How to Secure File Uploads in PHP
Allowing users to upload files is a common requirement for modern web applications, but it also introduces significant security risks. This article outlines the essential security checks you must perform when handling file uploads in PHP to protect your server from malicious attacks, unauthorized execution, and system compromise.
1. Implement a Strict Extension Whitelist
Never use a blacklist to block dangerous extensions like
.php or .exe, as attackers can often bypass
these using alternative extensions (like .phtml,
.php5, or .phar). Instead, define a strict
whitelist of allowed extensions (e.g.,
['jpg', 'jpeg', 'png', 'pdf']) and check the uploaded file
extension against this list.
$allowedExtensions = ['jpg', 'jpeg', 'png', 'pdf'];
$fileExtension = strtolower(pathinfo($_FILES['uploaded_file']['name'], PATHINFO_EXTENSION));
if (!in_array($fileExtension, $allowedExtensions)) {
die("File type not allowed.");
}2. Verify the Real MIME Type
Do not rely on the MIME type sent by the browser
($_FILES['uploaded_file']['type']), as this can easily be
spoofed by an attacker. Use PHP’s finfo extension to
analyze the actual content of the file and verify its MIME type.
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($_FILES['uploaded_file']['tmp_name']);
$allowedMimeTypes = ['image/jpeg', 'image/png', 'application/pdf'];
if (!in_array($mimeType, $allowedMimeTypes)) {
die("Invalid file content.");
}3. Rename Uploaded Files
Keep attackers from predicting the file path or overwriting existing
system files by renaming every uploaded file. Generate a
cryptographically secure random name using random_bytes()
or a UUID, and append the validated extension.
$newFileName = bin2hex(random_bytes(16)) . '.' . $fileExtension;4. Limit the Maximum File Size
Restrict the size of uploaded files to prevent Denial of Service
(DoS) attacks that attempt to fill your server’s disk space. Set limits
both in your php.ini configuration and within your PHP
application script.
In php.ini:
upload_max_filesize = 5M
post_max_size = 5MIn PHP:
$maxFileSize = 5 * 1024 * 1024; // 5 MB
if ($_FILES['uploaded_file']['size'] > $maxFileSize) {
die("File is too large.");
}5. Store Files Outside the Web Root
The most effective way to prevent users from executing malicious
scripts they upload is to store the files in a directory that is not
accessible via a public URL (outside the public_html or
www directory). If users need to download these files, use
a PHP script to read the file content and stream it to the browser.
6. Disable Directory Execution
If you must store uploaded files within the web root, configure your web server to prevent the execution of scripts in the upload directory.
For Apache, place a .htaccess file
inside the upload folder with the following directive:
Header set Content-Security-Policy "default-src 'self';"
<FilesMatch "\.(php|pl|py|jsp|asp|sh|cgi)$">
ForceType text/plain
Deny from all
</FilesMatch>For Nginx, add a rule to your server configuration blocking PHP execution inside the uploads folder:
location /uploads/ {
location ~ \.php$ {
deny all;
}
}
7. Prevent Directory Traversal
Attackers may attempt to manipulate the filename to save files in
sensitive system directories (e.g., using
../../etc/passwd). Always use basename() on
the file path to strip directory traversal sequences before handling the
file.
$safeFilename = basename($_FILES['uploaded_file']['name']);