Skip to Content
Course content

27: File Uploads and Handling

Click on the "Edit" button in the top corner of the screen to edit your slide content.

When you first start handling file uploads in PHP, it's tempting to treat the $_FILES superglobal as a reliable source of truth. You see a filename, you see a temporary path, and your instinct is to just move that file from the temporary folder to a permanent one. I've seen countless junior devs—and honestly, a few seniors in a rush—write code that looks something like this:

$target = "uploads/" . $_FILES['profile_pic']['name'];
move_uploaded_file($_FILES['profile_pic']['tmp_name'], $target);

Trusting the Client (and Why That's a Disaster)

On the surface, this works. You upload my_cat.jpg, it lands in the uploads folder, and you're happy. But here is the problem: you are trusting the user. In the world of web security, trusting the client is the fastest way to get your server compromised. The ['name'] key in the $_FILES array is provided by the browser, which means a malicious user can change it to whatever they want using a simple proxy tool.

Imagine someone uploads a file named ../../../index.php. If your server permissions are loose enough, they've just overwritten your homepage. Or worse, they upload shell.php, disguise it as an image, and then navigate to /uploads/shell.php to execute arbitrary code on your machine. I can't stress this enough: never, ever use the user-provided filename directly in your file system paths.

Sanitization and Strategic Storage

To do this right, we have to stop thinking about the filename as "the name of the file" and start thinking about it as "a suggestion that we should mostly ignore." The better approach is to generate your own unique filename and strictly validate the file's content, not just its extension.

Instead of trusting .jpg at the end of a string, use finfo_file or mime_content_type to look at the actual magic bytes of the file. If the user says it's a JPEG but the server sees it's a PHP script, you drop it immediately. Then, instead of keeping the original name, generate a hash or a UUID. This prevents filename collisions (two people uploading image.jpg) and kills the path traversal attack entirely.

$uploadDir = '/var/www/uploads/';
$fileTmpPath = $_FILES['profile_pic']['tmp_name'];

// Check the actual MIME type
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($fileTmpPath);
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];

if (!in_array($mimeType, $allowedTypes)) {
    die("Sorry, only images are allowed.");
}

// Generate a random name to avoid collisions and security risks
$extension = pathinfo($_FILES['profile_pic']['name'], PATHINFO_EXTENSION);
$newFileName = bin2hex(random_bytes(16)) . '.' . $extension;
$destPath = $uploadDir . $newFileName;

if (move_uploaded_file($fileTmpPath, $destPath)) {
    echo "File uploaded successfully as " . $newFileName;
}

The Cost of Security

Now, the trade-off here is complexity and "user experience" in the backend. By renaming the file, you lose the original context. If a user uploads vacation_in_italy.jpg and you save it as a1b2c3d4...jpg, you can't just look at your folder to see what's what. You'll need a database table to map the random filename back to the original name or the user ID.

There is also the issue of storage location. If you put your uploads folder inside your public web root, you're still potentially exposing yourself to execution attacks if your server isn't configured to disable script execution in that specific directory. The gold standard is to store files outside the public folder (above public_html or www) and serve them through a PHP proxy script that reads the file and sends the correct headers. It's a bit more work, but it's the difference between a professional application and a security liability.




📋 Practical Task

Build a Secure Profile Avatar Uploader

Create a PHP script that allows a user to upload a profile picture. Your implementation must meet the following security requirements:

  • MIME Validation: Use finfo` or mime_content_type to ensure the file is actually an image (JPEG, PNG, or GIF). Do not rely on the file extension.
  • Filename Randomization: Rename the uploaded file to a random string (using random_bytes or uniqid) to prevent overwriting and path traversal attacks.
  • Size Limit: Implement a check to ensure the file is no larger than 2MB.
  • Storage: Save the file into a directory named uploads/, ensuring the script handles the case where the directory doesn't exist yet.

Test your script by attempting to upload a .txt file renamed to .jpg to verify that your MIME check catches the deception.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.